From 57f2fc3fe18065855cf085f8381a409ac4234b1c Mon Sep 17 00:00:00 2001 From: Joshua Butner <53810+heyjawrsh@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:52:30 -0500 Subject: [PATCH 01/54] Wrapping phase 3 --- src/ai/parser.ts | 82 ++++++++++++++++ src/ai/prompts/bookmark-grouping.ts | 86 +++++++++++++++++ src/ai/prompts/tab-grouping.ts | 70 ++++++++++++++ src/ai/provider.ts | 32 +++++++ src/ai/providers/local.ts | 37 ++++++++ src/ai/providers/relaxed.ts | 139 ++++++++++++++++++++++++++++ src/ai/providers/yolo.ts | 134 +++++++++++++++++++++++++++ src/ai/types.ts | 30 ++++++ src/background/service-worker.ts | 57 +++++++++--- 9 files changed, 654 insertions(+), 13 deletions(-) create mode 100644 src/ai/parser.ts create mode 100644 src/ai/prompts/bookmark-grouping.ts create mode 100644 src/ai/prompts/tab-grouping.ts create mode 100644 src/ai/provider.ts create mode 100644 src/ai/providers/local.ts create mode 100644 src/ai/providers/relaxed.ts create mode 100644 src/ai/providers/yolo.ts create mode 100644 src/ai/types.ts diff --git a/src/ai/parser.ts b/src/ai/parser.ts new file mode 100644 index 0000000..9e6bea5 --- /dev/null +++ b/src/ai/parser.ts @@ -0,0 +1,82 @@ +import { z } from "zod"; +import type { TabOrganizationAIResult } from "./types"; +import type { TabGroupColor } from "@/shared/types"; + +const TAB_GROUP_COLORS: TabGroupColor[] = [ + "grey", "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange", +]; + +const TabGroupSuggestionSchema = z.object({ + name: z.string(), + color: z.string().transform((c) => + TAB_GROUP_COLORS.includes(c as TabGroupColor) ? c as TabGroupColor : "grey" + ), + tabIds: z.array(z.number()), +}); + +const TabOrganizationSchema = z.object({ + groups: z.array(TabGroupSuggestionSchema), + stale: z.array(z.number()).default([]), + duplicates: z.array(z.array(z.number())).default([]), + reasoning: z.string().default(""), +}); + +const BookmarkFolderSchema = z.object({ + name: z.string(), + bookmarkIds: z.array(z.string()), +}); + +const BookmarkOrganizationSchema = z.object({ + folders: z.array(BookmarkFolderSchema), + duplicates: z.array(z.array(z.string())).default([]), + reasoning: z.string().default(""), +}); + +const LocationSuggestionSchema = z.object({ + folderId: z.string(), + folderPath: z.string(), + confidence: z.number().min(0).max(1), + reason: z.string(), +}); + +const BookmarkLocationSchema = z.object({ + suggestions: z.array(LocationSuggestionSchema), +}); + +export function parseTabOrganization(raw: string): TabOrganizationAIResult { + const json = extractJson(raw); + const parsed = TabOrganizationSchema.parse(json); + return parsed as TabOrganizationAIResult; +} + +export function parseBookmarkOrganization(raw: string) { + const json = extractJson(raw); + return BookmarkOrganizationSchema.parse(json); +} + +export function parseBookmarkLocation(raw: string) { + const json = extractJson(raw); + return BookmarkLocationSchema.parse(json); +} + +function extractJson(text: string): unknown { + // Try parsing the whole string first + try { + return JSON.parse(text); + } catch { + // Look for JSON in code blocks + const codeBlockMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/); + if (codeBlockMatch) { + return JSON.parse(codeBlockMatch[1].trim()); + } + + // Look for first { to last } + const firstBrace = text.indexOf("{"); + const lastBrace = text.lastIndexOf("}"); + if (firstBrace !== -1 && lastBrace > firstBrace) { + return JSON.parse(text.slice(firstBrace, lastBrace + 1)); + } + + throw new Error("No valid JSON found in AI response"); + } +} diff --git a/src/ai/prompts/bookmark-grouping.ts b/src/ai/prompts/bookmark-grouping.ts new file mode 100644 index 0000000..b0f465c --- /dev/null +++ b/src/ai/prompts/bookmark-grouping.ts @@ -0,0 +1,86 @@ +import type { BookmarkInfo } from "@/shared/types"; + +interface BookmarkInput { + id: string; + title: string; + url?: string; +} + +export function buildBookmarkOrganizePrompt( + bookmarks: BookmarkInput[], + options: { includeUrls: boolean }, +): string { + const bookmarkList = bookmarks + .map((b) => { + const parts = [`id:"${b.id}"`, `title:"${b.title}"`]; + if (options.includeUrls && b.url) parts.push(`url:"${b.url}"`); + return ` { ${parts.join(", ")} }`; + }) + .join("\n"); + + return `You are a bookmark organizer. Analyze these bookmarks and suggest a folder structure. + +BOOKMARKS: +${bookmarkList} + +RULES: +- Create logical folder categories (e.g., "Dev Tools", "News", "Shopping") +- Use short, descriptive folder names (1-3 words) +- Each bookmark should belong to exactly one folder +- Identify duplicate bookmarks (same URL) +- Provide brief reasoning + +Respond with ONLY valid JSON matching this schema: +{ + "folders": [ + { "name": "string", "bookmarkIds": ["string"] } + ], + "duplicates": [["string", "string"]], + "reasoning": "string" +}`; +} + +export function buildBookmarkLocationPrompt( + bookmark: BookmarkInput, + folders: { id: string; path: string }[], + options: { includeUrls: boolean }, +): string { + const folderList = folders + .map((f) => ` { id:"${f.id}", path:"${f.path}" }`) + .join("\n"); + + const bookmarkDesc = options.includeUrls && bookmark.url + ? `title:"${bookmark.title}", url:"${bookmark.url}"` + : `title:"${bookmark.title}"`; + + return `Suggest the best folder for this bookmark. + +BOOKMARK: { ${bookmarkDesc} } + +EXISTING FOLDERS: +${folderList} + +Respond with ONLY valid JSON matching this schema: +{ + "suggestions": [ + { "folderId": "string", "folderPath": "string", "confidence": number, "reason": "string" } + ] +} + +Return up to 3 suggestions, ranked by confidence (0-1).`; +} + +export function bookmarksToYoloInput(bookmarks: BookmarkInfo[]): BookmarkInput[] { + return bookmarks.map((b) => ({ + id: b.id, + title: b.title, + url: b.url, + })); +} + +export function bookmarksToRelaxedInput(bookmarks: BookmarkInfo[]): BookmarkInput[] { + return bookmarks.map((b) => ({ + id: b.id, + title: b.title, + })); +} diff --git a/src/ai/prompts/tab-grouping.ts b/src/ai/prompts/tab-grouping.ts new file mode 100644 index 0000000..06d47b9 --- /dev/null +++ b/src/ai/prompts/tab-grouping.ts @@ -0,0 +1,70 @@ +import type { TabInfo } from "@/shared/types"; + +interface TabInput { + id: number; + title: string; + url?: string; + lastAccessed?: number; +} + +export function buildTabGroupingPrompt( + tabs: TabInput[], + options: { includeUrls: boolean }, +): string { + const tabList = tabs + .map((t) => { + const parts = [`id:${t.id}`, `title:"${t.title}"`]; + if (options.includeUrls && t.url) parts.push(`url:"${t.url}"`); + if (t.lastAccessed) { + const daysAgo = Math.floor( + (Date.now() - t.lastAccessed) / (1000 * 60 * 60 * 24), + ); + parts.push(`last_accessed:${daysAgo}d_ago`); + } + return ` { ${parts.join(", ")} }`; + }) + .join("\n"); + + return `You are a browser tab organizer. Analyze these open tabs and suggest logical groupings. + +TABS: +${tabList} + +RULES: +- Group tabs by topic, project, or activity (not just domain) +- Use short, descriptive group names (1-3 words) +- Assign a color from: grey, blue, red, yellow, green, pink, purple, cyan, orange +- Try to use different colors for different groups +- A tab can only belong to one group +- Tabs that don't fit any group can be omitted +- Flag tabs as "stale" if last_accessed is more than 7 days ago and they seem unimportant +- Flag exact duplicate URLs (same URL appearing multiple times) — list sets of duplicate tab IDs +- Provide brief reasoning for your grouping choices + +Respond with ONLY valid JSON matching this schema: +{ + "groups": [ + { "name": "string", "color": "string", "tabIds": [number] } + ], + "stale": [number], + "duplicates": [[number, number]], + "reasoning": "string" +}`; +} + +export function tabsToYoloInput(tabs: TabInfo[]): TabInput[] { + return tabs.map((t) => ({ + id: t.id, + title: t.title, + url: t.url, + lastAccessed: t.lastAccessed, + })); +} + +export function tabsToRelaxedInput(tabs: TabInfo[]): TabInput[] { + return tabs.map((t) => ({ + id: t.id, + title: t.title, + lastAccessed: t.lastAccessed, + })); +} diff --git a/src/ai/provider.ts b/src/ai/provider.ts new file mode 100644 index 0000000..6e115bf --- /dev/null +++ b/src/ai/provider.ts @@ -0,0 +1,32 @@ +import type { AIProvider } from "./types"; +import type { Settings } from "@/shared/types"; +import { getSettings } from "@/core/storage"; +import { LocalProvider } from "./providers/local"; +import { RelaxedProvider } from "./providers/relaxed"; +import { YoloProvider } from "./providers/yolo"; + +export async function getAIProvider(): Promise { + const settings = await getSettings(); + return createProvider(settings); +} + +function createProvider(settings: Settings): AIProvider { + switch (settings.aiTier) { + case "secure": + return new LocalProvider(); + case "relaxed": + return new RelaxedProvider( + settings.aiModelProvider, + settings.claudeApiKey, + settings.openaiApiKey, + ); + case "yolo": + return new YoloProvider( + settings.aiModelProvider, + settings.claudeApiKey, + settings.openaiApiKey, + ); + default: + return new LocalProvider(); + } +} diff --git a/src/ai/providers/local.ts b/src/ai/providers/local.ts new file mode 100644 index 0000000..48c2a58 --- /dev/null +++ b/src/ai/providers/local.ts @@ -0,0 +1,37 @@ +import type { AIProvider, TabOrganizationAIResult } from "../types"; +import type { + TabInfo, + BookmarkInfo, + BookmarkOrganizationResult, + LocationSuggestion, +} from "@/shared/types"; + +/** + * Local/Secure provider stub. + * Phase 5 will add Chrome Built-in AI + Ollama support. + * For now, returns an error directing users to choose a different tier. + */ +export class LocalProvider implements AIProvider { + async organizeTabs(_tabs: TabInfo[]): Promise { + throw new Error( + "Local AI not yet available. Use rule-based grouping or switch to Relaxed/YOLO tier in Settings.", + ); + } + + async organizeBookmarks( + _bookmarks: BookmarkInfo[], + ): Promise { + throw new Error( + "Local AI not yet available. Switch to Relaxed/YOLO tier in Settings.", + ); + } + + async suggestBookmarkLocation( + _bookmark: BookmarkInfo, + _folders: { id: string; path: string }[], + ): Promise { + throw new Error( + "Local AI not yet available. Switch to Relaxed/YOLO tier in Settings.", + ); + } +} diff --git a/src/ai/providers/relaxed.ts b/src/ai/providers/relaxed.ts new file mode 100644 index 0000000..bc4ce68 --- /dev/null +++ b/src/ai/providers/relaxed.ts @@ -0,0 +1,139 @@ +import type { AIProvider, TabOrganizationAIResult } from "../types"; +import type { + TabInfo, + BookmarkInfo, + BookmarkOrganizationResult, + LocationSuggestion, + AIModelProvider, +} from "@/shared/types"; +import { + buildTabGroupingPrompt, + tabsToRelaxedInput, +} from "../prompts/tab-grouping"; +import { + buildBookmarkOrganizePrompt, + buildBookmarkLocationPrompt, + bookmarksToRelaxedInput, +} from "../prompts/bookmark-grouping"; +import { parseTabOrganization, parseBookmarkOrganization, parseBookmarkLocation } from "../parser"; + +/** + * Relaxed provider: same API calls as YOLO but sends minimal data. + * - Tab/bookmark titles only (no URLs) + * - No favicons or other metadata + */ +export class RelaxedProvider implements AIProvider { + constructor( + private modelProvider: AIModelProvider, + private claudeKey: string, + private openaiKey: string, + ) {} + + async organizeTabs(tabs: TabInfo[]): Promise { + const input = tabsToRelaxedInput(tabs); + const prompt = buildTabGroupingPrompt(input, { includeUrls: false }); + const response = await this.complete(prompt); + return parseTabOrganization(response); + } + + async organizeBookmarks( + bookmarks: BookmarkInfo[], + ): Promise { + const input = bookmarksToRelaxedInput(bookmarks); + const prompt = buildBookmarkOrganizePrompt(input, { includeUrls: false }); + const response = await this.complete(prompt); + const parsed = parseBookmarkOrganization(response); + return { + folders: parsed.folders.map((f) => ({ ...f, parentId: undefined })), + moves: [], + duplicates: parsed.duplicates, + newFolders: [], + reasoning: parsed.reasoning, + }; + } + + async suggestBookmarkLocation( + bookmark: BookmarkInfo, + folders: { id: string; path: string }[], + ): Promise { + const input = { id: bookmark.id, title: bookmark.title }; + const prompt = buildBookmarkLocationPrompt(input, folders, { + includeUrls: false, + }); + const response = await this.complete(prompt); + return parseBookmarkLocation(response).suggestions; + } + + private async complete(prompt: string): Promise { + if (this.modelProvider === "claude") { + return this.completeClaude(prompt); + } + return this.completeOpenAI(prompt); + } + + private async completeClaude(prompt: string): Promise { + if (!this.claudeKey) throw new Error("Anthropic API key not configured"); + + const response = await fetch("https://api.anthropic.com/v1/messages", { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": this.claudeKey, + "anthropic-version": "2023-06-01", + "anthropic-dangerous-direct-browser-access": "true", + }, + body: JSON.stringify({ + model: "claude-sonnet-4-20250514", + max_tokens: 4096, + messages: [{ role: "user", content: prompt }], + }), + }); + + if (!response.ok) { + const err = await response.text(); + throw new Error(`Claude API error (${response.status}): ${err}`); + } + + const data = await response.json(); + const textBlock = data.content?.find( + (b: { type: string }) => b.type === "text", + ); + if (!textBlock?.text) throw new Error("No text in Claude response"); + return textBlock.text; + } + + private async completeOpenAI(prompt: string): Promise { + if (!this.openaiKey) throw new Error("OpenAI API key not configured"); + + const response = await fetch("https://api.openai.com/v1/chat/completions", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.openaiKey}`, + }, + body: JSON.stringify({ + model: "gpt-4o-mini", + messages: [ + { + role: "system", + content: + "You are a browser organization assistant. Always respond with valid JSON only.", + }, + { role: "user", content: prompt }, + ], + temperature: 0.3, + response_format: { type: "json_object" }, + }), + }); + + if (!response.ok) { + const err = await response.text(); + throw new Error(`OpenAI API error (${response.status}): ${err}`); + } + + const data = await response.json(); + const content = data.choices?.[0]?.message?.content; + if (!content) throw new Error("No content in OpenAI response"); + return content; + } +} diff --git a/src/ai/providers/yolo.ts b/src/ai/providers/yolo.ts new file mode 100644 index 0000000..d86132b --- /dev/null +++ b/src/ai/providers/yolo.ts @@ -0,0 +1,134 @@ +import type { AIProvider, TabOrganizationAIResult, AIRequestOptions } from "../types"; +import type { + TabInfo, + BookmarkInfo, + BookmarkOrganizationResult, + LocationSuggestion, + AIModelProvider, +} from "@/shared/types"; +import { + buildTabGroupingPrompt, + tabsToYoloInput, +} from "../prompts/tab-grouping"; +import { + buildBookmarkOrganizePrompt, + buildBookmarkLocationPrompt, + bookmarksToYoloInput, +} from "../prompts/bookmark-grouping"; +import { parseTabOrganization, parseBookmarkOrganization, parseBookmarkLocation } from "../parser"; + +export class YoloProvider implements AIProvider { + constructor( + private modelProvider: AIModelProvider, + private claudeKey: string, + private openaiKey: string, + ) {} + + async organizeTabs(tabs: TabInfo[]): Promise { + const input = tabsToYoloInput(tabs); + const prompt = buildTabGroupingPrompt(input, { includeUrls: true }); + const response = await this.complete(prompt); + return parseTabOrganization(response); + } + + async organizeBookmarks( + bookmarks: BookmarkInfo[], + ): Promise { + const input = bookmarksToYoloInput(bookmarks); + const prompt = buildBookmarkOrganizePrompt(input, { includeUrls: true }); + const response = await this.complete(prompt); + const parsed = parseBookmarkOrganization(response); + return { + folders: parsed.folders.map((f) => ({ ...f, parentId: undefined })), + moves: [], + duplicates: parsed.duplicates, + newFolders: [], + reasoning: parsed.reasoning, + }; + } + + async suggestBookmarkLocation( + bookmark: BookmarkInfo, + folders: { id: string; path: string }[], + ): Promise { + const input = { id: bookmark.id, title: bookmark.title, url: bookmark.url }; + const prompt = buildBookmarkLocationPrompt(input, folders, { + includeUrls: true, + }); + const response = await this.complete(prompt); + return parseBookmarkLocation(response).suggestions; + } + + private async complete(prompt: string): Promise { + if (this.modelProvider === "claude") { + return this.completeClaude(prompt); + } + return this.completeOpenAI(prompt); + } + + private async completeClaude(prompt: string): Promise { + if (!this.claudeKey) throw new Error("Anthropic API key not configured"); + + const response = await fetch("https://api.anthropic.com/v1/messages", { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": this.claudeKey, + "anthropic-version": "2023-06-01", + "anthropic-dangerous-direct-browser-access": "true", + }, + body: JSON.stringify({ + model: "claude-sonnet-4-20250514", + max_tokens: 4096, + messages: [{ role: "user", content: prompt }], + }), + }); + + if (!response.ok) { + const err = await response.text(); + throw new Error(`Claude API error (${response.status}): ${err}`); + } + + const data = await response.json(); + const textBlock = data.content?.find( + (b: { type: string }) => b.type === "text", + ); + if (!textBlock?.text) throw new Error("No text in Claude response"); + return textBlock.text; + } + + private async completeOpenAI(prompt: string): Promise { + if (!this.openaiKey) throw new Error("OpenAI API key not configured"); + + const response = await fetch("https://api.openai.com/v1/chat/completions", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.openaiKey}`, + }, + body: JSON.stringify({ + model: "gpt-4o-mini", + messages: [ + { + role: "system", + content: + "You are a browser organization assistant. Always respond with valid JSON only.", + }, + { role: "user", content: prompt }, + ], + temperature: 0.3, + response_format: { type: "json_object" }, + }), + }); + + if (!response.ok) { + const err = await response.text(); + throw new Error(`OpenAI API error (${response.status}): ${err}`); + } + + const data = await response.json(); + const content = data.choices?.[0]?.message?.content; + if (!content) throw new Error("No content in OpenAI response"); + return content; + } +} diff --git a/src/ai/types.ts b/src/ai/types.ts new file mode 100644 index 0000000..bec69b7 --- /dev/null +++ b/src/ai/types.ts @@ -0,0 +1,30 @@ +import type { + TabInfo, + TabGroupSuggestion, + BookmarkInfo, + BookmarkOrganizationResult, + LocationSuggestion, +} from "@/shared/types"; + +export interface TabOrganizationAIResult { + groups: TabGroupSuggestion[]; + stale: number[]; + duplicates: number[][]; + reasoning: string; +} + +export interface AIProvider { + organizeTabs(tabs: TabInfo[]): Promise; + organizeBookmarks( + bookmarks: BookmarkInfo[], + ): Promise; + suggestBookmarkLocation( + bookmark: BookmarkInfo, + folders: { id: string; path: string }[], + ): Promise; +} + +export interface AIRequestOptions { + apiKey: string; + model?: string; +} diff --git a/src/background/service-worker.ts b/src/background/service-worker.ts index b62285a..48793d6 100644 --- a/src/background/service-worker.ts +++ b/src/background/service-worker.ts @@ -1,6 +1,7 @@ import type { Message, MessageResponse } from "@/shared/messaging"; import type { Settings, + TabInfo, TabOrganizationResult, TabSnapshot, } from "@/shared/types"; @@ -22,6 +23,7 @@ import { domainToTabGroups, } from "@/core/tabs"; import { STORAGE_KEYS } from "@/shared/constants"; +import { getAIProvider } from "@/ai/provider"; // Open side panel when extension icon is clicked chrome.sidePanel @@ -81,19 +83,30 @@ async function handleOrganizeTabs(): Promise< })); await setSessionData(STORAGE_KEYS.TAB_SNAPSHOT, snapshot); - // Rule-based grouping (AI integration in Phase 3) - const domainMap = groupByDomain(tabs); - const groups = domainToTabGroups(domainMap); - const duplicates = findDuplicatesByUrl(tabs); - const stale = findStaleTabs(tabs, settings.staleDaysThreshold); - - const result: TabOrganizationResult = { - tabs, - groups, - stale, - duplicates, - reasoning: "Grouped by domain (rule-based)", - }; + // Try AI-powered grouping, fall back to rule-based + let result: TabOrganizationResult; + + if (settings.aiTier === "secure") { + // Secure tier: use rule-based for now (Phase 5 adds local AI) + result = ruleBasedOrganize(tabs, settings.staleDaysThreshold); + } else { + try { + const provider = await getAIProvider(); + const aiResult = await provider.organizeTabs(tabs); + result = { + tabs, + groups: aiResult.groups, + stale: aiResult.stale, + duplicates: aiResult.duplicates, + reasoning: aiResult.reasoning, + }; + } catch (err) { + // Fall back to rule-based on AI failure + console.warn("AI tab organization failed, falling back to rule-based:", err); + result = ruleBasedOrganize(tabs, settings.staleDaysThreshold); + result.reasoning = `AI unavailable (${err instanceof Error ? err.message : String(err)}). ${result.reasoning}`; + } + } return { success: true, data: result }; } @@ -195,3 +208,21 @@ async function handleUndoTabChanges(): Promise { await clearSessionData(STORAGE_KEYS.TAB_SNAPSHOT); return { success: true }; } + +function ruleBasedOrganize( + tabs: TabInfo[], + staleDays: number, +): TabOrganizationResult { + const domainMap = groupByDomain(tabs); + const groups = domainToTabGroups(domainMap); + const duplicates = findDuplicatesByUrl(tabs); + const stale = findStaleTabs(tabs, staleDays); + + return { + tabs, + groups, + stale, + duplicates, + reasoning: "Grouped by domain (rule-based)", + }; +} From 1a8162de85eeb88fdf5240f28a6d9bc4e0047971 Mon Sep 17 00:00:00 2001 From: Joshua Butner <53810+heyjawrsh@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:56:21 -0500 Subject: [PATCH 02/54] a little phase 4 pregame; adding in openrouter options --- src/ai/provider.ts | 13 ++ src/ai/providers/openrouter.ts | 120 ++++++++++++++++++ src/shared/types.ts | 8 +- src/sidepanel/pages/Settings.tsx | 205 +++++++++++++++++++++++-------- 4 files changed, 295 insertions(+), 51 deletions(-) create mode 100644 src/ai/providers/openrouter.ts diff --git a/src/ai/provider.ts b/src/ai/provider.ts index 6e115bf..ef448a8 100644 --- a/src/ai/provider.ts +++ b/src/ai/provider.ts @@ -4,6 +4,7 @@ import { getSettings } from "@/core/storage"; import { LocalProvider } from "./providers/local"; import { RelaxedProvider } from "./providers/relaxed"; import { YoloProvider } from "./providers/yolo"; +import { OpenRouterProvider } from "./providers/openrouter"; export async function getAIProvider(): Promise { const settings = await getSettings(); @@ -11,6 +12,18 @@ export async function getAIProvider(): Promise { } function createProvider(settings: Settings): AIProvider { + const includeUrls = settings.aiTier === "yolo"; + + // OpenRouter handles both tiers via the includeUrls flag + if (settings.aiModelProvider === "openrouter") { + if (settings.aiTier === "secure") return new LocalProvider(); + return new OpenRouterProvider( + settings.openrouterApiKey, + settings.openrouterModel, + includeUrls, + ); + } + switch (settings.aiTier) { case "secure": return new LocalProvider(); diff --git a/src/ai/providers/openrouter.ts b/src/ai/providers/openrouter.ts new file mode 100644 index 0000000..fa40b84 --- /dev/null +++ b/src/ai/providers/openrouter.ts @@ -0,0 +1,120 @@ +import type { AIProvider, TabOrganizationAIResult } from "../types"; +import type { + TabInfo, + BookmarkInfo, + BookmarkOrganizationResult, + LocationSuggestion, +} from "@/shared/types"; +import { + buildTabGroupingPrompt, + tabsToYoloInput, + tabsToRelaxedInput, +} from "../prompts/tab-grouping"; +import { + buildBookmarkOrganizePrompt, + buildBookmarkLocationPrompt, + bookmarksToYoloInput, + bookmarksToRelaxedInput, +} from "../prompts/bookmark-grouping"; +import { + parseTabOrganization, + parseBookmarkOrganization, + parseBookmarkLocation, +} from "../parser"; + +/** + * OpenRouter provider — unified gateway to Claude, GPT, Llama, Mistral, etc. + * Uses OpenAI-compatible chat completions API. + * Users get one key at openrouter.ai that works with many models. + */ +export class OpenRouterProvider implements AIProvider { + constructor( + private apiKey: string, + private model: string, + private includeUrls: boolean, + ) {} + + async organizeTabs(tabs: TabInfo[]): Promise { + const input = this.includeUrls + ? tabsToYoloInput(tabs) + : tabsToRelaxedInput(tabs); + const prompt = buildTabGroupingPrompt(input, { + includeUrls: this.includeUrls, + }); + const response = await this.complete(prompt); + return parseTabOrganization(response); + } + + async organizeBookmarks( + bookmarks: BookmarkInfo[], + ): Promise { + const input = this.includeUrls + ? bookmarksToYoloInput(bookmarks) + : bookmarksToRelaxedInput(bookmarks); + const prompt = buildBookmarkOrganizePrompt(input, { + includeUrls: this.includeUrls, + }); + const response = await this.complete(prompt); + const parsed = parseBookmarkOrganization(response); + return { + folders: parsed.folders.map((f) => ({ ...f, parentId: undefined })), + moves: [], + duplicates: parsed.duplicates, + newFolders: [], + reasoning: parsed.reasoning, + }; + } + + async suggestBookmarkLocation( + bookmark: BookmarkInfo, + folders: { id: string; path: string }[], + ): Promise { + const input = this.includeUrls + ? { id: bookmark.id, title: bookmark.title, url: bookmark.url } + : { id: bookmark.id, title: bookmark.title }; + const prompt = buildBookmarkLocationPrompt(input, folders, { + includeUrls: this.includeUrls, + }); + const response = await this.complete(prompt); + return parseBookmarkLocation(response).suggestions; + } + + private async complete(prompt: string): Promise { + if (!this.apiKey) throw new Error("OpenRouter API key not configured"); + + const response = await fetch( + "https://openrouter.ai/api/v1/chat/completions", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.apiKey}`, + "HTTP-Referer": "https://github.com/vxrtx", + "X-Title": "vxrtx", + }, + body: JSON.stringify({ + model: this.model, + messages: [ + { + role: "system", + content: + "You are a browser organization assistant. Always respond with valid JSON only.", + }, + { role: "user", content: prompt }, + ], + temperature: 0.3, + }), + }, + ); + + if (!response.ok) { + const err = await response.text(); + throw new Error(`OpenRouter API error (${response.status}): ${err}`); + } + + const data = await response.json(); + const content = data.choices?.[0]?.message?.content; + if (!content) throw new Error("No content in OpenRouter response"); + return content; + } +} diff --git a/src/shared/types.ts b/src/shared/types.ts index 37c9edb..536d8b8 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1,20 +1,24 @@ export type AITier = "secure" | "relaxed" | "yolo"; -export type AIModelProvider = "claude" | "openai"; +export type AIModelProvider = "claude" | "openai" | "openrouter"; export interface Settings { aiTier: AITier; aiModelProvider: AIModelProvider; claudeApiKey: string; openaiApiKey: string; + openrouterApiKey: string; + openrouterModel: string; staleDaysThreshold: number; } export const DEFAULT_SETTINGS: Settings = { aiTier: "secure", - aiModelProvider: "claude", + aiModelProvider: "openrouter", claudeApiKey: "", openaiApiKey: "", + openrouterApiKey: "", + openrouterModel: "anthropic/claude-sonnet-4", staleDaysThreshold: 7, }; diff --git a/src/sidepanel/pages/Settings.tsx b/src/sidepanel/pages/Settings.tsx index d5bc6e8..8c51017 100644 --- a/src/sidepanel/pages/Settings.tsx +++ b/src/sidepanel/pages/Settings.tsx @@ -1,6 +1,10 @@ import { useEffect, useState } from "react"; import { sendMessage } from "@/shared/messaging"; -import type { AITier, AIModelProvider, Settings as SettingsType } from "@/shared/types"; +import type { + AITier, + AIModelProvider, + Settings as SettingsType, +} from "@/shared/types"; import { DEFAULT_SETTINGS } from "@/shared/types"; const TIER_INFO: { id: AITier; label: string; description: string }[] = [ @@ -12,27 +16,65 @@ const TIER_INFO: { id: AITier; label: string; description: string }[] = [ { id: "relaxed", label: "Relaxed", - description: "Cloud AI with minimal data exposure. Titles only, no URLs.", + description: + "Cloud AI with minimal data exposure. Titles only, no URLs.", }, { id: "yolo", label: "YOLO", - description: "Full context sent to AI for best results. Titles, URLs, timestamps.", + description: + "Full context sent to AI for best results. Titles, URLs, timestamps.", }, ]; -const MODEL_PROVIDERS: { id: AIModelProvider; label: string }[] = [ - { id: "claude", label: "Claude (Anthropic)" }, - { id: "openai", label: "OpenAI" }, +const MODEL_PROVIDERS: { + id: AIModelProvider; + label: string; + description: string; +}[] = [ + { + id: "openrouter", + label: "OpenRouter", + description: "Access Claude, GPT, Llama & more with one key", + }, + { + id: "claude", + label: "Claude", + description: "Direct Anthropic API", + }, + { + id: "openai", + label: "OpenAI", + description: "Direct OpenAI API", + }, +]; + +const POPULAR_MODELS = [ + { id: "anthropic/claude-sonnet-4", label: "Claude Sonnet 4" }, + { id: "anthropic/claude-haiku-4", label: "Claude Haiku 4 (fast/cheap)" }, + { id: "openai/gpt-4o-mini", label: "GPT-4o Mini (fast/cheap)" }, + { id: "openai/gpt-4o", label: "GPT-4o" }, + { id: "google/gemini-2.5-flash", label: "Gemini 2.5 Flash" }, + { id: "meta-llama/llama-4-scout", label: "Llama 4 Scout (free)" }, ]; export function Settings() { const [settings, setSettings] = useState(DEFAULT_SETTINGS); const [saved, setSaved] = useState(false); + const [customModel, setCustomModel] = useState(false); useEffect(() => { sendMessage("get-settings").then((res) => { - if (res.success && res.data) setSettings(res.data); + if (res.success && res.data) { + setSettings(res.data); + // Check if current model is not in the popular list + if ( + res.data.openrouterModel && + !POPULAR_MODELS.some((m) => m.id === res.data!.openrouterModel) + ) { + setCustomModel(true); + } + } }); }, []); @@ -44,13 +86,17 @@ export function Settings() { setTimeout(() => setSaved(false), 1500); } + const showCloudSettings = settings.aiTier !== "secure"; + return (

Settings

{/* AI Tier */}
-

AI Privacy Tier

+

+ AI Privacy Tier +

{TIER_INFO.map((tier) => (
- {/* Model Provider (only for relaxed/yolo) */} - {settings.aiTier !== "secure" && ( + {/* Model Provider */} + {showCloudSettings && (
-

AI Model

-
+

AI Provider

+
{MODEL_PROVIDERS.map((provider) => ( ))}
)} - {/* API Keys */} - {settings.aiTier !== "secure" && ( + {/* OpenRouter Settings */} + {showCloudSettings && settings.aiModelProvider === "openrouter" && (
-

API Keys

- - {(settings.aiModelProvider === "claude" || settings.aiTier === "yolo") && ( -
- - save({ claudeApiKey: e.target.value })} - placeholder="sk-ant-..." - className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-indigo-500 focus:outline-none" - /> -
- )} - - {(settings.aiModelProvider === "openai" || settings.aiTier === "yolo") && ( -
- - save({ openaiApiKey: e.target.value })} - placeholder="sk-..." - className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-indigo-500 focus:outline-none" - /> -
- )} +

+ OpenRouter Configuration +

+ +
+ + save({ openrouterApiKey: e.target.value })} + placeholder="sk-or-..." + className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-indigo-500 focus:outline-none" + /> +
+ +
+ + {!customModel ? ( +
+ {POPULAR_MODELS.map((model) => ( + + ))} + +
+ ) : ( +
+ save({ openrouterModel: e.target.value })} + placeholder="provider/model-name" + className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-indigo-500 focus:outline-none" + /> + +
+ )} +
+
+ )} + + {/* Direct API Keys */} + {showCloudSettings && settings.aiModelProvider === "claude" && ( +
+

Anthropic API Key

+ save({ claudeApiKey: e.target.value })} + placeholder="sk-ant-..." + className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-indigo-500 focus:outline-none" + /> +
+ )} + + {showCloudSettings && settings.aiModelProvider === "openai" && ( +
+

OpenAI API Key

+ save({ openaiApiKey: e.target.value })} + placeholder="sk-..." + className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-indigo-500 focus:outline-none" + />
)} From 013477cdbd9dd17acaf453c35cc82aa33379c5e6 Mon Sep 17 00:00:00 2001 From: Joshua Butner <53810+heyjawrsh@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:14:17 -0500 Subject: [PATCH 03/54] Saving initial pass at bookmarks org --- src/background/service-worker.ts | 252 +++++++- src/core/bookmarks.ts | 93 ++- src/shared/types.ts | 18 + src/sidepanel/pages/BookmarkOrganizer.tsx | 744 +++++++++++++++++++++- 4 files changed, 1089 insertions(+), 18 deletions(-) diff --git a/src/background/service-worker.ts b/src/background/service-worker.ts index 48793d6..89e26a2 100644 --- a/src/background/service-worker.ts +++ b/src/background/service-worker.ts @@ -4,6 +4,12 @@ import type { TabInfo, TabOrganizationResult, TabSnapshot, + BookmarkInfo, + BookmarkSnapshot, + BookmarkOrganizationResult, + BookmarkDuplicateGroup, + LocationSuggestion, + FolderInfo, } from "@/shared/types"; import { getSettings, @@ -22,6 +28,17 @@ import { groupByDomain, domainToTabGroups, } from "@/core/tabs"; +import { + getBookmarkTree, + flattenBookmarks, + extractFolders, + buildFolderPathMap, + findDuplicateBookmarksDetailed, + snapshotBookmarks, + moveBookmark, + createFolder, + removeBookmark, +} from "@/core/bookmarks"; import { STORAGE_KEYS } from "@/shared/constants"; import { getAIProvider } from "@/ai/provider"; @@ -64,18 +81,38 @@ async function handleMessage(message: Message): Promise { case "undo-tab-changes": return await handleUndoTabChanges(); + case "organize-bookmarks": + return await handleOrganizeBookmarks(); + + case "find-duplicate-bookmarks": + return await handleFindDuplicateBookmarks(); + + case "suggest-bookmark-location": + return await handleSuggestBookmarkLocation( + message.payload as { bookmark: BookmarkInfo }, + ); + + case "apply-bookmark-suggestions": + return await handleApplyBookmarkSuggestions( + message.payload as BookmarkApplyPayload, + ); + + case "undo-bookmark-changes": + return await handleUndoBookmarkChanges(); + default: return { success: false, error: `Unknown action: ${message.action}` }; } } +// ─── Tab Organization ─────────────────────────────────────────────── + async function handleOrganizeTabs(): Promise< MessageResponse > { const settings = await getSettings(); const tabs = await queryAllTabs(); - // Snapshot current state for undo const snapshot: TabSnapshot[] = tabs.map((t) => ({ id: t.id, groupId: t.groupId, @@ -83,11 +120,9 @@ async function handleOrganizeTabs(): Promise< })); await setSessionData(STORAGE_KEYS.TAB_SNAPSHOT, snapshot); - // Try AI-powered grouping, fall back to rule-based let result: TabOrganizationResult; if (settings.aiTier === "secure") { - // Secure tier: use rule-based for now (Phase 5 adds local AI) result = ruleBasedOrganize(tabs, settings.staleDaysThreshold); } else { try { @@ -101,8 +136,7 @@ async function handleOrganizeTabs(): Promise< reasoning: aiResult.reasoning, }; } catch (err) { - // Fall back to rule-based on AI failure - console.warn("AI tab organization failed, falling back to rule-based:", err); + console.warn("AI tab organization failed:", err); result = ruleBasedOrganize(tabs, settings.staleDaysThreshold); result.reasoning = `AI unavailable (${err instanceof Error ? err.message : String(err)}). ${result.reasoning}`; } @@ -120,7 +154,6 @@ async function handleApplyTabSuggestions( return { success: false, error: "No window found" }; } - // Re-snapshot right before applying so undo is accurate const snapshot: TabSnapshot[] = tabs.map((t) => ({ id: t.id, groupId: t.groupId, @@ -128,7 +161,6 @@ async function handleApplyTabSuggestions( })); await setSessionData(STORAGE_KEYS.TAB_SNAPSHOT, snapshot); - // Apply groups for (const group of result.groups) { const validTabIds = group.tabIds.filter((id) => tabs.some((t) => t.id === id), @@ -137,7 +169,6 @@ async function handleApplyTabSuggestions( await createTabGroup({ ...group, tabIds: validTabIds }, windowId); } - // Close stale tabs if (result.stale.length > 0) { const validStale = result.stale.filter((id) => tabs.some((t) => t.id === id), @@ -145,7 +176,6 @@ async function handleApplyTabSuggestions( if (validStale.length > 0) await closeTabs(validStale); } - // Close duplicate tabs (keep first in each set) for (const dupSet of result.duplicates) { if (dupSet.length > 1) { const validDups = dupSet @@ -168,7 +198,6 @@ async function handleUndoTabChanges(): Promise { const currentTabs = await queryAllTabs(); - // Ungroup all currently grouped tabs first const groupedTabIds = currentTabs .filter((t) => t.groupId !== -1) .map((t) => t.id); @@ -176,7 +205,6 @@ async function handleUndoTabChanges(): Promise { await ungroupTabs(groupedTabIds); } - // Re-group tabs that were grouped in the snapshot const groupMap = new Map(); for (const entry of snapshot) { if (entry.groupId !== -1) { @@ -189,7 +217,6 @@ async function handleUndoTabChanges(): Promise { } } - // Recreate groups (we can't restore exact group IDs, but we can regroup) const windowId = currentTabs[0]?.windowId; if (windowId !== undefined) { for (const tabIds of groupMap.values()) { @@ -226,3 +253,204 @@ function ruleBasedOrganize( reasoning: "Grouped by domain (rule-based)", }; } + +// ─── Bookmark Organization ────────────────────────────────────────── + +interface BookmarkOrganizeResponse { + bookmarks: BookmarkInfo[]; + folders: FolderInfo[]; + result: BookmarkOrganizationResult; +} + +interface BookmarkDuplicateResponse { + duplicates: BookmarkDuplicateGroup[]; + folderPaths: Record; +} + +interface BookmarkLocationResponse { + suggestions: LocationSuggestion[]; +} + +interface BookmarkApplyPayload { + moves: { bookmarkId: string; targetFolderId: string }[]; + newFolders: { name: string; parentId: string }[]; + removals: string[]; +} + +async function handleOrganizeBookmarks(): Promise< + MessageResponse +> { + const settings = await getSettings(); + const tree = await getBookmarkTree(); + const bookmarks = flattenBookmarks(tree); + const folders = extractFolders(tree); + + // Snapshot for undo + await setSessionData( + STORAGE_KEYS.BOOKMARK_SNAPSHOT, + snapshotBookmarks(bookmarks), + ); + + if (settings.aiTier === "secure") { + // Rule-based: no restructuring, just return current state + return { + success: true, + data: { + bookmarks, + folders, + result: { + folders: [], + moves: [], + duplicates: [], + newFolders: [], + reasoning: + "Local AI not yet available. Showing current bookmarks. Switch to Relaxed/YOLO tier for AI-powered organization.", + }, + }, + }; + } + + try { + const provider = await getAIProvider(); + const aiResult = await provider.organizeBookmarks(bookmarks); + return { + success: true, + data: { bookmarks, folders, result: aiResult }, + }; + } catch (err) { + console.warn("AI bookmark organization failed:", err); + return { + success: true, + data: { + bookmarks, + folders, + result: { + folders: [], + moves: [], + duplicates: [], + newFolders: [], + reasoning: `AI unavailable (${err instanceof Error ? err.message : String(err)}). Showing current bookmarks.`, + }, + }, + }; + } +} + +async function handleFindDuplicateBookmarks(): Promise< + MessageResponse +> { + const tree = await getBookmarkTree(); + const bookmarks = flattenBookmarks(tree); + const folderPathMap = buildFolderPathMap(tree); + const duplicates = findDuplicateBookmarksDetailed(bookmarks); + + const folderPaths: Record = {}; + for (const [id, path] of folderPathMap) { + folderPaths[id] = path; + } + + // Snapshot for undo + await setSessionData( + STORAGE_KEYS.BOOKMARK_SNAPSHOT, + snapshotBookmarks(bookmarks), + ); + + return { success: true, data: { duplicates, folderPaths } }; +} + +async function handleSuggestBookmarkLocation( + payload: { bookmark: BookmarkInfo }, +): Promise> { + const settings = await getSettings(); + + if (settings.aiTier === "secure") { + return { + success: false, + error: + "Local AI not yet available for bookmark suggestions. Switch to Relaxed/YOLO tier.", + }; + } + + const tree = await getBookmarkTree(); + const folders = extractFolders(tree); + const folderInput = folders.map((f) => ({ id: f.id, path: f.path })); + + try { + const provider = await getAIProvider(); + const suggestions = await provider.suggestBookmarkLocation( + payload.bookmark, + folderInput, + ); + return { success: true, data: { suggestions } }; + } catch (err) { + return { + success: false, + error: `AI suggestion failed: ${err instanceof Error ? err.message : String(err)}`, + }; + } +} + +async function handleApplyBookmarkSuggestions( + payload: BookmarkApplyPayload, +): Promise { + // Re-snapshot before applying + const tree = await getBookmarkTree(); + const bookmarks = flattenBookmarks(tree); + await setSessionData( + STORAGE_KEYS.BOOKMARK_SNAPSHOT, + snapshotBookmarks(bookmarks), + ); + + // Create new folders first and build a map of placeholder → real ID + const folderIdMap = new Map(); + for (const folder of payload.newFolders) { + const created = await createFolder(folder.name, folder.parentId); + folderIdMap.set(`${folder.name}:${folder.parentId}`, created.id); + } + + // Apply moves + for (const move of payload.moves) { + const resolvedFolderId = + folderIdMap.get(move.targetFolderId) ?? move.targetFolderId; + try { + await moveBookmark(move.bookmarkId, { parentId: resolvedFolderId }); + } catch (err) { + console.warn(`Failed to move bookmark ${move.bookmarkId}:`, err); + } + } + + // Remove duplicates + for (const id of payload.removals) { + try { + await removeBookmark(id); + } catch (err) { + console.warn(`Failed to remove bookmark ${id}:`, err); + } + } + + return { success: true }; +} + +async function handleUndoBookmarkChanges(): Promise { + const snapshot = await getSessionData( + STORAGE_KEYS.BOOKMARK_SNAPSHOT, + ); + if (!snapshot || snapshot.length === 0) { + return { success: false, error: "No undo snapshot available" }; + } + + // Restore each bookmark to its original position + for (const entry of snapshot) { + try { + await moveBookmark(entry.id, { + parentId: entry.parentId, + index: entry.index, + }); + } catch { + // Bookmark may have been deleted, skip + } + } + + await clearSessionData(STORAGE_KEYS.BOOKMARK_SNAPSHOT); + return { success: true }; +} diff --git a/src/core/bookmarks.ts b/src/core/bookmarks.ts index 0a07e03..f2625c5 100644 --- a/src/core/bookmarks.ts +++ b/src/core/bookmarks.ts @@ -1,6 +1,13 @@ -import type { BookmarkInfo } from "@/shared/types"; +import type { + BookmarkInfo, + BookmarkSnapshot, + FolderInfo, + BookmarkDuplicateGroup, +} from "@/shared/types"; -export async function getBookmarkTree(): Promise { +export async function getBookmarkTree(): Promise< + chrome.bookmarks.BookmarkTreeNode[] +> { return chrome.bookmarks.getTree(); } @@ -45,6 +52,58 @@ export function flattenBookmarks( return result; } +export function extractFolders( + nodes: chrome.bookmarks.BookmarkTreeNode[], + parentPath: string = "", +): FolderInfo[] { + const folders: FolderInfo[] = []; + function walk( + node: chrome.bookmarks.BookmarkTreeNode, + currentPath: string, + ) { + if (!node.url && node.children) { + const path = currentPath + ? `${currentPath}/${node.title}` + : node.title || "Root"; + if (node.id !== "0") { + folders.push({ + id: node.id, + title: node.title, + path, + parentId: node.parentId, + }); + } + for (const child of node.children) { + walk(child, node.id === "0" ? "" : path); + } + } + } + for (const node of nodes) walk(node, parentPath); + return folders; +} + +export function buildFolderPathMap( + nodes: chrome.bookmarks.BookmarkTreeNode[], +): Map { + const map = new Map(); + function walk( + node: chrome.bookmarks.BookmarkTreeNode, + path: string, + ) { + const currentPath = path ? `${path}/${node.title}` : node.title || ""; + if (!node.url) { + map.set(node.id, currentPath || "Root"); + } + if (node.children) { + for (const child of node.children) { + walk(child, node.id === "0" ? "" : currentPath); + } + } + } + for (const node of nodes) walk(node, ""); + return map; +} + export function findDuplicateBookmarks( bookmarks: BookmarkInfo[], ): string[][] { @@ -60,3 +119,33 @@ export function findDuplicateBookmarks( } return Array.from(urlMap.values()).filter((ids) => ids.length > 1); } + +export function findDuplicateBookmarksDetailed( + bookmarks: BookmarkInfo[], +): BookmarkDuplicateGroup[] { + const urlMap = new Map(); + for (const bm of bookmarks) { + if (!bm.url) continue; + const existing = urlMap.get(bm.url); + if (existing) { + existing.push(bm); + } else { + urlMap.set(bm.url, [bm]); + } + } + return Array.from(urlMap.entries()) + .filter(([, bms]) => bms.length > 1) + .map(([url, bms]) => ({ url, bookmarks: bms })); +} + +export function snapshotBookmarks( + bookmarks: BookmarkInfo[], +): BookmarkSnapshot[] { + return bookmarks + .filter((b) => b.parentId !== undefined && b.index !== undefined) + .map((b) => ({ + id: b.id, + parentId: b.parentId!, + index: b.index!, + })); +} diff --git a/src/shared/types.ts b/src/shared/types.ts index 536d8b8..6dcfc3b 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -92,3 +92,21 @@ export interface LocationSuggestion { confidence: number; reason: string; } + +export interface BookmarkSnapshot { + id: string; + parentId: string; + index: number; +} + +export interface FolderInfo { + id: string; + title: string; + path: string; + parentId?: string; +} + +export interface BookmarkDuplicateGroup { + url: string; + bookmarks: BookmarkInfo[]; +} diff --git a/src/sidepanel/pages/BookmarkOrganizer.tsx b/src/sidepanel/pages/BookmarkOrganizer.tsx index 0d37e3a..5875c44 100644 --- a/src/sidepanel/pages/BookmarkOrganizer.tsx +++ b/src/sidepanel/pages/BookmarkOrganizer.tsx @@ -1,10 +1,746 @@ +import { useState } from "react"; +import { sendMessage } from "@/shared/messaging"; +import type { + BookmarkInfo, + BookmarkOrganizationResult, + BookmarkDuplicateGroup, + FolderInfo, + LocationSuggestion, +} from "@/shared/types"; + +type Mode = "menu" | "organize" | "locate" | "duplicates"; +type Status = "idle" | "loading" | "preview" | "applying" | "done"; + export function BookmarkOrganizer() { + const [mode, setMode] = useState("menu"); + return (
-

Bookmark Organizer

-

- Bookmark organization coming in Phase 4. -

+
+

Bookmark Organizer

+ {mode !== "menu" && ( + + )} +
+ + {mode === "menu" && } + {mode === "organize" && setMode("menu")} />} + {mode === "locate" && setMode("menu")} />} + {mode === "duplicates" && ( + setMode("menu")} /> + )}
); } + +function ModeMenu({ onSelect }: { onSelect: (mode: Mode) => void }) { + const modes = [ + { + id: "organize" as Mode, + title: "Reorganize All", + description: "AI suggests a new folder structure for your bookmarks", + }, + { + id: "locate" as Mode, + title: "Where Should This Go?", + description: "Pick a bookmark and AI suggests the best folder for it", + }, + { + id: "duplicates" as Mode, + title: "Find Duplicates", + description: "Scan for duplicate bookmarks and clean them up", + }, + ]; + + return ( +
+ {modes.map((m) => ( + + ))} +
+ ); +} + +// ─── Full Reorganize Mode ─────────────────────────────────────────── + +interface OrganizeData { + bookmarks: BookmarkInfo[]; + folders: FolderInfo[]; + result: BookmarkOrganizationResult; +} + +function OrganizeMode({ onBack }: { onBack: () => void }) { + const [status, setStatus] = useState("idle"); + const [data, setData] = useState(null); + const [enabledFolders, setEnabledFolders] = useState>(new Set()); + const [error, setError] = useState(null); + const [undoAvailable, setUndoAvailable] = useState(false); + + async function handleAnalyze() { + setStatus("loading"); + setError(null); + try { + const response = await sendMessage( + "organize-bookmarks", + ); + if (response.success && response.data) { + setData(response.data); + setEnabledFolders( + new Set(response.data.result.folders.map((_, i) => i)), + ); + setStatus("preview"); + } else { + setError(response.error ?? "Failed to analyze bookmarks"); + setStatus("idle"); + } + } catch (err) { + setError(String(err)); + setStatus("idle"); + } + } + + async function handleApply() { + if (!data) return; + setStatus("applying"); + try { + const enabledFolderData = data.result.folders.filter((_, i) => + enabledFolders.has(i), + ); + + // Find the Bookmarks Bar as default parent + const parentId = data.folders[0]?.id ?? "1"; + + const newFolders = enabledFolderData.map((f) => ({ + name: f.name, + parentId: f.parentId ?? parentId, + })); + + const moves = enabledFolderData.flatMap((f) => + f.bookmarkIds.map((bmId) => ({ + bookmarkId: bmId, + targetFolderId: `${f.name}:${f.parentId ?? parentId}`, + })), + ); + + const response = await sendMessage("apply-bookmark-suggestions", { + moves, + newFolders, + removals: [], + }); + + if (response.success) { + setStatus("done"); + setUndoAvailable(true); + } else { + setError(response.error ?? "Failed to apply changes"); + setStatus("preview"); + } + } catch (err) { + setError(String(err)); + setStatus("preview"); + } + } + + async function handleUndo() { + const response = await sendMessage("undo-bookmark-changes"); + if (response.success) { + setUndoAvailable(false); + setData(null); + setStatus("idle"); + } else { + setError(response.error ?? "Undo failed"); + } + } + + const bookmarkMap = new Map( + (data?.bookmarks ?? []).map((b) => [b.id, b]), + ); + + return ( +
+ {status === "idle" && ( + <> +

+ AI will analyze your bookmarks and suggest a new folder structure. +

+ + + )} + + {error && ( +
+ {error} +
+ )} + + {status === "loading" && ( +
+ + Analyzing bookmarks... +
+ )} + + {status === "preview" && data && ( +
+ {data.result.reasoning && ( +

{data.result.reasoning}

+ )} + + {data.result.folders.length > 0 ? ( + <> +

+ Suggested Folders +

+ {data.result.folders.map((folder, i) => ( +
+ +
+ {folder.bookmarkIds.slice(0, 5).map((bmId) => { + const bm = bookmarkMap.get(bmId); + return bm ? ( +
+ {bm.title} +
+ ) : null; + })} + {folder.bookmarkIds.length > 5 && ( +
+ +{folder.bookmarkIds.length - 5} more +
+ )} +
+
+ ))} + +
+ + +
+ + ) : ( +

+ No reorganization suggestions. Your bookmarks look well organized! +

+ )} +
+ )} + + {status === "applying" && ( +
+ + Applying changes... +
+ )} + + {status === "done" && ( +
+
+ Bookmarks reorganized! +
+
+ {undoAvailable && ( + + )} + +
+
+ )} +
+ ); +} + +// ─── "Where Should This Go?" Mode ────────────────────────────────── + +function LocateMode({ onBack }: { onBack: () => void }) { + const [status, setStatus] = useState<"pick" | "loading" | "results">("pick"); + const [bookmarks, setBookmarks] = useState([]); + const [selected, setSelected] = useState(null); + const [suggestions, setSuggestions] = useState([]); + const [search, setSearch] = useState(""); + const [error, setError] = useState(null); + + async function loadBookmarks() { + const response = await sendMessage( + "organize-bookmarks", + ); + if (response.success && response.data) { + setBookmarks(response.data.bookmarks); + } + } + + // Load bookmarks on mount + if (bookmarks.length === 0 && status === "pick") { + loadBookmarks(); + } + + async function handleSelect(bookmark: BookmarkInfo) { + setSelected(bookmark); + setStatus("loading"); + setError(null); + try { + const response = await sendMessage< + { bookmark: BookmarkInfo }, + { suggestions: LocationSuggestion[] } + >("suggest-bookmark-location", { bookmark }); + if (response.success && response.data) { + setSuggestions(response.data.suggestions); + setStatus("results"); + } else { + setError(response.error ?? "Failed to get suggestions"); + setStatus("pick"); + } + } catch (err) { + setError(String(err)); + setStatus("pick"); + } + } + + async function handleMove(folderId: string) { + if (!selected) return; + try { + await sendMessage("apply-bookmark-suggestions", { + moves: [{ bookmarkId: selected.id, targetFolderId: folderId }], + newFolders: [], + removals: [], + }); + setStatus("pick"); + setSelected(null); + setSuggestions([]); + // Refresh bookmarks list + loadBookmarks(); + } catch (err) { + setError(String(err)); + } + } + + const filtered = search + ? bookmarks.filter( + (b) => + b.title.toLowerCase().includes(search.toLowerCase()) || + (b.url ?? "").toLowerCase().includes(search.toLowerCase()), + ) + : bookmarks; + + return ( +
+ {error && ( +
+ {error} +
+ )} + + {status === "pick" && ( + <> +

+ Select a bookmark to find the best folder for it. +

+ setSearch(e.target.value)} + placeholder="Search bookmarks..." + className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-indigo-500 focus:outline-none" + /> +
+ {filtered.slice(0, 50).map((bm) => ( + + ))} + {filtered.length > 50 && ( +

+ Showing 50 of {filtered.length} — refine your search +

+ )} + {filtered.length === 0 && bookmarks.length > 0 && ( +

+ No bookmarks match your search +

+ )} +
+ + )} + + {status === "loading" && ( +
+
+
+ {selected?.title} +
+
+
+ + Finding best folder... +
+
+ )} + + {status === "results" && selected && ( +
+
+
+ {selected.title} +
+ {selected.url && ( +
+ {selected.url} +
+ )} +
+ +

+ Suggested Folders +

+ {suggestions.map((s, i) => ( + + ))} + + +
+ )} +
+ ); +} + +// ─── Duplicate Cleanup Mode ───────────────────────────────────────── + +interface DuplicateData { + duplicates: BookmarkDuplicateGroup[]; + folderPaths: Record; +} + +function DuplicatesMode({ onBack }: { onBack: () => void }) { + const [status, setStatus] = useState("idle"); + const [data, setData] = useState(null); + const [removals, setRemovals] = useState>(new Set()); + const [error, setError] = useState(null); + const [undoAvailable, setUndoAvailable] = useState(false); + + async function handleScan() { + setStatus("loading"); + setError(null); + try { + const response = await sendMessage( + "find-duplicate-bookmarks", + ); + if (response.success && response.data) { + setData(response.data); + // Pre-select all duplicates except the first in each group + const preselected = new Set(); + for (const group of response.data.duplicates) { + for (let i = 1; i < group.bookmarks.length; i++) { + preselected.add(group.bookmarks[i].id); + } + } + setRemovals(preselected); + setStatus("preview"); + } else { + setError(response.error ?? "Failed to scan bookmarks"); + setStatus("idle"); + } + } catch (err) { + setError(String(err)); + setStatus("idle"); + } + } + + async function handleApply() { + setStatus("applying"); + try { + const response = await sendMessage("apply-bookmark-suggestions", { + moves: [], + newFolders: [], + removals: Array.from(removals), + }); + if (response.success) { + setStatus("done"); + setUndoAvailable(true); + } else { + setError(response.error ?? "Failed to remove duplicates"); + setStatus("preview"); + } + } catch (err) { + setError(String(err)); + setStatus("preview"); + } + } + + async function handleUndo() { + const response = await sendMessage("undo-bookmark-changes"); + if (response.success) { + setUndoAvailable(false); + setData(null); + setStatus("idle"); + } else { + setError(response.error ?? "Undo failed"); + } + } + + function toggleRemoval(id: string) { + setRemovals((prev) => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; + }); + } + + return ( +
+ {status === "idle" && ( + <> +

+ Scan your bookmarks for exact URL duplicates. +

+ + + )} + + {error && ( +
+ {error} +
+ )} + + {status === "loading" && ( +
+ + Scanning bookmarks... +
+ )} + + {status === "preview" && data && ( +
+ {data.duplicates.length === 0 ? ( +

+ No duplicate bookmarks found! +

+ ) : ( + <> +

+ Found {data.duplicates.length} set + {data.duplicates.length > 1 ? "s" : ""} of duplicates. + First bookmark in each set is kept by default. +

+ + {data.duplicates.map((group, gi) => ( +
+
+ {group.url} +
+ {group.bookmarks.map((bm, bi) => { + const folderPath = + bm.parentId ? data.folderPaths[bm.parentId] : "Unknown"; + const isFirst = bi === 0; + + return ( +
+ {isFirst ? ( +
+ ) : ( + toggleRemoval(bm.id)} + className="h-3.5 w-3.5 shrink-0 accent-indigo-500" + /> + )} +
+
+ {bm.title} +
+
+ in {folderPath} +
+
+ + {isFirst + ? "keep" + : removals.has(bm.id) + ? "remove" + : "keep"} + +
+ ); + })} +
+ ))} + +
+ + +
+ + )} +
+ )} + + {status === "applying" && ( +
+ + Removing duplicates... +
+ )} + + {status === "done" && ( +
+
+ Duplicates removed! +
+
+ {undoAvailable && ( + + )} + +
+
+ )} +
+ ); +} + +// ─── Shared ───────────────────────────────────────────────────────── + +function Spinner() { + return ( +
+ ); +} From 64e39d431dd903f85e946af16ab7d9dedfd488b2 Mon Sep 17 00:00:00 2001 From: Joshua Butner <53810+heyjawrsh@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:25:59 -0500 Subject: [PATCH 04/54] Added folder cleanup to Bookmarks section --- src/background/service-worker.ts | 19 +++++ src/core/bookmarks.ts | 57 +++++++++++++++ src/shared/messaging.ts | 1 + src/sidepanel/pages/BookmarkOrganizer.tsx | 89 ++++++++++++++++++++++- 4 files changed, 165 insertions(+), 1 deletion(-) diff --git a/src/background/service-worker.ts b/src/background/service-worker.ts index 89e26a2..f3d347f 100644 --- a/src/background/service-worker.ts +++ b/src/background/service-worker.ts @@ -38,6 +38,7 @@ import { moveBookmark, createFolder, removeBookmark, + removeEmptyFolders, } from "@/core/bookmarks"; import { STORAGE_KEYS } from "@/shared/constants"; import { getAIProvider } from "@/ai/provider"; @@ -100,6 +101,9 @@ async function handleMessage(message: Message): Promise { case "undo-bookmark-changes": return await handleUndoBookmarkChanges(); + case "cleanup-empty-folders": + return await handleCleanupEmptyFolders(); + default: return { success: false, error: `Unknown action: ${message.action}` }; } @@ -275,6 +279,7 @@ interface BookmarkApplyPayload { moves: { bookmarkId: string; targetFolderId: string }[]; newFolders: { name: string; parentId: string }[]; removals: string[]; + cleanupEmptyFolders?: boolean; } async function handleOrganizeBookmarks(): Promise< @@ -428,9 +433,23 @@ async function handleApplyBookmarkSuggestions( } } + // Clean up empty folders left behind by moves/removals + if (payload.cleanupEmptyFolders !== false) { + const removed = await removeEmptyFolders(); + return { success: true, data: { emptyFoldersRemoved: removed } }; + } + return { success: true }; } +async function handleCleanupEmptyFolders(): Promise { + const removed = await removeEmptyFolders(); + return { + success: true, + data: { removed }, + }; +} + async function handleUndoBookmarkChanges(): Promise { const snapshot = await getSessionData( STORAGE_KEYS.BOOKMARK_SNAPSHOT, diff --git a/src/core/bookmarks.ts b/src/core/bookmarks.ts index f2625c5..95bd8af 100644 --- a/src/core/bookmarks.ts +++ b/src/core/bookmarks.ts @@ -138,6 +138,63 @@ export function findDuplicateBookmarksDetailed( .map(([url, bms]) => ({ url, bookmarks: bms })); } +// Chrome's built-in root folder IDs that must never be removed +const PROTECTED_FOLDER_IDS = new Set(["0", "1", "2", "3"]); + +export async function findEmptyFolders( + nodes: chrome.bookmarks.BookmarkTreeNode[], +): Promise<{ id: string; title: string; path: string }[]> { + const empties: { id: string; title: string; path: string }[] = []; + + function walk(node: chrome.bookmarks.BookmarkTreeNode, path: string) { + if (node.url) return; // It's a bookmark, not a folder + if (!node.children) return; + + const currentPath = path ? `${path}/${node.title}` : node.title || "Root"; + + // A folder is empty if it has no children at all + // or all its children are also empty folders (recursively) + const hasContent = node.children.some( + (child) => child.url || (child.children && child.children.length > 0), + ); + + if ( + !hasContent && + node.children.length === 0 && + !PROTECTED_FOLDER_IDS.has(node.id) + ) { + empties.push({ id: node.id, title: node.title, path: currentPath }); + } + + for (const child of node.children) { + walk(child, node.id === "0" ? "" : currentPath); + } + } + + for (const node of nodes) walk(node, ""); + return empties; +} + +export async function removeEmptyFolders(): Promise { + let totalRemoved = 0; + // Run multiple passes since removing a folder may make its parent empty + for (let pass = 0; pass < 10; pass++) { + const tree = await getBookmarkTree(); + const empties = await findEmptyFolders(tree); + if (empties.length === 0) break; + + for (const folder of empties) { + try { + await chrome.bookmarks.removeTree(folder.id); + totalRemoved++; + } catch { + // Folder may have been removed already by a parent removal + } + } + } + return totalRemoved; +} + export function snapshotBookmarks( bookmarks: BookmarkInfo[], ): BookmarkSnapshot[] { diff --git a/src/shared/messaging.ts b/src/shared/messaging.ts index 4f16b67..05d3636 100644 --- a/src/shared/messaging.ts +++ b/src/shared/messaging.ts @@ -7,6 +7,7 @@ export type MessageAction = | "find-duplicate-bookmarks" | "apply-bookmark-suggestions" | "undo-bookmark-changes" + | "cleanup-empty-folders" | "get-settings" | "save-settings"; diff --git a/src/sidepanel/pages/BookmarkOrganizer.tsx b/src/sidepanel/pages/BookmarkOrganizer.tsx index 5875c44..d5a9041 100644 --- a/src/sidepanel/pages/BookmarkOrganizer.tsx +++ b/src/sidepanel/pages/BookmarkOrganizer.tsx @@ -8,7 +8,7 @@ import type { LocationSuggestion, } from "@/shared/types"; -type Mode = "menu" | "organize" | "locate" | "duplicates"; +type Mode = "menu" | "organize" | "locate" | "duplicates" | "cleanup"; type Status = "idle" | "loading" | "preview" | "applying" | "done"; export function BookmarkOrganizer() { @@ -34,6 +34,9 @@ export function BookmarkOrganizer() { {mode === "duplicates" && ( setMode("menu")} /> )} + {mode === "cleanup" && ( + setMode("menu")} /> + )}
); } @@ -55,6 +58,11 @@ function ModeMenu({ onSelect }: { onSelect: (mode: Mode) => void }) { title: "Find Duplicates", description: "Scan for duplicate bookmarks and clean them up", }, + { + id: "cleanup" as Mode, + title: "Clean Up Empty Folders", + description: "Remove folders that no longer contain any bookmarks", + }, ]; return ( @@ -138,6 +146,7 @@ function OrganizeMode({ onBack }: { onBack: () => void }) { moves, newFolders, removals: [], + cleanupEmptyFolders: true, }); if (response.success) { @@ -369,6 +378,7 @@ function LocateMode({ onBack }: { onBack: () => void }) { moves: [{ bookmarkId: selected.id, targetFolderId: folderId }], newFolders: [], removals: [], + cleanupEmptyFolders: true, }); setStatus("pick"); setSelected(null); @@ -552,6 +562,7 @@ function DuplicatesMode({ onBack }: { onBack: () => void }) { moves: [], newFolders: [], removals: Array.from(removals), + cleanupEmptyFolders: true, }); if (response.success) { setStatus("done"); @@ -737,6 +748,82 @@ function DuplicatesMode({ onBack }: { onBack: () => void }) { ); } +// ─── Empty Folder Cleanup Mode ────────────────────────────────────── + +function CleanupMode({ onBack }: { onBack: () => void }) { + const [status, setStatus] = useState<"idle" | "loading" | "done">("idle"); + const [removed, setRemoved] = useState(0); + const [error, setError] = useState(null); + + async function handleCleanup() { + setStatus("loading"); + setError(null); + try { + const response = await sendMessage( + "cleanup-empty-folders", + ); + if (response.success && response.data) { + setRemoved(response.data.removed); + setStatus("done"); + } else { + setError(response.error ?? "Cleanup failed"); + setStatus("idle"); + } + } catch (err) { + setError(String(err)); + setStatus("idle"); + } + } + + return ( +
+ {error && ( +
+ {error} +
+ )} + + {status === "idle" && ( + <> +

+ Scan for and remove empty bookmark folders. This runs in multiple + passes to catch nested empty folders. +

+ + + )} + + {status === "loading" && ( +
+ + Scanning and removing empty folders... +
+ )} + + {status === "done" && ( +
+
+ {removed === 0 + ? "No empty folders found — bookmarks are clean!" + : `Removed ${removed} empty folder${removed !== 1 ? "s" : ""}.`} +
+ +
+ )} +
+ ); +} + // ─── Shared ───────────────────────────────────────────────────────── function Spinner() { From d87184c305a7fe67a7280179300d2559631221e4 Mon Sep 17 00:00:00 2001 From: Joshua Butner <53810+heyjawrsh@users.noreply.github.com> Date: Fri, 27 Mar 2026 04:51:47 -0500 Subject: [PATCH 05/54] Monorepo (#2) * Updating gitignore * Split into concerns of a newly created monorepo --- .gitignore | 27 + apps/docs/astro.config.mjs | 36 + apps/docs/package.json | 16 + apps/docs/public/favicon.svg | 1 + apps/docs/src/assets/icon.svg | 1 + apps/docs/src/content.config.ts | 7 + .../src/content/docs/config/ai-providers.md | 29 + apps/docs/src/content/docs/config/settings.md | 16 + .../src/content/docs/features/bookmarks.md | 18 + .../content/docs/features/privacy-tiers.md | 25 + apps/docs/src/content/docs/features/tabs.md | 27 + .../docs/getting-started/installation.md | 24 + .../docs/getting-started/quick-start.md | 23 + apps/docs/src/content/docs/index.mdx | 15 + apps/docs/src/styles/custom.css | 5 + apps/docs/tsconfig.json | 3 + apps/extension/package.json | 34 + .../extension/public}/icons/icon-128.png | Bin .../extension/public}/icons/icon-16.png | Bin .../extension/public}/icons/icon-48.png | Bin .../extension/public}/manifest.json | 0 {src => apps/extension/src}/ai/parser.ts | 0 .../src}/ai/prompts/bookmark-grouping.ts | 0 .../extension/src}/ai/prompts/tab-grouping.ts | 0 {src => apps/extension/src}/ai/provider.ts | 0 .../extension/src}/ai/providers/local.ts | 0 .../extension/src}/ai/providers/openrouter.ts | 0 .../extension/src}/ai/providers/relaxed.ts | 0 .../extension/src}/ai/providers/yolo.ts | 0 {src => apps/extension/src}/ai/types.ts | 0 .../src}/background/service-worker.ts | 0 {src => apps/extension/src}/core/bookmarks.ts | 0 {src => apps/extension/src}/core/storage.ts | 0 {src => apps/extension/src}/core/tabs.ts | 0 .../extension/src}/shared/constants.ts | 0 .../extension/src}/shared/messaging.ts | 0 {src => apps/extension/src}/shared/types.ts | 0 {src => apps/extension/src}/sidepanel/App.tsx | 0 .../extension/src}/sidepanel/index.html | 0 .../extension/src}/sidepanel/main.tsx | 0 .../sidepanel/pages/BookmarkOrganizer.tsx | 0 .../src}/sidepanel/pages/Settings.tsx | 0 .../src}/sidepanel/pages/TabOrganizer.tsx | 0 .../extension/src}/styles/globals.css | 0 {src => apps/extension/src}/vite-env.d.ts | 0 tsconfig.json => apps/extension/tsconfig.json | 0 .../extension/vite.config.ts | 0 apps/web/astro.config.mjs | 9 + apps/web/package.json | 16 + apps/web/public/favicon.svg | 1 + apps/web/src/layouts/Base.astro | 29 + apps/web/src/pages/index.astro | 66 + apps/web/src/styles/globals.css | 16 + apps/web/tsconfig.json | 3 + package-lock.json | 3326 --------- package.json | 40 +- packages/branding/icons/icon-color.svg | 1 + packages/branding/icons/icon-mono.svg | 1 + packages/branding/icons/icon.svg | 1 + packages/branding/logos/logo.svg | 1 + packages/branding/package.json | 12 + packages/branding/src/index.ts | 18 + packages/shared/package.json | 10 + packages/shared/src/index.ts | 3 + pnpm-lock.yaml | 6518 +++++++++++++++++ pnpm-workspace.yaml | 3 + turbo.json | 15 + 67 files changed, 7045 insertions(+), 3351 deletions(-) create mode 100644 apps/docs/astro.config.mjs create mode 100644 apps/docs/package.json create mode 100644 apps/docs/public/favicon.svg create mode 100644 apps/docs/src/assets/icon.svg create mode 100644 apps/docs/src/content.config.ts create mode 100644 apps/docs/src/content/docs/config/ai-providers.md create mode 100644 apps/docs/src/content/docs/config/settings.md create mode 100644 apps/docs/src/content/docs/features/bookmarks.md create mode 100644 apps/docs/src/content/docs/features/privacy-tiers.md create mode 100644 apps/docs/src/content/docs/features/tabs.md create mode 100644 apps/docs/src/content/docs/getting-started/installation.md create mode 100644 apps/docs/src/content/docs/getting-started/quick-start.md create mode 100644 apps/docs/src/content/docs/index.mdx create mode 100644 apps/docs/src/styles/custom.css create mode 100644 apps/docs/tsconfig.json create mode 100644 apps/extension/package.json rename {public => apps/extension/public}/icons/icon-128.png (100%) rename {public => apps/extension/public}/icons/icon-16.png (100%) rename {public => apps/extension/public}/icons/icon-48.png (100%) rename {public => apps/extension/public}/manifest.json (100%) rename {src => apps/extension/src}/ai/parser.ts (100%) rename {src => apps/extension/src}/ai/prompts/bookmark-grouping.ts (100%) rename {src => apps/extension/src}/ai/prompts/tab-grouping.ts (100%) rename {src => apps/extension/src}/ai/provider.ts (100%) rename {src => apps/extension/src}/ai/providers/local.ts (100%) rename {src => apps/extension/src}/ai/providers/openrouter.ts (100%) rename {src => apps/extension/src}/ai/providers/relaxed.ts (100%) rename {src => apps/extension/src}/ai/providers/yolo.ts (100%) rename {src => apps/extension/src}/ai/types.ts (100%) rename {src => apps/extension/src}/background/service-worker.ts (100%) rename {src => apps/extension/src}/core/bookmarks.ts (100%) rename {src => apps/extension/src}/core/storage.ts (100%) rename {src => apps/extension/src}/core/tabs.ts (100%) rename {src => apps/extension/src}/shared/constants.ts (100%) rename {src => apps/extension/src}/shared/messaging.ts (100%) rename {src => apps/extension/src}/shared/types.ts (100%) rename {src => apps/extension/src}/sidepanel/App.tsx (100%) rename {src => apps/extension/src}/sidepanel/index.html (100%) rename {src => apps/extension/src}/sidepanel/main.tsx (100%) rename {src => apps/extension/src}/sidepanel/pages/BookmarkOrganizer.tsx (100%) rename {src => apps/extension/src}/sidepanel/pages/Settings.tsx (100%) rename {src => apps/extension/src}/sidepanel/pages/TabOrganizer.tsx (100%) rename {src => apps/extension/src}/styles/globals.css (100%) rename {src => apps/extension/src}/vite-env.d.ts (100%) rename tsconfig.json => apps/extension/tsconfig.json (100%) rename vite.config.ts => apps/extension/vite.config.ts (100%) create mode 100644 apps/web/astro.config.mjs create mode 100644 apps/web/package.json create mode 100644 apps/web/public/favicon.svg create mode 100644 apps/web/src/layouts/Base.astro create mode 100644 apps/web/src/pages/index.astro create mode 100644 apps/web/src/styles/globals.css create mode 100644 apps/web/tsconfig.json delete mode 100644 package-lock.json create mode 100644 packages/branding/icons/icon-color.svg create mode 100644 packages/branding/icons/icon-mono.svg create mode 100644 packages/branding/icons/icon.svg create mode 100644 packages/branding/logos/logo.svg create mode 100644 packages/branding/package.json create mode 100644 packages/branding/src/index.ts create mode 100644 packages/shared/package.json create mode 100644 packages/shared/src/index.ts create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 turbo.json diff --git a/.gitignore b/.gitignore index b518b92..754de99 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,31 @@ node_modules/ dist/ +.turbo/ *.local .DS_Store + +# Environment variables +.env +.env.* +!.env.example + +# Astro generated types +.astro/ + +# Build / cache +*.tsbuildinfo +.cache/ +coverage/ + +# Logs +*.log + +# Editor +.vscode/ + +# AI agent configs +.claude/ +.serena/ + +# Wrong lockfile (pnpm project) +package-lock.json diff --git a/apps/docs/astro.config.mjs b/apps/docs/astro.config.mjs new file mode 100644 index 0000000..e499d0b --- /dev/null +++ b/apps/docs/astro.config.mjs @@ -0,0 +1,36 @@ +import { defineConfig } from "astro/config"; +import starlight from "@astrojs/starlight"; + +export default defineConfig({ + integrations: [ + starlight({ + title: "vxrtx docs", + logo: { + src: "./src/assets/icon.svg", + }, + social: [ + { + icon: "github", + label: "GitHub", + href: "https://github.com/vxrtx", + }, + ], + sidebar: [ + { + label: "Getting Started", + autogenerate: { directory: "getting-started" }, + }, + { + label: "Features", + autogenerate: { directory: "features" }, + }, + { + label: "Configuration", + autogenerate: { directory: "config" }, + }, + ], + customCss: ["./src/styles/custom.css"], + }), + ], + site: "https://docs.vxrtx.app", +}); diff --git a/apps/docs/package.json b/apps/docs/package.json new file mode 100644 index 0000000..1c85438 --- /dev/null +++ b/apps/docs/package.json @@ -0,0 +1,16 @@ +{ + "name": "@vxrtx/docs", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview" + }, + "dependencies": { + "astro": "^5.9.3", + "@astrojs/starlight": "^0.34.3", + "sharp": "^0.33.5" + } +} diff --git a/apps/docs/public/favicon.svg b/apps/docs/public/favicon.svg new file mode 100644 index 0000000..27ad037 --- /dev/null +++ b/apps/docs/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/docs/src/assets/icon.svg b/apps/docs/src/assets/icon.svg new file mode 100644 index 0000000..27ad037 --- /dev/null +++ b/apps/docs/src/assets/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/docs/src/content.config.ts b/apps/docs/src/content.config.ts new file mode 100644 index 0000000..7fbcf2c --- /dev/null +++ b/apps/docs/src/content.config.ts @@ -0,0 +1,7 @@ +import { defineCollection } from "astro:content"; +import { docsLoader } from "@astrojs/starlight/loaders"; +import { docsSchema } from "@astrojs/starlight/schema"; + +export const collections = { + docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), +}; diff --git a/apps/docs/src/content/docs/config/ai-providers.md b/apps/docs/src/content/docs/config/ai-providers.md new file mode 100644 index 0000000..727dd6c --- /dev/null +++ b/apps/docs/src/content/docs/config/ai-providers.md @@ -0,0 +1,29 @@ +--- +title: AI Providers +description: Configure which AI provider vxrtx uses +--- + +## OpenRouter (Recommended) + +One API key, access to many models — Claude, GPT, Llama, Gemini, and more. + +1. Get a key at [openrouter.ai](https://openrouter.ai) +2. Go to **Settings** > **AI Provider** > **OpenRouter** +3. Paste your key and select a model + +### Popular Models +- **Claude Sonnet 4** — best quality +- **Claude Haiku 4** — fast and cheap +- **GPT-4o Mini** — fast and cheap +- **Llama 4 Scout** — free + +## Direct API Access + +You can also use Claude or OpenAI APIs directly: + +- **Claude**: Get a key at [console.anthropic.com](https://console.anthropic.com) +- **OpenAI**: Get a key at [platform.openai.com](https://platform.openai.com) + +## Local AI (Secure Tier) + +See [Privacy Tiers](/features/privacy-tiers) for Chrome Built-in AI and Ollama setup. diff --git a/apps/docs/src/content/docs/config/settings.md b/apps/docs/src/content/docs/config/settings.md new file mode 100644 index 0000000..4459dfe --- /dev/null +++ b/apps/docs/src/content/docs/config/settings.md @@ -0,0 +1,16 @@ +--- +title: Settings +description: All configurable options in vxrtx +--- + +## AI Privacy Tier +Choose how much data AI can access. See [Privacy Tiers](/features/privacy-tiers). + +## AI Provider +Select OpenRouter, Claude, or OpenAI for cloud tiers. See [AI Providers](/config/ai-providers). + +## Local AI Backend +When using the Secure tier, choose between Chrome Built-in AI and Ollama. + +## Stale Tab Threshold +Number of days after which a tab is considered stale (default: 7). Tabs not accessed within this window will be flagged during organization. diff --git a/apps/docs/src/content/docs/features/bookmarks.md b/apps/docs/src/content/docs/features/bookmarks.md new file mode 100644 index 0000000..81e626c --- /dev/null +++ b/apps/docs/src/content/docs/features/bookmarks.md @@ -0,0 +1,18 @@ +--- +title: Bookmark Organization +description: Restructure bookmarks, find duplicates, and get AI folder suggestions +--- + +## Three Modes + +### Reorganize All +AI analyzes your entire bookmark collection and suggests a new folder structure. Preview the changes, toggle folders on/off, then apply. + +### Where Should This Go? +Pick a bookmark from a searchable list. AI suggests the best existing folder for it, ranked by confidence with reasoning. + +### Find Duplicates +Scans all bookmarks by URL. Shows groups of duplicates with which folder each lives in. Pick which copies to keep. + +## Empty Folder Cleanup +After any reorganization, empty folders are automatically cleaned up. You can also run cleanup manually from the Bookmarks menu. diff --git a/apps/docs/src/content/docs/features/privacy-tiers.md b/apps/docs/src/content/docs/features/privacy-tiers.md new file mode 100644 index 0000000..37bbde0 --- /dev/null +++ b/apps/docs/src/content/docs/features/privacy-tiers.md @@ -0,0 +1,25 @@ +--- +title: AI Privacy Tiers +description: Control exactly what data leaves your machine +--- + +## Three Tiers + +### Secure +Fully local AI. Your data never leaves your machine. + +- **Chrome Built-in AI** (Gemini Nano) — zero setup, runs inside Chrome +- **Ollama** — connect to a local model running on your machine + +### Relaxed +Cloud AI with minimal data exposure. + +- Only tab/bookmark **titles** are sent — no URLs +- Works with OpenRouter, Claude, or OpenAI + +### YOLO +Full context sent to AI for the best possible results. + +- Titles, URLs, and timestamps +- Produces the most accurate groupings +- Same provider options as Relaxed diff --git a/apps/docs/src/content/docs/features/tabs.md b/apps/docs/src/content/docs/features/tabs.md new file mode 100644 index 0000000..21584b7 --- /dev/null +++ b/apps/docs/src/content/docs/features/tabs.md @@ -0,0 +1,27 @@ +--- +title: Tab Organization +description: AI-powered tab grouping, deduplication, and stale tab detection +--- + +## How It Works + +vxrtx analyzes your open tabs and suggests logical groupings by topic, project, or activity — not just domain. + +## Features + +### Smart Grouping +AI examines tab titles (and URLs in YOLO mode) to create meaningful groups like "Work — Jira", "Research", or "Shopping". + +### Duplicate Detection +Finds tabs with identical URLs and suggests closing the extras. + +### Stale Tab Detection +Flags tabs you haven't visited in a while (configurable threshold in Settings). + +### Full Preview Control +Every suggestion is a proposal. You can: +- Toggle groups on/off with checkboxes +- Rename groups by clicking the name +- Cycle colors by clicking the color dot +- Remove individual tabs from groups +- Undo after applying diff --git a/apps/docs/src/content/docs/getting-started/installation.md b/apps/docs/src/content/docs/getting-started/installation.md new file mode 100644 index 0000000..588f5f1 --- /dev/null +++ b/apps/docs/src/content/docs/getting-started/installation.md @@ -0,0 +1,24 @@ +--- +title: Installation +description: How to install the vxrtx Chrome extension +--- + +## Chrome Web Store + +Install vxrtx directly from the [Chrome Web Store](#) (coming soon). + +## Manual Installation (Development) + +1. Clone the repository and build the extension: + +```bash +git clone https://github.com/vxrtx/vxrtx.git +cd vxrtx +pnpm install +pnpm build:extension +``` + +2. Open Chrome and navigate to `chrome://extensions` +3. Enable **Developer mode** (toggle in the top right) +4. Click **Load unpacked** and select the `apps/extension/dist` folder +5. The vxrtx icon will appear in your toolbar diff --git a/apps/docs/src/content/docs/getting-started/quick-start.md b/apps/docs/src/content/docs/getting-started/quick-start.md new file mode 100644 index 0000000..d533969 --- /dev/null +++ b/apps/docs/src/content/docs/getting-started/quick-start.md @@ -0,0 +1,23 @@ +--- +title: Quick Start +description: Get up and running with vxrtx in under a minute +--- + +## Open the Side Panel + +Click the vxrtx icon in your Chrome toolbar, or press `Cmd+Shift+V` (Mac) / `Ctrl+Shift+V` (Windows/Linux). + +## Organize Your Tabs + +1. Go to the **Tabs** tab +2. Click **Organize Tabs** +3. Review the suggested groups — toggle, rename, or recolor as needed +4. Click **Apply** to create the groups + +## Choose Your AI Tier + +Go to **Settings** and pick your privacy level: + +- **Secure** — fully local AI, nothing leaves your machine +- **Relaxed** — cloud AI with minimal data (titles only) +- **YOLO** — full context for the best results diff --git a/apps/docs/src/content/docs/index.mdx b/apps/docs/src/content/docs/index.mdx new file mode 100644 index 0000000..9bc4ec3 --- /dev/null +++ b/apps/docs/src/content/docs/index.mdx @@ -0,0 +1,15 @@ +--- +title: vxrtx Documentation +description: AI-powered tab and bookmark organization for Chrome +template: splash +hero: + tagline: Organize your browser with AI — on your terms + actions: + - text: Get Started + link: /getting-started/installation/ + icon: right-arrow + variant: primary + - text: View on GitHub + link: https://github.com/vxrtx + icon: external +--- diff --git a/apps/docs/src/styles/custom.css b/apps/docs/src/styles/custom.css new file mode 100644 index 0000000..72a53ed --- /dev/null +++ b/apps/docs/src/styles/custom.css @@ -0,0 +1,5 @@ +:root { + --sl-color-accent-low: #083242; + --sl-color-accent: #18ccf1; + --sl-color-accent-high: #d1f7fc; +} diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json new file mode 100644 index 0000000..bcbf8b5 --- /dev/null +++ b/apps/docs/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "astro/tsconfigs/strict" +} diff --git a/apps/extension/package.json b/apps/extension/package.json new file mode 100644 index 0000000..9766f89 --- /dev/null +++ b/apps/extension/package.json @@ -0,0 +1,34 @@ +{ + "name": "@vxrtx/extension", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "AI-powered tab and bookmark organization — Chrome extension", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "test": "vitest run", + "test:watch": "vitest", + "lint": "eslint src/" + }, + "dependencies": { + "react": "^19.2.4", + "react-dom": "^19.2.4", + "zod": "^4.3.6" + }, + "devDependencies": { + "@crxjs/vite-plugin": "^2.4.0", + "@tailwindcss/vite": "^4.2.2", + "@types/chrome": "^0.1.38", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "autoprefixer": "^10.4.27", + "eslint": "^10.1.0", + "postcss": "^8.5.8", + "tailwindcss": "^4.2.2", + "typescript": "^6.0.2", + "vite": "^8.0.3", + "vitest": "^4.1.2" + } +} diff --git a/public/icons/icon-128.png b/apps/extension/public/icons/icon-128.png similarity index 100% rename from public/icons/icon-128.png rename to apps/extension/public/icons/icon-128.png diff --git a/public/icons/icon-16.png b/apps/extension/public/icons/icon-16.png similarity index 100% rename from public/icons/icon-16.png rename to apps/extension/public/icons/icon-16.png diff --git a/public/icons/icon-48.png b/apps/extension/public/icons/icon-48.png similarity index 100% rename from public/icons/icon-48.png rename to apps/extension/public/icons/icon-48.png diff --git a/public/manifest.json b/apps/extension/public/manifest.json similarity index 100% rename from public/manifest.json rename to apps/extension/public/manifest.json diff --git a/src/ai/parser.ts b/apps/extension/src/ai/parser.ts similarity index 100% rename from src/ai/parser.ts rename to apps/extension/src/ai/parser.ts diff --git a/src/ai/prompts/bookmark-grouping.ts b/apps/extension/src/ai/prompts/bookmark-grouping.ts similarity index 100% rename from src/ai/prompts/bookmark-grouping.ts rename to apps/extension/src/ai/prompts/bookmark-grouping.ts diff --git a/src/ai/prompts/tab-grouping.ts b/apps/extension/src/ai/prompts/tab-grouping.ts similarity index 100% rename from src/ai/prompts/tab-grouping.ts rename to apps/extension/src/ai/prompts/tab-grouping.ts diff --git a/src/ai/provider.ts b/apps/extension/src/ai/provider.ts similarity index 100% rename from src/ai/provider.ts rename to apps/extension/src/ai/provider.ts diff --git a/src/ai/providers/local.ts b/apps/extension/src/ai/providers/local.ts similarity index 100% rename from src/ai/providers/local.ts rename to apps/extension/src/ai/providers/local.ts diff --git a/src/ai/providers/openrouter.ts b/apps/extension/src/ai/providers/openrouter.ts similarity index 100% rename from src/ai/providers/openrouter.ts rename to apps/extension/src/ai/providers/openrouter.ts diff --git a/src/ai/providers/relaxed.ts b/apps/extension/src/ai/providers/relaxed.ts similarity index 100% rename from src/ai/providers/relaxed.ts rename to apps/extension/src/ai/providers/relaxed.ts diff --git a/src/ai/providers/yolo.ts b/apps/extension/src/ai/providers/yolo.ts similarity index 100% rename from src/ai/providers/yolo.ts rename to apps/extension/src/ai/providers/yolo.ts diff --git a/src/ai/types.ts b/apps/extension/src/ai/types.ts similarity index 100% rename from src/ai/types.ts rename to apps/extension/src/ai/types.ts diff --git a/src/background/service-worker.ts b/apps/extension/src/background/service-worker.ts similarity index 100% rename from src/background/service-worker.ts rename to apps/extension/src/background/service-worker.ts diff --git a/src/core/bookmarks.ts b/apps/extension/src/core/bookmarks.ts similarity index 100% rename from src/core/bookmarks.ts rename to apps/extension/src/core/bookmarks.ts diff --git a/src/core/storage.ts b/apps/extension/src/core/storage.ts similarity index 100% rename from src/core/storage.ts rename to apps/extension/src/core/storage.ts diff --git a/src/core/tabs.ts b/apps/extension/src/core/tabs.ts similarity index 100% rename from src/core/tabs.ts rename to apps/extension/src/core/tabs.ts diff --git a/src/shared/constants.ts b/apps/extension/src/shared/constants.ts similarity index 100% rename from src/shared/constants.ts rename to apps/extension/src/shared/constants.ts diff --git a/src/shared/messaging.ts b/apps/extension/src/shared/messaging.ts similarity index 100% rename from src/shared/messaging.ts rename to apps/extension/src/shared/messaging.ts diff --git a/src/shared/types.ts b/apps/extension/src/shared/types.ts similarity index 100% rename from src/shared/types.ts rename to apps/extension/src/shared/types.ts diff --git a/src/sidepanel/App.tsx b/apps/extension/src/sidepanel/App.tsx similarity index 100% rename from src/sidepanel/App.tsx rename to apps/extension/src/sidepanel/App.tsx diff --git a/src/sidepanel/index.html b/apps/extension/src/sidepanel/index.html similarity index 100% rename from src/sidepanel/index.html rename to apps/extension/src/sidepanel/index.html diff --git a/src/sidepanel/main.tsx b/apps/extension/src/sidepanel/main.tsx similarity index 100% rename from src/sidepanel/main.tsx rename to apps/extension/src/sidepanel/main.tsx diff --git a/src/sidepanel/pages/BookmarkOrganizer.tsx b/apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx similarity index 100% rename from src/sidepanel/pages/BookmarkOrganizer.tsx rename to apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx diff --git a/src/sidepanel/pages/Settings.tsx b/apps/extension/src/sidepanel/pages/Settings.tsx similarity index 100% rename from src/sidepanel/pages/Settings.tsx rename to apps/extension/src/sidepanel/pages/Settings.tsx diff --git a/src/sidepanel/pages/TabOrganizer.tsx b/apps/extension/src/sidepanel/pages/TabOrganizer.tsx similarity index 100% rename from src/sidepanel/pages/TabOrganizer.tsx rename to apps/extension/src/sidepanel/pages/TabOrganizer.tsx diff --git a/src/styles/globals.css b/apps/extension/src/styles/globals.css similarity index 100% rename from src/styles/globals.css rename to apps/extension/src/styles/globals.css diff --git a/src/vite-env.d.ts b/apps/extension/src/vite-env.d.ts similarity index 100% rename from src/vite-env.d.ts rename to apps/extension/src/vite-env.d.ts diff --git a/tsconfig.json b/apps/extension/tsconfig.json similarity index 100% rename from tsconfig.json rename to apps/extension/tsconfig.json diff --git a/vite.config.ts b/apps/extension/vite.config.ts similarity index 100% rename from vite.config.ts rename to apps/extension/vite.config.ts diff --git a/apps/web/astro.config.mjs b/apps/web/astro.config.mjs new file mode 100644 index 0000000..352c8e7 --- /dev/null +++ b/apps/web/astro.config.mjs @@ -0,0 +1,9 @@ +import { defineConfig } from "astro/config"; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + vite: { + plugins: [tailwindcss()], + }, + site: "https://vxrtx.app", +}); diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..2a8f824 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,16 @@ +{ + "name": "@vxrtx/web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview" + }, + "dependencies": { + "astro": "^5.9.3", + "@tailwindcss/vite": "^4.2.2", + "tailwindcss": "^4.2.2" + } +} diff --git a/apps/web/public/favicon.svg b/apps/web/public/favicon.svg new file mode 100644 index 0000000..27ad037 --- /dev/null +++ b/apps/web/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/layouts/Base.astro b/apps/web/src/layouts/Base.astro new file mode 100644 index 0000000..6b57848 --- /dev/null +++ b/apps/web/src/layouts/Base.astro @@ -0,0 +1,29 @@ +--- +interface Props { + title?: string; + description?: string; +} + +const { + title = "vxrtx — AI-powered tab and bookmark organization", + description = "Organize your browser tabs and bookmarks with AI. Privacy-first, with local and cloud options.", +} = Astro.props; +--- + + + + + + + + {title} + + + + + + + + diff --git a/apps/web/src/pages/index.astro b/apps/web/src/pages/index.astro new file mode 100644 index 0000000..a1c064c --- /dev/null +++ b/apps/web/src/pages/index.astro @@ -0,0 +1,66 @@ +--- +import Base from "../layouts/Base.astro"; +--- + + +
+
+ vxrtx + +

+ AI-powered tab & bookmark + organization +

+ +

+ Stop drowning in tabs. Let AI organize your browser — on your terms. + Choose between fully local, privacy-conscious, or full-power cloud AI. +

+ + +
+ +
+
+

Smart Tab Groups

+

+ AI analyzes your open tabs and suggests logical groups by topic, + project, or activity. Preview before applying. +

+
+ +
+

+ Bookmark Organization +

+

+ Restructure your entire bookmark tree, find the right folder for new + bookmarks, or clean up duplicates. +

+
+ +
+

+ Privacy Tiers +

+

+ Secure (fully local), Relaxed (minimal cloud data), or YOLO (full + cloud power). You control what leaves your machine. +

+
+
+
+ diff --git a/apps/web/src/styles/globals.css b/apps/web/src/styles/globals.css new file mode 100644 index 0000000..bc735c3 --- /dev/null +++ b/apps/web/src/styles/globals.css @@ -0,0 +1,16 @@ +@import "tailwindcss"; + +@theme { + --color-brand-50: #edfcfe; + --color-brand-100: #d1f7fc; + --color-brand-200: #a8eef9; + --color-brand-300: #6de1f4; + --color-brand-400: #18ccf1; + --color-brand-500: #0aaecd; + --color-brand-600: #0b8bac; + --color-brand-700: #10708c; + --color-brand-800: #165b72; + --color-brand-900: #164c61; + --color-brand-950: #083242; + --color-coal: #111313; +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..bcbf8b5 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "astro/tsconfigs/strict" +} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 8b24a19..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3326 +0,0 @@ -{ - "name": "vxrtx", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "vxrtx", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "react": "^19.2.4", - "react-dom": "^19.2.4", - "zod": "^4.3.6" - }, - "devDependencies": { - "@crxjs/vite-plugin": "^2.4.0", - "@tailwindcss/vite": "^4.2.2", - "@types/chrome": "^0.1.38", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "autoprefixer": "^10.4.27", - "eslint": "^10.1.0", - "postcss": "^8.5.8", - "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", - "vite": "^8.0.3", - "vitest": "^4.1.2" - } - }, - "node_modules/@crxjs/vite-plugin": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@crxjs/vite-plugin/-/vite-plugin-2.4.0.tgz", - "integrity": "sha512-bDLdq0W2V1SkMQDJjrcYyjK9/uKtdl4joT7GRImcootCjZdKRiRYt+cv9z8tJoU/tK3o1lX48LTqN7JMsk5AQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^4.1.2", - "@webcomponents/custom-elements": "^1.5.0", - "acorn-walk": "^8.2.0", - "convert-source-map": "^1.7.0", - "debug": "^4.3.3", - "es-module-lexer": "^0.10.0", - "fast-glob": "^3.2.11", - "fs-extra": "^10.0.1", - "jsesc": "^3.0.2", - "magic-string": "^0.30.12", - "node-html-parser": "^7.0.2", - "pathe": "^2.0.1", - "picocolors": "^1.1.1", - "react-refresh": "^0.13.0", - "rollup": "2.79.2", - "rxjs": "7.5.7" - }, - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", - "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", - "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", - "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.23.3", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz", - "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^3.0.3", - "debug": "^4.3.1", - "minimatch": "^10.2.4" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz", - "integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.1.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", - "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/object-schema": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz", - "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz", - "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.1.1", - "levn": "^0.4.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", - "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.122.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", - "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", - "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", - "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", - "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/pluginutils": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", - "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tailwindcss/node": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", - "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.19.0", - "jiti": "^2.6.1", - "lightningcss": "1.32.0", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.2.2" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", - "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.2", - "@tailwindcss/oxide-darwin-arm64": "4.2.2", - "@tailwindcss/oxide-darwin-x64": "4.2.2", - "@tailwindcss/oxide-freebsd-x64": "4.2.2", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", - "@tailwindcss/oxide-linux-x64-musl": "4.2.2", - "@tailwindcss/oxide-wasm32-wasi": "4.2.2", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", - "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", - "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", - "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", - "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", - "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", - "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", - "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", - "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", - "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", - "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.8.1", - "@emnapi/runtime": "^1.8.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.1", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", - "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", - "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz", - "integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.2.2", - "@tailwindcss/oxide": "4.2.2", - "tailwindcss": "4.2.2" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7 || ^8" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/chrome": { - "version": "0.1.38", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.38.tgz", - "integrity": "sha512-5aK4m9wZqoWAoB98aElESLm/5pXpqJnFWMNoiCs/XdPsXR6wNdVkJFSdQ9Wr4PnTuUrxD0SuNuDHh3EG5QeBzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/filesystem": "*", - "@types/har-format": "*" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/filesystem": { - "version": "0.0.36", - "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz", - "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/filewriter": "*" - } - }, - "node_modules/@types/filewriter": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", - "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/har-format": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", - "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", - "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", - "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.2", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/mocker/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", - "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", - "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.2", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", - "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.2", - "@vitest/utils": "4.1.2", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", - "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", - "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.2", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webcomponents/custom-elements": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@webcomponents/custom-elements/-/custom-elements-1.6.0.tgz", - "integrity": "sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", - "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.27", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", - "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001774", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.11", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.11.tgz", - "integrity": "sha512-DAKrHphkJyiGuau/cFieRYhcTFeK/lBuD++C7cZ6KZHbMhBrisoi+EvhQ5RZrIfV5qwsW8kgQ07JIC+MDJRAhg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001781", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", - "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.325", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.325.tgz", - "integrity": "sha512-PwfIw7WQSt3xX7yOf5OE/unLzsK9CaN2f/FvV3WjPR1Knoc1T9vePRVV4W1EM301JzzysK51K7FNKcusCr0zYA==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", - "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-module-lexer": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.10.5.tgz", - "integrity": "sha512-+7IwY/kiGAacQfY+YBhKMvEmyAJnw5grTUgjG85Pe7vcUI/6b7pZjZG8nQ7+48YhzEAEqrEgD2dCz/JIK+AYvw==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.1.0.tgz", - "integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.3", - "@eslint/config-helpers": "^0.5.3", - "@eslint/core": "^1.1.1", - "@eslint/plugin-kit": "^0.6.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-html-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-7.1.0.tgz", - "integrity": "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "css-select": "^5.1.0", - "he": "1.2.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.4" - } - }, - "node_modules/react-refresh": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.13.0.tgz", - "integrity": "sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rolldown": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", - "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.122.0", - "@rolldown/pluginutils": "1.0.0-rc.12" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-x64": "1.0.0-rc.12", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" - } - }, - "node_modules/rollup": { - "version": "2.79.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", - "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/tailwindcss": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", - "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", - "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", - "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vite": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", - "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.12", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/vitest": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", - "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.2", - "@vitest/mocker": "4.1.2", - "@vitest/pretty-format": "4.1.2", - "@vitest/runner": "4.1.2", - "@vitest/snapshot": "4.1.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.2", - "@vitest/browser-preview": "4.1.2", - "@vitest/browser-webdriverio": "4.1.2", - "@vitest/ui": "4.1.2", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/vitest/node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/package.json b/package.json index 2bbda15..32569b7 100644 --- a/package.json +++ b/package.json @@ -2,33 +2,23 @@ "name": "vxrtx", "version": "0.1.0", "private": true, - "type": "module", - "description": "AI-powered tab and bookmark organization", + "packageManager": "pnpm@10.33.0", "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "test": "vitest run", - "test:watch": "vitest", - "lint": "eslint src/" - }, - "dependencies": { - "react": "^19.2.4", - "react-dom": "^19.2.4", - "zod": "^4.3.6" + "dev": "turbo dev", + "build": "turbo build", + "lint": "turbo lint", + "test": "turbo test", + "dev:extension": "pnpm --filter @vxrtx/extension dev", + "dev:web": "pnpm --filter @vxrtx/web dev", + "dev:docs": "pnpm --filter @vxrtx/docs dev", + "build:extension": "pnpm --filter @vxrtx/extension build", + "build:web": "pnpm --filter @vxrtx/web build", + "build:docs": "pnpm --filter @vxrtx/docs build" }, "devDependencies": { - "@crxjs/vite-plugin": "^2.4.0", - "@tailwindcss/vite": "^4.2.2", - "@types/chrome": "^0.1.38", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "autoprefixer": "^10.4.27", - "eslint": "^10.1.0", - "postcss": "^8.5.8", - "tailwindcss": "^4.2.2", - "typescript": "^6.0.2", - "vite": "^8.0.3", - "vitest": "^4.1.2" + "turbo": "^2.5.4" + }, + "pnpm": { + "onlyBuiltDependencies": ["esbuild", "sharp"] } } diff --git a/packages/branding/icons/icon-color.svg b/packages/branding/icons/icon-color.svg new file mode 100644 index 0000000..13ba905 --- /dev/null +++ b/packages/branding/icons/icon-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/branding/icons/icon-mono.svg b/packages/branding/icons/icon-mono.svg new file mode 100644 index 0000000..ce670f1 --- /dev/null +++ b/packages/branding/icons/icon-mono.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/branding/icons/icon.svg b/packages/branding/icons/icon.svg new file mode 100644 index 0000000..27ad037 --- /dev/null +++ b/packages/branding/icons/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/branding/logos/logo.svg b/packages/branding/logos/logo.svg new file mode 100644 index 0000000..ad0caf9 --- /dev/null +++ b/packages/branding/logos/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/branding/package.json b/packages/branding/package.json new file mode 100644 index 0000000..df278ec --- /dev/null +++ b/packages/branding/package.json @@ -0,0 +1,12 @@ +{ + "name": "@vxrtx/branding", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "src/index.ts", + "exports": { + ".": "./src/index.ts", + "./icons/*": "./icons/*", + "./logos/*": "./logos/*" + } +} diff --git a/packages/branding/src/index.ts b/packages/branding/src/index.ts new file mode 100644 index 0000000..66a274b --- /dev/null +++ b/packages/branding/src/index.ts @@ -0,0 +1,18 @@ +export const colors = { + brand: { + 50: "#edfcfe", + 100: "#d1f7fc", + 200: "#a8eef9", + 300: "#6de1f4", + 400: "#18ccf1", + 500: "#0aaecd", + 600: "#0b8bac", + 700: "#10708c", + 800: "#165b72", + 900: "#164c61", + 950: "#083242", + }, + coal: "#111313", +} as const; + +export type BrandColor = keyof typeof colors.brand; diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000..d0d5ff5 --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,10 @@ +{ + "name": "@vxrtx/shared", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "src/index.ts", + "exports": { + ".": "./src/index.ts" + } +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 0000000..6d48455 --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,3 @@ +export const APP_NAME = "vxrtx"; +export const APP_DESCRIPTION = "AI-powered tab and bookmark organization"; +export const APP_URL = "https://vxrtx.app"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..cd12f77 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,6518 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + turbo: + specifier: ^2.5.4 + version: 2.8.20 + + apps/docs: + dependencies: + '@astrojs/starlight': + specifier: ^0.34.3 + version: 0.34.8(astro@5.18.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@6.0.2)(yaml@2.8.3)) + astro: + specifier: ^5.9.3 + version: 5.18.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@6.0.2)(yaml@2.8.3) + sharp: + specifier: ^0.33.5 + version: 0.33.5 + + apps/extension: + dependencies: + react: + specifier: ^19.2.4 + version: 19.2.4 + react-dom: + specifier: ^19.2.4 + version: 19.2.4(react@19.2.4) + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@crxjs/vite-plugin': + specifier: ^2.4.0 + version: 2.4.0(vite@8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + '@tailwindcss/vite': + specifier: ^4.2.2 + version: 4.2.2(vite@8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + '@types/chrome': + specifier: ^0.1.38 + version: 0.1.38 + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + autoprefixer: + specifier: ^10.4.27 + version: 10.4.27(postcss@8.5.8) + eslint: + specifier: ^10.1.0 + version: 10.1.0(jiti@2.6.1) + postcss: + specifier: ^8.5.8 + version: 8.5.8 + tailwindcss: + specifier: ^4.2.2 + version: 4.2.2 + typescript: + specifier: ^6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.3 + version: 8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3) + vitest: + specifier: ^4.1.2 + version: 4.1.2(@types/node@24.12.0)(vite@8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + + apps/web: + dependencies: + '@tailwindcss/vite': + specifier: ^4.2.2 + version: 4.2.2(vite@8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + astro: + specifier: ^5.9.3 + version: 5.18.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@6.0.2)(yaml@2.8.3) + tailwindcss: + specifier: ^4.2.2 + version: 4.2.2 + + packages/branding: {} + + packages/shared: {} + +packages: + + '@astrojs/compiler@2.13.1': + resolution: {integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==} + + '@astrojs/internal-helpers@0.7.6': + resolution: {integrity: sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q==} + + '@astrojs/markdown-remark@6.3.11': + resolution: {integrity: sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ==} + + '@astrojs/mdx@4.3.14': + resolution: {integrity: sha512-FBrqJQORVm+rkRa2TS5CjU9PBA6hkhrwLVBSS9A77gN2+iehvjq1w6yya/d0YKC7osiVorKkr3Qd9wNbl0ZkGA==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + peerDependencies: + astro: ^5.0.0 + + '@astrojs/prism@3.3.0': + resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/sitemap@3.7.2': + resolution: {integrity: sha512-PqkzkcZTb5ICiyIR8VoKbIAP/laNRXi5tw616N1Ckk+40oNB8Can1AzVV56lrbC5GKSZFCyJYUVYqVivMisvpA==} + + '@astrojs/starlight@0.34.8': + resolution: {integrity: sha512-XuYz0TfCZhje2u1Q9FNtmTdm7/B9QP91RDI1VkPgYvDhSYlME3k8gwgcBMHnR9ASDo2p9gskrqe7t1Pub/qryg==} + peerDependencies: + astro: ^5.5.0 + + '@astrojs/telemetry@3.3.0': + resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@capsizecss/unpack@4.0.0': + resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==} + engines: {node: '>=18'} + + '@crxjs/vite-plugin@2.4.0': + resolution: {integrity: sha512-bDLdq0W2V1SkMQDJjrcYyjK9/uKtdl4joT7GRImcootCjZdKRiRYt+cv9z8tJoU/tK3o1lX48LTqN7JMsk5AQg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@ctrl/tinycolor@4.2.0': + resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} + engines: {node: '>=14'} + + '@emnapi/core@1.9.1': + resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} + + '@emnapi/runtime@1.9.1': + resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} + + '@emnapi/wasi-threads@1.2.0': + resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.4': + resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.4': + resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.4': + resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.4': + resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.4': + resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.4': + resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.4': + resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.4': + resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.4': + resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.4': + resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.4': + resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.4': + resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.4': + resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.4': + resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.4': + resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.4': + resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.4': + resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.4': + resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.4': + resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.4': + resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.4': + resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.4': + resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.4': + resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.4': + resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.4': + resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.4': + resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.3': + resolution: {integrity: sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.5.3': + resolution: {integrity: sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.1.1': + resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/object-schema@3.0.3': + resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.6.1': + resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@expressive-code/core@0.41.7': + resolution: {integrity: sha512-ck92uZYZ9Wba2zxkiZLsZGi9N54pMSAVdrI9uW3Oo9AtLglD5RmrdTwbYPCT2S/jC36JGB2i+pnQtBm/Ib2+dg==} + + '@expressive-code/plugin-frames@0.41.7': + resolution: {integrity: sha512-diKtxjQw/979cTglRFaMCY/sR6hWF0kSMg8jsKLXaZBSfGS0I/Hoe7Qds3vVEgeoW+GHHQzMcwvgx/MOIXhrTA==} + + '@expressive-code/plugin-shiki@0.41.7': + resolution: {integrity: sha512-DL605bLrUOgqTdZ0Ot5MlTaWzppRkzzqzeGEu7ODnHF39IkEBbFdsC7pbl3LbUQ1DFtnfx6rD54k/cdofbW6KQ==} + + '@expressive-code/plugin-text-markers@0.41.7': + resolution: {integrity: sha512-Ewpwuc5t6eFdZmWlFyeuy3e1PTQC0jFvw2Q+2bpcWXbOZhPLsT7+h8lsSIJxb5mS7wZko7cKyQ2RLYDyK6Fpmw==} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + + '@oxc-project/types@0.122.0': + resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} + + '@pagefind/darwin-arm64@1.4.0': + resolution: {integrity: sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==} + cpu: [arm64] + os: [darwin] + + '@pagefind/darwin-x64@1.4.0': + resolution: {integrity: sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==} + cpu: [x64] + os: [darwin] + + '@pagefind/default-ui@1.4.0': + resolution: {integrity: sha512-wie82VWn3cnGEdIjh4YwNESyS1G6vRHwL6cNjy9CFgNnWW/PGRjsLq300xjVH5sfPFK3iK36UxvIBymtQIEiSQ==} + + '@pagefind/freebsd-x64@1.4.0': + resolution: {integrity: sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==} + cpu: [x64] + os: [freebsd] + + '@pagefind/linux-arm64@1.4.0': + resolution: {integrity: sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==} + cpu: [arm64] + os: [linux] + + '@pagefind/linux-x64@1.4.0': + resolution: {integrity: sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==} + cpu: [x64] + os: [linux] + + '@pagefind/windows-x64@1.4.0': + resolution: {integrity: sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==} + cpu: [x64] + os: [win32] + + '@rolldown/binding-android-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.12': + resolution: {integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': + resolution: {integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': + resolution: {integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': + resolution: {integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-rc.12': + resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} + + '@rollup/pluginutils@4.2.1': + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.60.0': + resolution: {integrity: sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.0': + resolution: {integrity: sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.0': + resolution: {integrity: sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.0': + resolution: {integrity: sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.0': + resolution: {integrity: sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.0': + resolution: {integrity: sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.0': + resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.0': + resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.0': + resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.0': + resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.0': + resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.0': + resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.0': + resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.0': + resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.0': + resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.0': + resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.0': + resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.0': + resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.0': + resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.0': + resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.0': + resolution: {integrity: sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.0': + resolution: {integrity: sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.0': + resolution: {integrity: sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.0': + resolution: {integrity: sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.0': + resolution: {integrity: sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==} + cpu: [x64] + os: [win32] + + '@shikijs/core@3.23.0': + resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} + + '@shikijs/engine-javascript@3.23.0': + resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} + + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} + + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tailwindcss/node@4.2.2': + resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} + + '@tailwindcss/oxide-android-arm64@4.2.2': + resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.2': + resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.2': + resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.2': + resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.2': + resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.2.2': + resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@turbo/darwin-64@2.8.20': + resolution: {integrity: sha512-FQ9EX1xMU5nbwjxXxM3yU88AQQ6Sqc6S44exPRroMcx9XZHqqppl5ymJF0Ig/z3nvQNwDmz1Gsnvxubo+nXWjQ==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.8.20': + resolution: {integrity: sha512-Gpyh9ATFGThD6/s9L95YWY54cizg/VRWl2B67h0yofG8BpHf67DFAh9nuJVKG7bY0+SBJDAo5cMur+wOl9YOYw==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.8.20': + resolution: {integrity: sha512-p2QxWUYyYUgUFG0b0kR+pPi8t7c9uaVlRtjTTI1AbCvVqkpjUfCcReBn6DgG/Hu8xrWdKLuyQFaLYFzQskZbcA==} + cpu: [x64] + os: [linux] + + '@turbo/linux-arm64@2.8.20': + resolution: {integrity: sha512-Gn5yjlZGLRZWarLWqdQzv0wMqyBNIdq1QLi48F1oY5Lo9kiohuf7BPQWtWxeNVS2NgJ1+nb/DzK1JduYC4AWOA==} + cpu: [arm64] + os: [linux] + + '@turbo/windows-64@2.8.20': + resolution: {integrity: sha512-vyaDpYk/8T6Qz5V/X+ihKvKFEZFUoC0oxYpC1sZanK6gaESJlmV3cMRT3Qhcg4D2VxvtC2Jjs9IRkrZGL+exLw==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.8.20': + resolution: {integrity: sha512-voicVULvUV5yaGXo0Iue13BcHGYW3u0VgqSbfQwBaHbpj1zLjYV4KIe+7fYIo6DO8FVUJzxFps3ODCQG/Wy2Qw==} + cpu: [arm64] + os: [win32] + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/chrome@0.1.38': + resolution: {integrity: sha512-5aK4m9wZqoWAoB98aElESLm/5pXpqJnFWMNoiCs/XdPsXR6wNdVkJFSdQ9Wr4PnTuUrxD0SuNuDHh3EG5QeBzA==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/filesystem@0.0.36': + resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} + + '@types/filewriter@0.0.33': + resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==} + + '@types/har-format@1.2.16': + resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/nlcst@2.0.3': + resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + + '@types/node@24.12.0': + resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@types/sax@1.2.7': + resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitest/expect@4.1.2': + resolution: {integrity: sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==} + + '@vitest/mocker@4.1.2': + resolution: {integrity: sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.2': + resolution: {integrity: sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==} + + '@vitest/runner@4.1.2': + resolution: {integrity: sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==} + + '@vitest/snapshot@4.1.2': + resolution: {integrity: sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==} + + '@vitest/spy@4.1.2': + resolution: {integrity: sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==} + + '@vitest/utils@4.1.2': + resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==} + + '@webcomponents/custom-elements@1.6.0': + resolution: {integrity: sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-iterate@2.0.1: + resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + astro-expressive-code@0.41.7: + resolution: {integrity: sha512-hUpogGc6DdAd+I7pPXsctyYPRBJDK7Q7d06s4cyP0Vz3OcbziP3FNzN0jZci1BpCvLn9675DvS7B9ctKKX64JQ==} + peerDependencies: + astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta + + astro@5.18.1: + resolution: {integrity: sha512-m4VWilWZ+Xt6NPoYzC4CgGZim/zQUO7WFL0RHCH0AiEavF1153iC3+me2atDvXpf/yX4PyGUeD8wZLq1cirT3g==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} + hasBin: true + + autoprefixer@10.4.27: + resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + base-64@1.0.0: + resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} + + baseline-browser-mapping@2.10.11: + resolution: {integrity: sha512-DAKrHphkJyiGuau/cFieRYhcTFeK/lBuD++C7cZ6KZHbMhBrisoi+EvhQ5RZrIfV5qwsW8kgQ07JIC+MDJRAhg==} + engines: {node: '>=6.0.0'} + hasBin: true + + bcp-47-match@2.0.3: + resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} + + bcp-47@2.1.0: + resolution: {integrity: sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + boxen@8.0.1: + resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} + engines: {node: '>=18'} + + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + camelcase@8.0.0: + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} + engines: {node: '>=16'} + + caniuse-lite@1.0.30001781: + resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + common-ancestor-path@1.0.1: + resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-selector-parser@3.3.0: + resolution: {integrity: sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + deterministic-object-hash@2.0.2: + resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} + engines: {node: '>=18'} + + devalue@5.6.4: + resolution: {integrity: sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + direction@2.0.1: + resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==} + hasBin: true + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dset@3.1.4: + resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} + engines: {node: '>=4'} + + electron-to-chromium@1.5.328: + resolution: {integrity: sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + enhanced-resolve@5.20.1: + resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-module-lexer@0.10.5: + resolution: {integrity: sha512-+7IwY/kiGAacQfY+YBhKMvEmyAJnw5grTUgjG85Pe7vcUI/6b7pZjZG8nQ7+48YhzEAEqrEgD2dCz/JIK+AYvw==} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.4: + resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.1.0: + resolution: {integrity: sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + expressive-code@0.41.7: + resolution: {integrity: sha512-2wZjC8OQ3TaVEMcBtYY4Va3lo6J+Ai9jf3d4dbhURMJcU4Pbqe6EcHe424MIZI0VHUA1bR6xdpoHYi3yxokWqA==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + flattie@1.1.1: + resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} + engines: {node: '>=8'} + + fontace@0.4.1: + resolution: {integrity: sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==} + + fontkitten@1.0.3: + resolution: {integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==} + engines: {node: '>=20'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + h3@1.15.10: + resolution: {integrity: sha512-YzJeWSkDZxAhvmp8dexjRK5hxziRO7I9m0N53WhvYL5NiWfkUkzssVzY9jvGu0HBoLFW6+duYmNSn6MaZBCCtg==} + + hast-util-embedded@3.0.0: + resolution: {integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==} + + hast-util-format@1.1.0: + resolution: {integrity: sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-has-property@3.0.0: + resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==} + + hast-util-is-body-ok-link@3.0.1: + resolution: {integrity: sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-minify-whitespace@1.0.1: + resolution: {integrity: sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-phrasing@3.0.1: + resolution: {integrity: sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-select@6.0.4: + resolution: {integrity: sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + html-whitespace-sensitive-tag-names@3.0.1: + resolution: {integrity: sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + i18next@23.16.8: + resolution: {integrity: sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + iron-webcrypto@1.2.1: + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + klona@2.0.6: + resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} + engines: {node: '>= 8'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + lru-cache@11.2.7: + resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} + engines: {node: 20 || >=22} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + mdast-util-definitions@6.0.0: + resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} + + mdast-util-directive@3.1.0: + resolution: {integrity: sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-directive@3.0.2: + resolution: {integrity: sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + neotraverse@0.6.18: + resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} + engines: {node: '>= 10'} + + nlcst-to-string@4.0.0: + resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-html-parser@7.1.0: + resolution: {integrity: sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ==} + + node-mock-http@1.0.4: + resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} + + node-releases@2.0.36: + resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@4.3.5: + resolution: {integrity: sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@6.2.0: + resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} + engines: {node: '>=18'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-queue@8.1.1: + resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==} + engines: {node: '>=18'} + + p-timeout@6.1.4: + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} + + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + + pagefind@1.4.0: + resolution: {integrity: sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==} + hasBin: true + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse-latin@7.0.0: + resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + piccolore@0.1.3: + resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react-refresh@0.13.0: + resolution: {integrity: sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==} + engines: {node: '>=0.10.0'} + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + rehype-expressive-code@0.41.7: + resolution: {integrity: sha512-25f8ZMSF1d9CMscX7Cft0TSQIqdwjce2gDOvQ+d/w0FovsMwrSt3ODP4P3Z7wO1jsIJ4eYyaDRnIR/27bd/EMQ==} + + rehype-format@5.0.1: + resolution: {integrity: sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==} + + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + + rehype@13.0.2: + resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==} + + remark-directive@3.0.1: + resolution: {integrity: sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-smartypants@3.0.2: + resolution: {integrity: sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==} + engines: {node: '>=16.0.0'} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + retext-latin@4.0.0: + resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} + + retext-smartypants@6.2.0: + resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} + + retext-stringify@4.0.0: + resolution: {integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==} + + retext@9.0.0: + resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rolldown@1.0.0-rc.12: + resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rollup@2.79.2: + resolution: {integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==} + engines: {node: '>=10.0.0'} + hasBin: true + + rollup@4.60.0: + resolution: {integrity: sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.5.7: + resolution: {integrity: sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==} + + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shiki@3.23.0: + resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + sitemap@9.0.1: + resolution: {integrity: sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==} + engines: {node: '>=20.19.5', npm: '>=10.8.2'} + hasBin: true + + smol-toml@1.6.1: + resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} + engines: {node: '>= 18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.0.0: + resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} + + stream-replace-string@2.0.0: + resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + svgo@4.0.1: + resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==} + engines: {node: '>=16'} + hasBin: true + + tailwindcss@4.2.2: + resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} + + tapable@2.3.2: + resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} + engines: {node: '>=6'} + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.0.4: + resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + turbo@2.8.20: + resolution: {integrity: sha512-Rb4qk5YT8RUwwdXtkLpkVhNEe/lor6+WV7S5tTlLpxSz6MjV5Qi8jGNn4gS6NAvrYGA/rNrE6YUQM85sCZUDbQ==} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typescript@6.0.2: + resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + ultrahtml@1.6.0: + resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} + + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unifont@0.7.4: + resolution: {integrity: sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-modify-children@4.0.0: + resolution: {integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-children@3.0.0: + resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unstorage@1.17.5: + resolution: {integrity: sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6 || ^7 || ^8 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1 || ^2 || ^3 + aws4fetch: ^1.0.20 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vite@8.0.3: + resolution: {integrity: sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.2: + resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + + vitest@4.1.2: + resolution: {integrity: sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.2 + '@vitest/browser-preview': 4.1.2 + '@vitest/browser-webdriverio': 4.1.2 + '@vitest/ui': 4.1.2 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + which-pm-runs@1.1.0: + resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} + engines: {node: '>=4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + widest-line@5.0.0: + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} + engines: {node: '>=18'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + xxhash-wasm@1.1.0: + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + + yaml@2.8.3: + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + yocto-spinner@0.2.3: + resolution: {integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==} + engines: {node: '>=18.19'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod-to-ts@1.2.0: + resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==} + peerDependencies: + typescript: ^4.9.4 || ^5.0.2 + zod: ^3 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@astrojs/compiler@2.13.1': {} + + '@astrojs/internal-helpers@0.7.6': {} + + '@astrojs/markdown-remark@6.3.11': + dependencies: + '@astrojs/internal-helpers': 0.7.6 + '@astrojs/prism': 3.3.0 + github-slugger: 2.0.0 + hast-util-from-html: 2.0.3 + hast-util-to-text: 4.0.2 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + mdast-util-definitions: 6.0.0 + rehype-raw: 7.0.0 + rehype-stringify: 10.0.1 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + remark-smartypants: 3.0.2 + shiki: 3.23.0 + smol-toml: 1.6.1 + unified: 11.0.5 + unist-util-remove-position: 5.0.0 + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/mdx@4.3.14(astro@5.18.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@6.0.2)(yaml@2.8.3))': + dependencies: + '@astrojs/markdown-remark': 6.3.11 + '@mdx-js/mdx': 3.1.1 + acorn: 8.16.0 + astro: 5.18.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@6.0.2)(yaml@2.8.3) + es-module-lexer: 1.7.0 + estree-util-visit: 2.0.0 + hast-util-to-html: 9.0.5 + piccolore: 0.1.3 + rehype-raw: 7.0.0 + remark-gfm: 4.0.1 + remark-smartypants: 3.0.2 + source-map: 0.7.6 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/prism@3.3.0': + dependencies: + prismjs: 1.30.0 + + '@astrojs/sitemap@3.7.2': + dependencies: + sitemap: 9.0.1 + stream-replace-string: 2.0.0 + zod: 4.3.6 + + '@astrojs/starlight@0.34.8(astro@5.18.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@6.0.2)(yaml@2.8.3))': + dependencies: + '@astrojs/markdown-remark': 6.3.11 + '@astrojs/mdx': 4.3.14(astro@5.18.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@6.0.2)(yaml@2.8.3)) + '@astrojs/sitemap': 3.7.2 + '@pagefind/default-ui': 1.4.0 + '@types/hast': 3.0.4 + '@types/js-yaml': 4.0.9 + '@types/mdast': 4.0.4 + astro: 5.18.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@6.0.2)(yaml@2.8.3) + astro-expressive-code: 0.41.7(astro@5.18.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@6.0.2)(yaml@2.8.3)) + bcp-47: 2.1.0 + hast-util-from-html: 2.0.3 + hast-util-select: 6.0.4 + hast-util-to-string: 3.0.1 + hastscript: 9.0.1 + i18next: 23.16.8 + js-yaml: 4.1.1 + klona: 2.0.6 + mdast-util-directive: 3.1.0 + mdast-util-to-markdown: 2.1.2 + mdast-util-to-string: 4.0.0 + pagefind: 1.4.0 + rehype: 13.0.2 + rehype-format: 5.0.1 + remark-directive: 3.0.1 + ultrahtml: 1.6.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/telemetry@3.3.0': + dependencies: + ci-info: 4.4.0 + debug: 4.4.3 + dlv: 1.1.3 + dset: 3.1.4 + is-docker: 3.0.0 + is-wsl: 3.1.1 + which-pm-runs: 1.1.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/runtime@7.29.2': {} + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@capsizecss/unpack@4.0.0': + dependencies: + fontkitten: 1.0.3 + + '@crxjs/vite-plugin@2.4.0(vite@8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3))': + dependencies: + '@rollup/pluginutils': 4.2.1 + '@webcomponents/custom-elements': 1.6.0 + acorn-walk: 8.3.5 + convert-source-map: 1.9.0 + debug: 4.4.3 + es-module-lexer: 0.10.5 + fast-glob: 3.3.3 + fs-extra: 10.1.0 + jsesc: 3.1.0 + magic-string: 0.30.21 + node-html-parser: 7.1.0 + pathe: 2.0.3 + picocolors: 1.1.1 + react-refresh: 0.13.0 + rollup: 2.79.2 + rxjs: 7.5.7 + vite: 8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3) + transitivePeerDependencies: + - supports-color + + '@ctrl/tinycolor@4.2.0': {} + + '@emnapi/core@1.9.1': + dependencies: + '@emnapi/wasi-threads': 1.2.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.9.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.4': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.4': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.4': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.4': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.4': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.4': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.4': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.4': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.4': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.4': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.4': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.4': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.4': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.4': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.4': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.4': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.4': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.4': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.4': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.4': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.4': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.4': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.4': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.4': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.4': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.4': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@10.1.0(jiti@2.6.1))': + dependencies: + eslint: 10.1.0(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.3': + dependencies: + '@eslint/object-schema': 3.0.3 + debug: 4.4.3 + minimatch: 10.2.4 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.5.3': + dependencies: + '@eslint/core': 1.1.1 + + '@eslint/core@1.1.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/object-schema@3.0.3': {} + + '@eslint/plugin-kit@0.6.1': + dependencies: + '@eslint/core': 1.1.1 + levn: 0.4.1 + + '@expressive-code/core@0.41.7': + dependencies: + '@ctrl/tinycolor': 4.2.0 + hast-util-select: 6.0.4 + hast-util-to-html: 9.0.5 + hast-util-to-text: 4.0.2 + hastscript: 9.0.1 + postcss: 8.5.8 + postcss-nested: 6.2.0(postcss@8.5.8) + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 + + '@expressive-code/plugin-frames@0.41.7': + dependencies: + '@expressive-code/core': 0.41.7 + + '@expressive-code/plugin-shiki@0.41.7': + dependencies: + '@expressive-code/core': 0.41.7 + shiki: 3.23.0 + + '@expressive-code/plugin-text-markers@0.41.7': + dependencies: + '@expressive-code/core': 0.41.7 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@img/colour@1.1.0': + optional: true + + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.9.1 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.9.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@mdx-js/mdx@3.1.1': + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 + acorn: 8.16.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.16.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@napi-rs/wasm-runtime@1.1.1': + dependencies: + '@emnapi/core': 1.9.1 + '@emnapi/runtime': 1.9.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@oslojs/encoding@1.1.0': {} + + '@oxc-project/types@0.122.0': {} + + '@pagefind/darwin-arm64@1.4.0': + optional: true + + '@pagefind/darwin-x64@1.4.0': + optional: true + + '@pagefind/default-ui@1.4.0': {} + + '@pagefind/freebsd-x64@1.4.0': + optional: true + + '@pagefind/linux-arm64@1.4.0': + optional: true + + '@pagefind/linux-x64@1.4.0': + optional: true + + '@pagefind/windows-x64@1.4.0': + optional: true + + '@rolldown/binding-android-arm64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.12': {} + + '@rollup/pluginutils@4.2.1': + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.2 + + '@rollup/pluginutils@5.3.0(rollup@4.60.0)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.4 + optionalDependencies: + rollup: 4.60.0 + + '@rollup/rollup-android-arm-eabi@4.60.0': + optional: true + + '@rollup/rollup-android-arm64@4.60.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.0': + optional: true + + '@rollup/rollup-darwin-x64@4.60.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.0': + optional: true + + '@shikijs/core@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.5 + + '@shikijs/engine-oniguruma@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/themes@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/types@3.23.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@standard-schema/spec@1.1.0': {} + + '@tailwindcss/node@4.2.2': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.20.1 + jiti: 2.6.1 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.2 + + '@tailwindcss/oxide-android-arm64@4.2.2': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.2': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.2': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + optional: true + + '@tailwindcss/oxide@4.2.2': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-x64': 4.2.2 + '@tailwindcss/oxide-freebsd-x64': 4.2.2 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-x64-musl': 4.2.2 + '@tailwindcss/oxide-wasm32-wasi': 4.2.2 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 + + '@tailwindcss/vite@4.2.2(vite@8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3))': + dependencies: + '@tailwindcss/node': 4.2.2 + '@tailwindcss/oxide': 4.2.2 + tailwindcss: 4.2.2 + vite: 8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3) + + '@turbo/darwin-64@2.8.20': + optional: true + + '@turbo/darwin-arm64@2.8.20': + optional: true + + '@turbo/linux-64@2.8.20': + optional: true + + '@turbo/linux-arm64@2.8.20': + optional: true + + '@turbo/windows-64@2.8.20': + optional: true + + '@turbo/windows-arm64@2.8.20': + optional: true + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/chrome@0.1.38': + dependencies: + '@types/filesystem': 0.0.36 + '@types/har-format': 1.2.16 + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/deep-eql@4.0.2': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + + '@types/estree@1.0.8': {} + + '@types/filesystem@0.0.36': + dependencies: + '@types/filewriter': 0.0.33 + + '@types/filewriter@0.0.33': {} + + '@types/har-format@1.2.16': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/js-yaml@4.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.13': {} + + '@types/ms@2.1.0': {} + + '@types/nlcst@2.0.3': + dependencies: + '@types/unist': 3.0.3 + + '@types/node@24.12.0': + dependencies: + undici-types: 7.16.0 + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@types/sax@1.2.7': + dependencies: + '@types/node': 24.12.0 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.0': {} + + '@vitest/expect@4.1.2': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.2 + '@vitest/utils': 4.1.2 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.2(vite@8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3))': + dependencies: + '@vitest/spy': 4.1.2 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3) + + '@vitest/pretty-format@4.1.2': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.2': + dependencies: + '@vitest/utils': 4.1.2 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.2': + dependencies: + '@vitest/pretty-format': 4.1.2 + '@vitest/utils': 4.1.2 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.2': {} + + '@vitest/utils@4.1.2': + dependencies: + '@vitest/pretty-format': 4.1.2 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + '@webcomponents/custom-elements@1.6.0': {} + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn-walk@8.3.5: + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@6.2.3: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-iterate@2.0.1: {} + + assertion-error@2.0.1: {} + + astring@1.9.0: {} + + astro-expressive-code@0.41.7(astro@5.18.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@6.0.2)(yaml@2.8.3)): + dependencies: + astro: 5.18.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@6.0.2)(yaml@2.8.3) + rehype-expressive-code: 0.41.7 + + astro@5.18.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.0)(typescript@6.0.2)(yaml@2.8.3): + dependencies: + '@astrojs/compiler': 2.13.1 + '@astrojs/internal-helpers': 0.7.6 + '@astrojs/markdown-remark': 6.3.11 + '@astrojs/telemetry': 3.3.0 + '@capsizecss/unpack': 4.0.0 + '@oslojs/encoding': 1.1.0 + '@rollup/pluginutils': 5.3.0(rollup@4.60.0) + acorn: 8.16.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + boxen: 8.0.1 + ci-info: 4.4.0 + clsx: 2.1.1 + common-ancestor-path: 1.0.1 + cookie: 1.1.1 + cssesc: 3.0.0 + debug: 4.4.3 + deterministic-object-hash: 2.0.2 + devalue: 5.6.4 + diff: 8.0.4 + dlv: 1.1.3 + dset: 3.1.4 + es-module-lexer: 1.7.0 + esbuild: 0.27.4 + estree-walker: 3.0.3 + flattie: 1.1.1 + fontace: 0.4.1 + github-slugger: 2.0.0 + html-escaper: 3.0.3 + http-cache-semantics: 4.2.0 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + magic-string: 0.30.21 + magicast: 0.5.2 + mrmime: 2.0.1 + neotraverse: 0.6.18 + p-limit: 6.2.0 + p-queue: 8.1.1 + package-manager-detector: 1.6.0 + piccolore: 0.1.3 + picomatch: 4.0.4 + prompts: 2.4.2 + rehype: 13.0.2 + semver: 7.7.4 + shiki: 3.23.0 + smol-toml: 1.6.1 + svgo: 4.0.1 + tinyexec: 1.0.4 + tinyglobby: 0.2.15 + tsconfck: 3.1.6(typescript@6.0.2) + ultrahtml: 1.6.0 + unifont: 0.7.4 + unist-util-visit: 5.1.0 + unstorage: 1.17.5 + vfile: 6.0.3 + vite: 6.4.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + vitefu: 1.1.2(vite@6.4.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)) + xxhash-wasm: 1.1.0 + yargs-parser: 21.1.1 + yocto-spinner: 0.2.3 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + zod-to-ts: 1.2.0(typescript@6.0.2)(zod@3.25.76) + optionalDependencies: + sharp: 0.34.5 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - db0 + - idb-keyval + - ioredis + - jiti + - less + - lightningcss + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - uploadthing + - yaml + + autoprefixer@10.4.27(postcss@8.5.8): + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001781 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + axobject-query@4.1.0: {} + + bail@2.0.2: {} + + balanced-match@4.0.4: {} + + base-64@1.0.0: {} + + baseline-browser-mapping@2.10.11: {} + + bcp-47-match@2.0.3: {} + + bcp-47@2.1.0: + dependencies: + is-alphabetical: 2.0.1 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + + boolbase@1.0.0: {} + + boxen@8.0.1: + dependencies: + ansi-align: 3.0.1 + camelcase: 8.0.0 + chalk: 5.6.2 + cli-boxes: 3.0.0 + string-width: 7.2.0 + type-fest: 4.41.0 + widest-line: 5.0.0 + wrap-ansi: 9.0.2 + + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.11 + caniuse-lite: 1.0.30001781 + electron-to-chromium: 1.5.328 + node-releases: 2.0.36 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + camelcase@8.0.0: {} + + caniuse-lite@1.0.30001781: {} + + ccount@2.0.1: {} + + chai@6.2.2: {} + + chalk@5.6.2: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + ci-info@4.4.0: {} + + cli-boxes@3.0.0: {} + + clsx@2.1.1: {} + + collapse-white-space@2.1.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + comma-separated-tokens@2.0.3: {} + + commander@11.1.0: {} + + common-ancestor-path@1.0.1: {} + + convert-source-map@1.9.0: {} + + convert-source-map@2.0.0: {} + + cookie-es@1.2.2: {} + + cookie@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-selector-parser@3.3.0: {} + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + + cssesc@3.0.0: {} + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + deep-is@0.1.4: {} + + defu@6.1.4: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + detect-libc@2.1.2: {} + + deterministic-object-hash@2.0.2: + dependencies: + base-64: 1.0.0 + + devalue@5.6.4: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff@8.0.4: {} + + direction@2.0.1: {} + + dlv@1.1.3: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dset@3.1.4: {} + + electron-to-chromium@1.5.328: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + enhanced-resolve@5.20.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.2 + + entities@4.5.0: {} + + entities@6.0.1: {} + + es-module-lexer@0.10.5: {} + + es-module-lexer@1.7.0: {} + + es-module-lexer@2.0.0: {} + + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.16.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.4: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.4 + '@esbuild/android-arm': 0.27.4 + '@esbuild/android-arm64': 0.27.4 + '@esbuild/android-x64': 0.27.4 + '@esbuild/darwin-arm64': 0.27.4 + '@esbuild/darwin-x64': 0.27.4 + '@esbuild/freebsd-arm64': 0.27.4 + '@esbuild/freebsd-x64': 0.27.4 + '@esbuild/linux-arm': 0.27.4 + '@esbuild/linux-arm64': 0.27.4 + '@esbuild/linux-ia32': 0.27.4 + '@esbuild/linux-loong64': 0.27.4 + '@esbuild/linux-mips64el': 0.27.4 + '@esbuild/linux-ppc64': 0.27.4 + '@esbuild/linux-riscv64': 0.27.4 + '@esbuild/linux-s390x': 0.27.4 + '@esbuild/linux-x64': 0.27.4 + '@esbuild/netbsd-arm64': 0.27.4 + '@esbuild/netbsd-x64': 0.27.4 + '@esbuild/openbsd-arm64': 0.27.4 + '@esbuild/openbsd-x64': 0.27.4 + '@esbuild/openharmony-arm64': 0.27.4 + '@esbuild/sunos-x64': 0.27.4 + '@esbuild/win32-arm64': 0.27.4 + '@esbuild/win32-ia32': 0.27.4 + '@esbuild/win32-x64': 0.27.4 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.1.0(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.3 + '@eslint/config-helpers': 0.5.3 + '@eslint/core': 1.1.1 + '@eslint/plugin-kit': 0.6.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.4 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.8 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + eventemitter3@5.0.4: {} + + expect-type@1.3.0: {} + + expressive-code@0.41.7: + dependencies: + '@expressive-code/core': 0.41.7 + '@expressive-code/plugin-frames': 0.41.7 + '@expressive-code/plugin-shiki': 0.41.7 + '@expressive-code/plugin-text-markers': 0.41.7 + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + flattie@1.1.1: {} + + fontace@0.4.1: + dependencies: + fontkitten: 1.0.3 + + fontkitten@1.0.3: + dependencies: + tiny-inflate: 1.0.3 + + fraction.js@5.3.4: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + get-east-asian-width@1.5.0: {} + + github-slugger@2.0.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + graceful-fs@4.2.11: {} + + h3@1.15.10: + dependencies: + cookie-es: 1.2.2 + crossws: 0.3.5 + defu: 6.1.4 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.4 + radix3: 1.1.2 + ufo: 1.6.3 + uncrypto: 0.1.3 + + hast-util-embedded@3.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-is-element: 3.0.0 + + hast-util-format@1.1.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-minify-whitespace: 1.0.1 + hast-util-phrasing: 3.0.1 + hast-util-whitespace: 3.0.0 + html-whitespace-sensitive-tag-names: 3.0.1 + unist-util-visit-parents: 6.0.2 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-has-property@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-is-body-ok-link@3.0.1: + dependencies: + '@types/hast': 3.0.4 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-minify-whitespace@1.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-is-element: 3.0.0 + hast-util-whitespace: 3.0.0 + unist-util-is: 6.0.1 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-phrasing@3.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-has-property: 3.0.0 + hast-util-is-body-ok-link: 3.0.1 + hast-util-is-element: 3.0.0 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-select@6.0.4: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + bcp-47-match: 2.0.3 + comma-separated-tokens: 2.0.3 + css-selector-parser: 3.3.0 + devlop: 1.1.0 + direction: 2.0.1 + hast-util-has-property: 3.0.0 + hast-util-to-string: 3.0.1 + hast-util-whitespace: 3.0.0 + nth-check: 2.1.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-string@3.0.1: + dependencies: + '@types/hast': 3.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + + he@1.2.0: {} + + html-escaper@3.0.3: {} + + html-void-elements@3.0.0: {} + + html-whitespace-sensitive-tag-names@3.0.1: {} + + http-cache-semantics@4.2.0: {} + + i18next@23.16.8: + dependencies: + '@babel/runtime': 7.29.2 + + ignore@5.3.2: {} + + import-meta-resolve@4.2.0: {} + + imurmurhash@0.1.4: {} + + inline-style-parser@0.2.7: {} + + iron-webcrypto@1.2.1: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-arrayish@0.3.4: {} + + is-decimal@2.0.1: {} + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hexadecimal@2.0.1: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-number@7.0.0: {} + + is-plain-obj@4.1.0: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + + jiti@2.6.1: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@3.0.3: {} + + klona@2.0.6: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + longest-streak@3.1.0: {} + + lru-cache@11.2.7: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.2: + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + markdown-extensions@2.0.0: {} + + markdown-table@3.0.4: {} + + mdast-util-definitions@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + mdast-util-directive@3.1.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-visit-parents: 6.0.2 + transitivePeerDependencies: + - supports-color + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.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.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdn-data@2.0.28: {} + + mdn-data@2.27.1: {} + + merge2@1.4.1: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-directive@3.0.2: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + parse-entities: 4.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.8 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.5 + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + neotraverse@0.6.18: {} + + nlcst-to-string@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + + node-fetch-native@1.6.7: {} + + node-html-parser@7.1.0: + dependencies: + css-select: 5.2.2 + he: 1.2.0 + + node-mock-http@1.0.4: {} + + node-releases@2.0.36: {} + + normalize-path@3.0.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + obug@2.1.1: {} + + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.3 + + ohash@2.0.11: {} + + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.5: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.1.0 + regex-recursion: 6.0.2 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-limit@6.2.0: + dependencies: + yocto-queue: 1.2.2 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-queue@8.1.1: + dependencies: + eventemitter3: 5.0.4 + p-timeout: 6.1.4 + + p-timeout@6.1.4: {} + + package-manager-detector@1.6.0: {} + + pagefind@1.4.0: + optionalDependencies: + '@pagefind/darwin-arm64': 1.4.0 + '@pagefind/darwin-x64': 1.4.0 + '@pagefind/freebsd-x64': 1.4.0 + '@pagefind/linux-arm64': 1.4.0 + '@pagefind/linux-x64': 1.4.0 + '@pagefind/windows-x64': 1.4.0 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-latin@7.0.0: + dependencies: + '@types/nlcst': 2.0.3 + '@types/unist': 3.0.3 + nlcst-to-string: 4.0.0 + unist-util-modify-children: 4.0.0 + unist-util-visit-children: 3.0.0 + vfile: 6.0.3 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + pathe@2.0.3: {} + + piccolore@0.1.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + postcss-nested@6.2.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prismjs@1.30.0: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + property-information@7.1.0: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + radix3@1.1.2: {} + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-refresh@0.13.0: {} + + react@19.2.4: {} + + readdirp@5.0.0: {} + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.8 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rehype-expressive-code@0.41.7: + dependencies: + expressive-code: 0.41.7 + + rehype-format@5.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-format: 1.1.0 + + rehype-parse@9.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + + rehype@13.0.2: + dependencies: + '@types/hast': 3.0.4 + rehype-parse: 9.0.1 + rehype-stringify: 10.0.1 + unified: 11.0.5 + + remark-directive@3.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-directive: 3.1.0 + micromark-extension-directive: 3.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-smartypants@3.0.2: + dependencies: + retext: 9.0.0 + retext-smartypants: 6.2.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + retext-latin@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + parse-latin: 7.0.0 + unified: 11.0.5 + + retext-smartypants@6.2.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unist-util-visit: 5.1.0 + + retext-stringify@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unified: 11.0.5 + + retext@9.0.0: + dependencies: + '@types/nlcst': 2.0.3 + retext-latin: 4.0.0 + retext-stringify: 4.0.0 + unified: 11.0.5 + + reusify@1.1.0: {} + + rolldown@1.0.0-rc.12: + dependencies: + '@oxc-project/types': 0.122.0 + '@rolldown/pluginutils': 1.0.0-rc.12 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-x64': 1.0.0-rc.12 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.12 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.12 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.12 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.12 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.12 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.12 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.12 + + rollup@2.79.2: + optionalDependencies: + fsevents: 2.3.3 + + rollup@4.60.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.0 + '@rollup/rollup-android-arm64': 4.60.0 + '@rollup/rollup-darwin-arm64': 4.60.0 + '@rollup/rollup-darwin-x64': 4.60.0 + '@rollup/rollup-freebsd-arm64': 4.60.0 + '@rollup/rollup-freebsd-x64': 4.60.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.0 + '@rollup/rollup-linux-arm-musleabihf': 4.60.0 + '@rollup/rollup-linux-arm64-gnu': 4.60.0 + '@rollup/rollup-linux-arm64-musl': 4.60.0 + '@rollup/rollup-linux-loong64-gnu': 4.60.0 + '@rollup/rollup-linux-loong64-musl': 4.60.0 + '@rollup/rollup-linux-ppc64-gnu': 4.60.0 + '@rollup/rollup-linux-ppc64-musl': 4.60.0 + '@rollup/rollup-linux-riscv64-gnu': 4.60.0 + '@rollup/rollup-linux-riscv64-musl': 4.60.0 + '@rollup/rollup-linux-s390x-gnu': 4.60.0 + '@rollup/rollup-linux-x64-gnu': 4.60.0 + '@rollup/rollup-linux-x64-musl': 4.60.0 + '@rollup/rollup-openbsd-x64': 4.60.0 + '@rollup/rollup-openharmony-arm64': 4.60.0 + '@rollup/rollup-win32-arm64-msvc': 4.60.0 + '@rollup/rollup-win32-ia32-msvc': 4.60.0 + '@rollup/rollup-win32-x64-gnu': 4.60.0 + '@rollup/rollup-win32-x64-msvc': 4.60.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.5.7: + dependencies: + tslib: 2.8.1 + + sax@1.6.0: {} + + scheduler@0.27.0: {} + + semver@7.7.4: {} + + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shiki@3.23.0: + dependencies: + '@shikijs/core': 3.23.0 + '@shikijs/engine-javascript': 3.23.0 + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + siginfo@2.0.0: {} + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + + sisteransi@1.0.5: {} + + sitemap@9.0.1: + dependencies: + '@types/node': 24.12.0 + '@types/sax': 1.2.7 + arg: 5.0.2 + sax: 1.6.0 + + smol-toml@1.6.1: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + + stackback@0.0.2: {} + + std-env@4.0.0: {} + + stream-replace-string@2.0.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + svgo@4.0.1: + dependencies: + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.2.1 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.6.0 + + tailwindcss@4.2.2: {} + + tapable@2.3.2: {} + + tiny-inflate@1.0.3: {} + + tinybench@2.9.0: {} + + tinyexec@1.0.4: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + tsconfck@3.1.6(typescript@6.0.2): + optionalDependencies: + typescript: 6.0.2 + + tslib@2.8.1: {} + + turbo@2.8.20: + optionalDependencies: + '@turbo/darwin-64': 2.8.20 + '@turbo/darwin-arm64': 2.8.20 + '@turbo/linux-64': 2.8.20 + '@turbo/linux-arm64': 2.8.20 + '@turbo/windows-64': 2.8.20 + '@turbo/windows-arm64': 2.8.20 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@4.41.0: {} + + typescript@6.0.2: {} + + ufo@1.6.3: {} + + ultrahtml@1.6.0: {} + + uncrypto@0.1.3: {} + + undici-types@7.16.0: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unifont@0.7.4: + dependencies: + css-tree: 3.2.1 + ofetch: 1.5.1 + ohash: 2.0.11 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-modify-children@4.0.0: + dependencies: + '@types/unist': 3.0.3 + array-iterate: 2.0.1 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-children@3.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + universalify@2.0.1: {} + + unstorage@1.17.5: + dependencies: + anymatch: 3.1.3 + chokidar: 5.0.0 + destr: 2.0.5 + h3: 1.15.10 + lru-cache: 11.2.7 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.3 + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@6.4.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.8 + rollup: 4.60.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.12.0 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.32.0 + yaml: 2.8.3 + + vite@8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.8 + rolldown: 1.0.0-rc.12 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.12.0 + esbuild: 0.27.4 + fsevents: 2.3.3 + jiti: 2.6.1 + yaml: 2.8.3 + + vitefu@1.1.2(vite@6.4.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)): + optionalDependencies: + vite: 6.4.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3) + + vitest@4.1.2(@types/node@24.12.0)(vite@8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)): + dependencies: + '@vitest/expect': 4.1.2 + '@vitest/mocker': 4.1.2(vite@8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.2 + '@vitest/runner': 4.1.2 + '@vitest/snapshot': 4.1.2 + '@vitest/spy': 4.1.2 + '@vitest/utils': 4.1.2 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.0.0 + tinybench: 2.9.0 + tinyexec: 1.0.4 + tinyglobby: 0.2.15 + tinyrainbow: 3.1.0 + vite: 8.0.3(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.12.0 + transitivePeerDependencies: + - msw + + web-namespaces@2.0.1: {} + + which-pm-runs@1.1.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + widest-line@5.0.0: + dependencies: + string-width: 7.2.0 + + word-wrap@1.2.5: {} + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + xxhash-wasm@1.1.0: {} + + yaml@2.8.3: + optional: true + + yargs-parser@21.1.1: {} + + yocto-queue@0.1.0: {} + + yocto-queue@1.2.2: {} + + yocto-spinner@0.2.3: + dependencies: + yoctocolors: 2.1.2 + + yoctocolors@2.1.2: {} + + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod-to-ts@1.2.0(typescript@6.0.2)(zod@3.25.76): + dependencies: + typescript: 6.0.2 + zod: 3.25.76 + + zod@3.25.76: {} + + zod@4.3.6: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..3ff5faa --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - "apps/*" + - "packages/*" diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..edfcb25 --- /dev/null +++ b/turbo.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "lint": {}, + "test": {} + } +} From 602a2f1b8be79f91d2d54272c81422653740264d Mon Sep 17 00:00:00 2001 From: Joshua Butner <53810+heyjawrsh@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:39:15 -0500 Subject: [PATCH 06/54] feat: add tab group locking and brand color theme (#4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduce LockedTabGroup type, storage key, and persistence helpers - Add queryTabGroups, getLockedTabIds, and resolveStaleLockedGroups utilities (including stale group re-matching by name+color) - Add get-locked-tab-groups, lock-tab-group, unlock-tab-group message actions and service worker handlers - Integrate locking into organize, apply, and undo flows — locked tabs are excluded from all AI-driven reorganization - Surface lock/unlock toggles in TabOrganizer idle state, with dormant lock management for groups no longer open - Replace hardcoded indigo-* colors with a custom brand color scale (cyan) and coal background token via Tailwind @theme - Update extension icons --- apps/extension/public/icons/icon-128.png | Bin 360 -> 3969 bytes apps/extension/public/icons/icon-16.png | Bin 82 -> 538 bytes apps/extension/public/icons/icon-48.png | Bin 157 -> 1549 bytes .../src/background/service-worker.ts | 140 +++++++++++- apps/extension/src/core/storage.ts | 15 +- apps/extension/src/core/tabs.ts | 38 ++++ apps/extension/src/shared/constants.ts | 1 + apps/extension/src/shared/messaging.ts | 3 + apps/extension/src/shared/types.ts | 7 + apps/extension/src/sidepanel/App.tsx | 2 +- .../src/sidepanel/pages/BookmarkOrganizer.tsx | 22 +- .../src/sidepanel/pages/Settings.tsx | 16 +- .../src/sidepanel/pages/TabOrganizer.tsx | 213 ++++++++++++++++-- apps/extension/src/styles/globals.css | 15 ++ 14 files changed, 426 insertions(+), 46 deletions(-) diff --git a/apps/extension/public/icons/icon-128.png b/apps/extension/public/icons/icon-128.png index 361fcbc3cc0e01adaf04371bbc65aa2d85358854..fc10f1fdbff40645beabfe71e6d5d8a688f22507 100644 GIT binary patch literal 3969 zcmb7H_cz;*_kK~ei`X+p6Dz*CXvJPpo8pZcZ?jb+RikRe ztWtY?l-8CdOzyHDKhkNgN&Uwx~_lM^^&$;myW=1y{`56HKxPdS>u=+Ej{w;di zKfM?3OacH_V}yaObx6T(fkQBd5qJM1dn9HQk+v~1tcj`x!l=h2Vff~lG_Is(>I^Ob z2Gj%1)QO%2ji~6pUgn5i)mxrXB2(w}aGxrhckOULf#RU6(alzZvE7u-RlBgwKfHgv zR!Ii_GP@a-H%b3$Lz(*Zt^XMkfayq$FrDCYHsNjax@jjz@S}?-#y6aje)nEI8H`x! zA|4IQfk97F_k#V_9LTHVF)`xkF*x7Q8qD?nv?QqkgPd zkxwTwo4b*`e(>Hbl{%gs2;5!?8e_+%R%Et`)GAz4^uAb&jQGV~FTj4%zq*&dHt_@Q z(c&4es?g(bIB~iaoMflkTIwGf9pF>Utas-o-fH^l;?NoTAP(qnN_MkLS9l^Vm;%MY1cy2UqV)mx8u!SP|=uXX|kY6qbkzO1D z?7X~FHX)if*g(IuwCz)3ALb`ipxJM-d`C&hHRARqc|$1yf&hk3yEm1@PC<6Jp@l^d zO%d@52dFq~{xYJUhx}P@OL3sX@x%N1Spy}094vltB-t!Zexkx){2+7tAF}VD?}+}5 z53F)$L4B~~cuaapZ-;LWl#)ijCbtjq*rt;(YOR-D$9(P7=%m~&W;f;rnlHDGcu*mD z55*r{98!mk4Vz4hSN8L&hH3g#- zpN`A)kIVHhw?rC};)#blZ0=uP%5fURr|$^N%m>Z7g4S&<;b#iG=vicDvAL0#2PSTx z`rQOdcj&sNrbg%dB}1g$zO7sl7nj;e*IF{0D&^n>czvZ&7}s5{Hzhlw@z}3u(QENM z^zn1TKSYG=M0=-AQq?=hLU_}Q;Bn^OX5o5vMJ=+cB?r(kElujF%05x=>WJAb6BIyy zn7;J$lk;ES?FrL-#mBaE?>tmT0d=dOgw^PWes;kAOse5sAPLrHf_X^IjgZp#E_ST^ zY?;Y1ly^noYbRL5F66JZr$4OE_H^D9$5aEnlHv5t0tDTfsks1O@-UJ45?g>J%e?$S z^N;NHuF`Jdc1aHXbjn)W9@`fX%~UtZU`Oh0-6L*&YnD@Ly!njafP0Av^FNG>f*xF{ z7uL+OPtw?a+K(w+6FuGv$ZEPLbVggcn{ln&P;_prgq;e>(Q@9z_A`jIZboW4k&Cu# zpp!V`-2%I>G#BebsWH~<;8fDR$5r=3hMIm{T;lFhRsnTvEfRDcYO~)|L zraDg;fP@BqSxTa?cmLPlCQVG=ATk-$9PWF3McS+`V^*{9+SoUxV{5CC@9D|zyWM`TI$%}1pT?&wH)#ys%bMnR4i-@yELgXr9bssLF*v&H?Jkb8 zdeyD4D5Zz6Xm=v*C9~acmj=y$CsGTTnE(3t#Q6*jNDIg)*;rBq$GJOKFw@t5D_EHX zfZ^?p9=6_2MUH})UScnG&U%)oho%#I9VCV|s~k)GPsnpT|C+`b~?n2~G~>)7xf z0+@+$s-l7&>|q_U1cPhg>$Hv)H#MnMnzdsuRBe(gS^dY1rt`zEi_941EcIXfmW+$p zg;L4$!w}UERC<_+NjctspAY_g8wQa5U_q+Ss)mPfz)XiP0j0i+*s1ChZJXuXhkvnsdD zIg+LG`}Iywfj`T7%H5oxXvUWBD<4Q+wGh0{@LXfSi;#ZWbqh)FjbY-j$UHnlVpBop zG6hZ9X-++uHM$BT;N;q(G6=5ue8xl#=!y=4zfD)<05SXm*QK-DvP_CnR<3z^tT!JMK^fG(|5*)LQWW zy+o8mqQ`nzFEnGE`nDNc5_!?O-~1Xxc-WeQ3BIYmh8kYTj2hXUT)EP2*nPWOSaGlfdm?zHOduo|U+}gj zmd~7aM~+DjoF!cfF{{La?>)Z(t=v^)uJcm+YTnxFZ;vzM`h|Kfm89DP%s2lQ3t2TVGu$ug%uW}LqP$0EjZAnqf=k z`Zu5EFefqodXhGR>?a)oT(0Zm+_(fFXQ8PfK-1kBbc2GH;&XQbX;E3H$yePZJWKR< zv@nz{HR-~}mWl(k^R21AKieXni*Vw{Bu3j#DI(dmQe8!0;k6D*E@9|@i^7wZea96 zoZ}n(c9={SLsXk0Ov;7BE9H|CAPqUvdSP;1{sBTi{BAyBYK4r%#{DV*z6^w5yMPqf%lad%O7C&~}*rF0oAnn%%gnhWKZAyPb3_l79nw{8k~zWd5jz{*H}-O02AvPb6=l)~7__$K|-k zf$I_qZVXZ0J_lYZgzIWxN87t}3PCYBuOV+C^^!enY{akf%~}`3J3$KRO^Pb9np9o{ z>~r;w@H#oRA4Q2Yg=Vt!iZ9sa@r(719j);jG+cYPI5?phGEsyXI~u1;8hbKJ-gmFtIDpuC_;Mv1Xcza-ud*6%P_n} zVO&mx?5mt@Y+{iJd1gR+ks$G5r@7Mm4Ou5|;9+>s$1esH^vCyGK@rhwEY1=0HnE>A zGH9am!{hRIUhy%br@YUbQv>S9u8icr4mo|~6Xtl-gjX7Nt1UIO@(s+m4NiTwFOq+( zlWI$|MaQvuB=5cw_}JeF*YvQ~)^%7Us3(!rRr>ZRjaniOP(SKQg6U z{csix>0x623r7HLDF6Tf literal 360 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrVAS_?aSW-L^Y)UVAcKOyfeqhJ z$^HvuFp`+1Slxc^*7HlI`6ax@=y85}Sb4q9e07Cp_)Bpeg diff --git a/apps/extension/public/icons/icon-16.png b/apps/extension/public/icons/icon-16.png index 9f3b82b0820983d74326d994f16106d5ffb8bc2c..344cd24e7f9c4a5dd6efd394443f3c91cc53a788 100644 GIT binary patch delta 512 zcmV+b0{{I|ngoy|e*xi1L_t(|oUKwzZWBQeebpTZNMH{;1DnnXVhej6`vjO11fL)v zu|-TyKsbTT32?*#V%9dYbAk*DV0MfH5}!jd^~rHUc4y zX)&QoUBv7M4Ub6f7{~)fNENb84Fq~DFlm6kin)q8kP!B$eJ73iR|o&&gapSigDBz@ z8PpCS;fUt}e>FXbAJWSGG9M}=fxWuL<{W>zOd)s%#Xh40u=_g~AW_8vhIh(BQ7 zV}3%pLxh@M-nlseu`*Z*RNt`bc*w;k29bA?F8Pe&LaV1eZ$Jvd4~^;Pu1M&snlUyu zGL)r@9jdi0HHmrDM9%oLrb4^i%28bV%3vL=ZSWJpWbTzCS*`8>0000FVdQ I&MBb@0P`;q=l}o! diff --git a/apps/extension/public/icons/icon-48.png b/apps/extension/public/icons/icon-48.png index abb0a403ae6443c1d6c48e1c79ddb3fdfa0da8ac..d2f8839f189bdd96815a3c2b8b94c3cdf68a15e2 100644 GIT binary patch delta 1532 zcmV0gVihBYy?dNklgNwz&)7w)y`B@Wb%;-mm?~Da@~q?(mOz)c-CcgMUT<-Q&p{jJ_t42?>$3 zrXqcC@5xct7h^pBPwD=0u}X3}ASnk#0vGL%mfM?T&ZJdJD6LAUS>*wHLV>EVVq(Ut%<& z=>C!Y;(4$fD1ZNJ6^ZFdYP}z=JV{jYhInU8Hj=hSEA2OA;S7N3o{Td*ULYIeMbA#g zsU)!k6UZ!z?roU+=h^XiC>p zl9(RCY!W?KK86V=H$@XLI1D$RYNQ9h--a(~P%VzNOqK@f<}FOvi^S%U$Ym@TtH z5dD0<+}?ue^{FGz@XnY(VBVQe*@|_`HBbR!ej?6o z_%!!d-th5Mo)949Rh$b&08GF+gL9vqy{)cRDZQs07%dUidWUMQ!>prxIH_KFsiYDR zxPNBYJ$;5MSDCfTlpK|sWv%dWXZBi^n)!I-r2-^{6@dH2yjf-RupfLeDupU6!XTHA zF!zc_*V-qT1X-Rxq`=?Q@yn#_$y{3OmlS`xfU!ENQpzWTkE&GF68+uE@bSq@t$_@h z0Tj`@Wul|^?H8_;ZmXt`FwP5~uC8?<0w9R7OJ2Ie!E7LKc_}5}Xn(8M z0hQ9d`iqibwN0B~0Q>_4KO2WIZ;qi^f9IKxmLp6j!2p;*TMba}7{NUi0E;Wm1(;so z6Gly*!L>~;P+~kw$n085KK!@1BM!tNqkU+;2hA_Rc2G}(4}ohgzP4JR+n?Sq2uN#| z+a?yW(D?bOd_ocx>Rf;shePgJ(a%amla&WBxXak4xB%dx9O`veo?=wvPg zt{~=#>H;#TPG1DU)gWNDO`#4zn8iuYJ}fWasXRlLfm48B0N0uX;3c)q<_ucx3sPLK zIaD8rJ_If=;S$#|+a0ohN+e=3s8uTbDN;jA6s8kz09@+O(Qji0fUn6ERexK8X;A%& zx={c|;J9tz55Ul{#Xc6Zj%RJy1;Un)V}bVPKvP2a&s#|;Qof2Cn|=L(XoI<%xE4@S z_U6yii}gz5<(Qozw9o^oe_E=xNd~;XYUc~~2(|7fzN(uQHgN~w&@fgpA<^2>8&S)| zr%_XazRC3-gG@f+tmt7#jdjT_(t*uzw^f0wMm4cK!{Y>*NupLOwHHuUDc{3OWAdQB zfqK63Ge}m3b79kNmwaYSr}+S~8c3Yj80uiI4*snnaQ$Og2DWr;=K0KfK94U7;A`M6 ifZOISfZOJq58!XvCJPDkD`Has0000pP+&M7`^ZIsv`?+%=BX5QSv($wKHj@QpG53@)_6Rp+p3RB00oupl>FVdQ&MBb@ E0F2Nsg8%>k diff --git a/apps/extension/src/background/service-worker.ts b/apps/extension/src/background/service-worker.ts index f3d347f..e84123a 100644 --- a/apps/extension/src/background/service-worker.ts +++ b/apps/extension/src/background/service-worker.ts @@ -4,6 +4,7 @@ import type { TabInfo, TabOrganizationResult, TabSnapshot, + LockedTabGroup, BookmarkInfo, BookmarkSnapshot, BookmarkOrganizationResult, @@ -17,9 +18,12 @@ import { setSessionData, getSessionData, clearSessionData, + getLockedTabGroups, + saveLockedTabGroups, } from "@/core/storage"; import { queryAllTabs, + queryTabGroups, createTabGroup, ungroupTabs, closeTabs, @@ -27,6 +31,8 @@ import { findStaleTabs, groupByDomain, domainToTabGroups, + getLockedTabIds, + resolveStaleLockedGroups, } from "@/core/tabs"; import { getBookmarkTree, @@ -104,6 +110,19 @@ async function handleMessage(message: Message): Promise { case "cleanup-empty-folders": return await handleCleanupEmptyFolders(); + case "get-locked-tab-groups": + return await handleGetLockedTabGroups(); + + case "lock-tab-group": + return await handleLockTabGroup( + message.payload as { chromeGroupId: number }, + ); + + case "unlock-tab-group": + return await handleUnlockTabGroup( + message.payload as { chromeGroupId: number }, + ); + default: return { success: false, error: `Unknown action: ${message.action}` }; } @@ -115,15 +134,22 @@ async function handleOrganizeTabs(): Promise< MessageResponse > { const settings = await getSettings(); - const tabs = await queryAllTabs(); + const allTabs = await queryAllTabs(); - const snapshot: TabSnapshot[] = tabs.map((t) => ({ + const snapshot: TabSnapshot[] = allTabs.map((t) => ({ id: t.id, groupId: t.groupId, windowId: t.windowId, })); await setSessionData(STORAGE_KEYS.TAB_SNAPSHOT, snapshot); + // Filter out tabs in locked groups + const windowId = allTabs[0]?.windowId; + const lockedGroups = + windowId !== undefined ? await resolveLockedGroups(windowId) : []; + const lockedTabIds = getLockedTabIds(lockedGroups, allTabs); + const tabs = allTabs.filter((t) => !lockedTabIds.has(t.id)); + let result: TabOrganizationResult; if (settings.aiTier === "secure") { @@ -146,6 +172,10 @@ async function handleOrganizeTabs(): Promise< } } + if (lockedGroups.length > 0) { + result.reasoning = `${lockedGroups.length} locked group(s) excluded. ${result.reasoning ?? ""}`; + } + return { success: true, data: result }; } @@ -158,6 +188,10 @@ async function handleApplyTabSuggestions( return { success: false, error: "No window found" }; } + // Resolve locked groups and build protected tab ID set + const lockedGroups = await resolveLockedGroups(windowId); + const lockedTabIds = getLockedTabIds(lockedGroups, tabs); + const snapshot: TabSnapshot[] = tabs.map((t) => ({ id: t.id, groupId: t.groupId, @@ -166,16 +200,16 @@ async function handleApplyTabSuggestions( await setSessionData(STORAGE_KEYS.TAB_SNAPSHOT, snapshot); for (const group of result.groups) { - const validTabIds = group.tabIds.filter((id) => - tabs.some((t) => t.id === id), + const validTabIds = group.tabIds.filter( + (id) => !lockedTabIds.has(id) && tabs.some((t) => t.id === id), ); if (validTabIds.length === 0) continue; await createTabGroup({ ...group, tabIds: validTabIds }, windowId); } if (result.stale.length > 0) { - const validStale = result.stale.filter((id) => - tabs.some((t) => t.id === id), + const validStale = result.stale.filter( + (id) => !lockedTabIds.has(id) && tabs.some((t) => t.id === id), ); if (validStale.length > 0) await closeTabs(validStale); } @@ -184,7 +218,9 @@ async function handleApplyTabSuggestions( if (dupSet.length > 1) { const validDups = dupSet .slice(1) - .filter((id) => tabs.some((t) => t.id === id)); + .filter( + (id) => !lockedTabIds.has(id) && tabs.some((t) => t.id === id), + ); if (validDups.length > 0) await closeTabs(validDups); } } @@ -201,17 +237,25 @@ async function handleUndoTabChanges(): Promise { } const currentTabs = await queryAllTabs(); + const windowId = currentTabs[0]?.windowId; + + // Resolve locked groups — their tabs must not be ungrouped or moved + const lockedGroups = + windowId !== undefined ? await resolveLockedGroups(windowId) : []; + const lockedTabIds = getLockedTabIds(lockedGroups, currentTabs); + // Ungroup all non-locked grouped tabs const groupedTabIds = currentTabs - .filter((t) => t.groupId !== -1) + .filter((t) => t.groupId !== -1 && !lockedTabIds.has(t.id)) .map((t) => t.id); if (groupedTabIds.length > 0) { await ungroupTabs(groupedTabIds); } + // Re-group non-locked tabs from snapshot const groupMap = new Map(); for (const entry of snapshot) { - if (entry.groupId !== -1) { + if (entry.groupId !== -1 && !lockedTabIds.has(entry.id)) { const existing = groupMap.get(entry.groupId); if (existing) { existing.push(entry.id); @@ -221,7 +265,6 @@ async function handleUndoTabChanges(): Promise { } } - const windowId = currentTabs[0]?.windowId; if (windowId !== undefined) { for (const tabIds of groupMap.values()) { const validIds = tabIds.filter((id) => @@ -258,6 +301,83 @@ function ruleBasedOrganize( }; } +// ─── Tab Group Locking ────────────────────────────────────────────── + +async function resolveLockedGroups( + windowId: number, +): Promise { + const stored = await getLockedTabGroups(); + if (stored.length === 0) return []; + + const liveGroups = await queryTabGroups(windowId); + const { resolved, changed } = resolveStaleLockedGroups(stored, liveGroups); + if (changed) await saveLockedTabGroups(resolved); + return resolved; +} + +async function handleGetLockedTabGroups(): Promise< + MessageResponse<{ locked: LockedTabGroup[]; dormant: LockedTabGroup[] }> +> { + const tabs = await queryAllTabs(); + const windowId = tabs[0]?.windowId; + if (windowId === undefined) { + return { success: true, data: { locked: [], dormant: [] } }; + } + + const stored = await getLockedTabGroups(); + if (stored.length === 0) { + return { success: true, data: { locked: [], dormant: [] } }; + } + + const liveGroups = await queryTabGroups(windowId); + const { resolved, changed } = resolveStaleLockedGroups(stored, liveGroups); + if (changed) await saveLockedTabGroups(resolved); + + const liveIds = new Set(liveGroups.map((g) => g.id)); + const locked = resolved.filter((g) => liveIds.has(g.chromeGroupId)); + const dormant = resolved.filter((g) => !liveIds.has(g.chromeGroupId)); + + return { success: true, data: { locked, dormant } }; +} + +async function handleLockTabGroup( + payload: { chromeGroupId: number }, +): Promise { + try { + const group = await chrome.tabGroups.get(payload.chromeGroupId); + const stored = await getLockedTabGroups(); + + // Don't double-lock + if (stored.some((g) => g.chromeGroupId === payload.chromeGroupId)) { + return { success: true }; + } + + const entry: LockedTabGroup = { + chromeGroupId: payload.chromeGroupId, + name: group.title ?? "", + color: group.color as LockedTabGroup["color"], + lockedAt: Date.now(), + }; + await saveLockedTabGroups([...stored, entry]); + return { success: true }; + } catch (err) { + return { + success: false, + error: `Failed to lock group: ${err instanceof Error ? err.message : String(err)}`, + }; + } +} + +async function handleUnlockTabGroup( + payload: { chromeGroupId: number }, +): Promise { + const stored = await getLockedTabGroups(); + await saveLockedTabGroups( + stored.filter((g) => g.chromeGroupId !== payload.chromeGroupId), + ); + return { success: true }; +} + // ─── Bookmark Organization ────────────────────────────────────────── interface BookmarkOrganizeResponse { diff --git a/apps/extension/src/core/storage.ts b/apps/extension/src/core/storage.ts index 04d9e78..c950490 100644 --- a/apps/extension/src/core/storage.ts +++ b/apps/extension/src/core/storage.ts @@ -1,5 +1,5 @@ import { STORAGE_KEYS } from "@/shared/constants"; -import { DEFAULT_SETTINGS, type Settings } from "@/shared/types"; +import { DEFAULT_SETTINGS, type Settings, type LockedTabGroup } from "@/shared/types"; export async function getSettings(): Promise { const result = await chrome.storage.local.get(STORAGE_KEYS.SETTINGS); @@ -31,3 +31,16 @@ export async function setSessionData( export async function clearSessionData(key: string): Promise { await chrome.storage.session.remove(key); } + +export async function getLockedTabGroups(): Promise { + const result = await chrome.storage.local.get(STORAGE_KEYS.LOCKED_TAB_GROUPS); + return (result[STORAGE_KEYS.LOCKED_TAB_GROUPS] as LockedTabGroup[]) ?? []; +} + +export async function saveLockedTabGroups( + groups: LockedTabGroup[], +): Promise { + await chrome.storage.local.set({ + [STORAGE_KEYS.LOCKED_TAB_GROUPS]: groups, + }); +} diff --git a/apps/extension/src/core/tabs.ts b/apps/extension/src/core/tabs.ts index bdf7acf..2243600 100644 --- a/apps/extension/src/core/tabs.ts +++ b/apps/extension/src/core/tabs.ts @@ -2,6 +2,7 @@ import type { TabInfo, TabGroupColor, TabGroupSuggestion, + LockedTabGroup, } from "@/shared/types"; export async function queryAllTabs(): Promise { @@ -19,6 +20,43 @@ export async function queryAllTabs(): Promise { })); } +export async function queryTabGroups( + windowId: number, +): Promise { + return chrome.tabGroups.query({ windowId }); +} + +export function getLockedTabIds( + lockedGroups: LockedTabGroup[], + tabs: TabInfo[], +): Set { + const lockedGroupIds = new Set(lockedGroups.map((g) => g.chromeGroupId)); + return new Set( + tabs.filter((t) => lockedGroupIds.has(t.groupId)).map((t) => t.id), + ); +} + +export function resolveStaleLockedGroups( + lockedGroups: LockedTabGroup[], + liveGroups: chrome.tabGroups.TabGroup[], +): { resolved: LockedTabGroup[]; changed: boolean } { + const liveIds = new Set(liveGroups.map((g) => g.id)); + let changed = false; + const resolved = lockedGroups.map((locked) => { + if (liveIds.has(locked.chromeGroupId)) return locked; + // Try to match by name + color + const match = liveGroups.find( + (g) => g.title === locked.name && g.color === locked.color, + ); + if (match) { + changed = true; + return { ...locked, chromeGroupId: match.id }; + } + return locked; + }); + return { resolved, changed }; +} + export async function createTabGroup( suggestion: TabGroupSuggestion, windowId: number, diff --git a/apps/extension/src/shared/constants.ts b/apps/extension/src/shared/constants.ts index a9061d0..97ea994 100644 --- a/apps/extension/src/shared/constants.ts +++ b/apps/extension/src/shared/constants.ts @@ -2,6 +2,7 @@ export const STORAGE_KEYS = { SETTINGS: "vxrtx_settings", TAB_SNAPSHOT: "vxrtx_tab_snapshot", BOOKMARK_SNAPSHOT: "vxrtx_bookmark_snapshot", + LOCKED_TAB_GROUPS: "vxrtx_locked_tab_groups", } as const; export const TAB_GROUP_COLORS: readonly string[] = [ diff --git a/apps/extension/src/shared/messaging.ts b/apps/extension/src/shared/messaging.ts index 05d3636..4000652 100644 --- a/apps/extension/src/shared/messaging.ts +++ b/apps/extension/src/shared/messaging.ts @@ -8,6 +8,9 @@ export type MessageAction = | "apply-bookmark-suggestions" | "undo-bookmark-changes" | "cleanup-empty-folders" + | "get-locked-tab-groups" + | "lock-tab-group" + | "unlock-tab-group" | "get-settings" | "save-settings"; diff --git a/apps/extension/src/shared/types.ts b/apps/extension/src/shared/types.ts index 6dcfc3b..6eafda0 100644 --- a/apps/extension/src/shared/types.ts +++ b/apps/extension/src/shared/types.ts @@ -63,6 +63,13 @@ export interface TabSnapshot { windowId: number; } +export interface LockedTabGroup { + chromeGroupId: number; + name: string; + color: TabGroupColor; + lockedAt: number; +} + export interface BookmarkInfo { id: string; title: string; diff --git a/apps/extension/src/sidepanel/App.tsx b/apps/extension/src/sidepanel/App.tsx index af0edad..8aec0ba 100644 --- a/apps/extension/src/sidepanel/App.tsx +++ b/apps/extension/src/sidepanel/App.tsx @@ -23,7 +23,7 @@ export function App() { onClick={() => setPage(item.id)} className={`flex-1 px-4 py-3 text-sm font-medium transition-colors ${ page === item.id - ? "border-b-2 border-indigo-500 text-indigo-400" + ? "border-b-2 border-brand-400 text-brand-400" : "text-zinc-400 hover:text-zinc-200" }`} > diff --git a/apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx b/apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx index d5a9041..124774c 100644 --- a/apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx +++ b/apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx @@ -186,7 +186,7 @@ function OrganizeMode({ onBack }: { onBack: () => void }) {

@@ -238,7 +238,7 @@ function OrganizeMode({ onBack }: { onBack: () => void }) { return next; }); }} - className="h-3.5 w-3.5 accent-indigo-500" + className="h-3.5 w-3.5 accent-brand-400" /> {folder.name} @@ -271,7 +271,7 @@ function OrganizeMode({ onBack }: { onBack: () => void }) { @@ -416,7 +416,7 @@ function LocateMode({ onBack }: { onBack: () => void }) { value={search} onChange={(e) => setSearch(e.target.value)} placeholder="Search bookmarks..." - className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-indigo-500 focus:outline-none" + className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-brand-400 focus:outline-none" />
{filtered.slice(0, 50).map((bm) => ( @@ -483,13 +483,13 @@ function LocateMode({ onBack }: { onBack: () => void }) { @@ -665,7 +665,7 @@ function DuplicatesMode({ onBack }: { onBack: () => void }) { type="checkbox" checked={removals.has(bm.id)} onChange={() => toggleRemoval(bm.id)} - className="h-3.5 w-3.5 shrink-0 accent-indigo-500" + className="h-3.5 w-3.5 shrink-0 accent-brand-400" /> )}
@@ -697,7 +697,7 @@ function DuplicatesMode({ onBack }: { onBack: () => void }) { @@ -828,6 +828,6 @@ function CleanupMode({ onBack }: { onBack: () => void }) { function Spinner() { return ( -
+
); } diff --git a/apps/extension/src/sidepanel/pages/Settings.tsx b/apps/extension/src/sidepanel/pages/Settings.tsx index 8c51017..af313d8 100644 --- a/apps/extension/src/sidepanel/pages/Settings.tsx +++ b/apps/extension/src/sidepanel/pages/Settings.tsx @@ -104,7 +104,7 @@ export function Settings() { onClick={() => save({ aiTier: tier.id })} className={`w-full rounded-lg border p-3 text-left transition-colors ${ settings.aiTier === tier.id - ? "border-indigo-500 bg-indigo-950/30" + ? "border-brand-400 bg-brand-950/30" : "border-zinc-800 bg-zinc-900 hover:border-zinc-700" }`} > @@ -128,7 +128,7 @@ export function Settings() { onClick={() => save({ aiModelProvider: provider.id })} className={`w-full rounded-lg border p-2.5 text-left transition-colors ${ settings.aiModelProvider === provider.id - ? "border-indigo-500 bg-indigo-950/30" + ? "border-brand-400 bg-brand-950/30" : "border-zinc-800 bg-zinc-900 hover:border-zinc-700" }`} > @@ -161,7 +161,7 @@ export function Settings() { value={settings.openrouterApiKey} onChange={(e) => save({ openrouterApiKey: e.target.value })} placeholder="sk-or-..." - className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-indigo-500 focus:outline-none" + className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-brand-400 focus:outline-none" />
@@ -175,7 +175,7 @@ export function Settings() { onClick={() => save({ openrouterModel: model.id })} className={`w-full rounded-md border px-3 py-1.5 text-left text-xs transition-colors ${ settings.openrouterModel === model.id - ? "border-indigo-500 bg-indigo-950/30 text-indigo-400" + ? "border-brand-400 bg-brand-950/30 text-brand-400" : "border-zinc-800 bg-zinc-900 text-zinc-400 hover:border-zinc-700" }`} > @@ -196,7 +196,7 @@ export function Settings() { value={settings.openrouterModel} onChange={(e) => save({ openrouterModel: e.target.value })} placeholder="provider/model-name" - className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-indigo-500 focus:outline-none" + className="w-full rounded-lg border border-zinc-800 bg-zinc-900 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-brand-400 focus:outline-none" />
diff --git a/apps/extension/src/sidepanel/pages/TabOrganizer.tsx b/apps/extension/src/sidepanel/pages/TabOrganizer.tsx index ff88d8b..7dc46c2 100644 --- a/apps/extension/src/sidepanel/pages/TabOrganizer.tsx +++ b/apps/extension/src/sidepanel/pages/TabOrganizer.tsx @@ -1,10 +1,11 @@ -import { useState, useCallback } from "react"; +import { useState, useCallback, useEffect } from "react"; import { sendMessage } from "@/shared/messaging"; import type { TabOrganizationResult, TabGroupSuggestion, TabGroupColor, TabInfo, + LockedTabGroup, } from "@/shared/types"; type Status = "idle" | "loading" | "preview" | "applying" | "done"; @@ -22,6 +23,13 @@ interface PreviewState { allDuplicates: number[][]; } +interface ChromeGroup { + id: number; + title: string; + color: TabGroupColor; + tabCount: number; +} + const GROUP_COLORS: TabGroupColor[] = [ "blue", "red", @@ -40,6 +48,90 @@ export function TabOrganizer() { const [error, setError] = useState(null); const [undoAvailable, setUndoAvailable] = useState(false); + // Lock state + const [chromeGroups, setChromeGroups] = useState([]); + const [lockedIds, setLockedIds] = useState>(new Set()); + const [dormantLocks, setDormantLocks] = useState([]); + + // Load current Chrome groups + locked state on mount and when returning to idle + useEffect(() => { + if (status === "idle") loadGroupsAndLocks(); + }, [status]); + + async function loadGroupsAndLocks() { + try { + // Get current tabs to find windowId and count tabs per group + const tabs = await chrome.tabs.query({ currentWindow: true }); + const windowId = tabs[0]?.windowId; + if (windowId === undefined) return; + + const groups = await chrome.tabGroups.query({ windowId }); + const tabCountByGroup = new Map(); + for (const tab of tabs) { + if (tab.groupId !== undefined && tab.groupId !== -1) { + tabCountByGroup.set( + tab.groupId, + (tabCountByGroup.get(tab.groupId) ?? 0) + 1, + ); + } + } + + setChromeGroups( + groups.map((g) => ({ + id: g.id, + title: g.title ?? "", + color: g.color as TabGroupColor, + tabCount: tabCountByGroup.get(g.id) ?? 0, + })), + ); + + // Load locked groups + const lockRes = await sendMessage< + void, + { locked: LockedTabGroup[]; dormant: LockedTabGroup[] } + >("get-locked-tab-groups"); + if (lockRes.success && lockRes.data) { + setLockedIds( + new Set(lockRes.data.locked.map((g) => g.chromeGroupId)), + ); + setDormantLocks(lockRes.data.dormant); + } + } catch { + // Side panel may not have access yet + } + } + + async function handleLock(groupId: number) { + const res = await sendMessage("lock-tab-group", { + chromeGroupId: groupId, + }); + if (res.success) { + setLockedIds((prev) => new Set([...prev, groupId])); + } + } + + async function handleUnlock(groupId: number) { + const res = await sendMessage("unlock-tab-group", { + chromeGroupId: groupId, + }); + if (res.success) { + setLockedIds((prev) => { + const next = new Set(prev); + next.delete(groupId); + return next; + }); + } + } + + async function handleRemoveDormantLock(lock: LockedTabGroup) { + await sendMessage("unlock-tab-group", { + chromeGroupId: lock.chromeGroupId, + }); + setDormantLocks((prev) => + prev.filter((l) => l.chromeGroupId !== lock.chromeGroupId), + ); + } + async function handleOrganize() { setStatus("loading"); setError(null); @@ -83,7 +175,9 @@ export function TabOrganizer() { .map(({ enabled: _, ...g }) => g), stale: Array.from(preview.staleEnabled), duplicates: preview.allDuplicates - .map((set) => set.filter((id) => preview.duplicatesEnabled.has(id))) + .map((set) => + set.filter((id) => preview.duplicatesEnabled.has(id)), + ) .filter((set) => set.length > 0) .map((toClose) => [0, ...toClose]), reasoning: preview.reasoning, @@ -214,7 +308,7 @@ export function TabOrganizer() { {status === "idle" && ( @@ -227,6 +321,101 @@ export function TabOrganizer() {
)} + {/* Idle state: show current groups with lock toggles */} + {status === "idle" && chromeGroups.length > 0 && ( +
+

+ Tab Groups + {lockedIds.size > 0 && ( + + ({lockedIds.size} locked) + + )} +

+ {chromeGroups.map((group) => { + const isLocked = lockedIds.has(group.id); + return ( +
+
+ + {group.title || "Untitled"} + + + {group.tabCount} tab{group.tabCount !== 1 ? "s" : ""} + + +
+ ); + })} + + {/* Dormant locks */} + {dormantLocks.length > 0 && ( +
+

+ Dormant locks (group no longer open): +

+ {dormantLocks.map((lock) => ( +
+
+ + {lock.name || "Untitled"} + + +
+ ))} +
+ )} +
+ )} + + {status === "idle" && !error && chromeGroups.length === 0 && ( +

+ Click "Organize Tabs" to analyze and group your open tabs. +

+ )} + + {status === "idle" && !error && chromeGroups.length > 0 && ( +

+ Locked groups are excluded from all organization. Click "Organize + Tabs" to analyze unlocked tabs. +

+ )} + {status === "loading" && (
@@ -347,7 +536,7 @@ export function TabOrganizer() { @@ -391,12 +580,6 @@ export function TabOrganizer() {
)} - - {status === "idle" && !error && ( -

- Click "Organize Tabs" to analyze and group your open tabs. -

- )}
); } @@ -405,7 +588,7 @@ export function TabOrganizer() { function Spinner() { return ( -
+
); } @@ -442,7 +625,7 @@ function TabCheckbox({ type="checkbox" checked={checked} onChange={onToggle} - className="h-3.5 w-3.5 rounded border-zinc-600 bg-zinc-800 text-indigo-500 accent-indigo-500" + className="h-3.5 w-3.5 rounded border-zinc-600 bg-zinc-800 text-brand-400 accent-brand-400" /> @@ -486,7 +669,7 @@ function GroupCard({ type="checkbox" checked={group.enabled} onChange={() => onToggle(index)} - className="h-3.5 w-3.5 rounded border-zinc-600 bg-zinc-800 accent-indigo-500" + className="h-3.5 w-3.5 rounded border-zinc-600 bg-zinc-800 accent-brand-400" /> {/* Color picker */} @@ -511,12 +694,12 @@ function GroupCard({ onBlur={() => setEditingName(false)} onKeyDown={(e) => e.key === "Enter" && setEditingName(false)} autoFocus - className="min-w-0 flex-1 rounded border border-zinc-600 bg-zinc-800 px-1.5 py-0.5 text-sm text-zinc-100 outline-none focus:border-indigo-500" + className="min-w-0 flex-1 rounded border border-zinc-600 bg-zinc-800 px-1.5 py-0.5 text-sm text-zinc-100 outline-none focus:border-brand-400" /> ) : ( - ))} +
+
+ {modes.map((m) => ( + + ))} +
+ + {folders.length > 0 && ( +
+

+ Bookmark Folders + {lockedIds.size > 0 && ( + + ({lockedIds.size} locked) + + )} +

+

+ Locked folders are excluded from all organization. +

+ {folders.map((folder) => { + const isLocked = lockedIds.has(folder.id); + return ( +
+ 📁 + + {folder.title} + + + {folder.path.split("/")[0]} + + +
+ ); + })} +
+ )}
); } From 483b28d1fbfe199ee5030a47baebf0f9db7c36f9 Mon Sep 17 00:00:00 2001 From: Joshua Butner <53810+heyjawrsh@users.noreply.github.com> Date: Mon, 30 Mar 2026 08:51:43 -0500 Subject: [PATCH 08/54] feat: add granularity slider and long-running message support Add a 1-5 granularity control for tab and bookmark organization that lets users choose between broad and fine-grained grouping. Introduce port-based messaging for long-running operations with progress updates, elapsed timers, and session storage persistence for preview state. --- .../src/ai/prompts/bookmark-grouping.ts | 40 ++++- apps/extension/src/ai/prompts/tab-grouping.ts | 24 ++- apps/extension/src/ai/providers/local.ts | 3 +- apps/extension/src/ai/providers/openrouter.ts | 6 +- apps/extension/src/ai/providers/relaxed.ts | 8 +- apps/extension/src/ai/providers/yolo.ts | 8 +- apps/extension/src/ai/types.ts | 7 +- .../src/background/service-worker.ts | 88 ++++++++-- apps/extension/src/shared/messaging.ts | 45 +++++ apps/extension/src/shared/types.ts | 10 ++ .../components/GranularitySlider.tsx | 41 +++++ .../src/sidepanel/pages/BookmarkOrganizer.tsx | 119 ++++++++++++-- .../src/sidepanel/pages/TabOrganizer.tsx | 154 ++++++++++++++---- 13 files changed, 474 insertions(+), 79 deletions(-) create mode 100644 apps/extension/src/sidepanel/components/GranularitySlider.tsx diff --git a/apps/extension/src/ai/prompts/bookmark-grouping.ts b/apps/extension/src/ai/prompts/bookmark-grouping.ts index b0f465c..6c2e218 100644 --- a/apps/extension/src/ai/prompts/bookmark-grouping.ts +++ b/apps/extension/src/ai/prompts/bookmark-grouping.ts @@ -1,4 +1,4 @@ -import type { BookmarkInfo } from "@/shared/types"; +import type { BookmarkInfo, GroupingGranularity } from "@/shared/types"; interface BookmarkInput { id: string; @@ -6,9 +6,24 @@ interface BookmarkInput { url?: string; } +function granularityInstruction(g: GroupingGranularity): string { + switch (g) { + case 1: + return "Create very few, broad folders (2-4 max). Merge related topics aggressively. Prefer general categories like 'Work', 'Personal', 'Reference'."; + case 2: + return "Create fewer folders with broader categories. Combine loosely related bookmarks. Aim for 3-6 folders."; + case 3: + return "Create a balanced number of folders. Group by topic or purpose. Aim for a natural number of categories."; + case 4: + return "Create more specific folders. Split topics into distinct sub-categories. More folders is better than fewer."; + case 5: + return "Create many fine-grained folders. Each distinct topic, tool, or domain should get its own folder. Prefer specificity."; + } +} + export function buildBookmarkOrganizePrompt( bookmarks: BookmarkInput[], - options: { includeUrls: boolean }, + options: { includeUrls: boolean; granularity?: GroupingGranularity }, ): string { const bookmarkList = bookmarks .map((b) => { @@ -18,13 +33,17 @@ export function buildBookmarkOrganizePrompt( }) .join("\n"); + const granularity = options.granularity ?? 3; + return `You are a bookmark organizer. Analyze these bookmarks and suggest a folder structure. BOOKMARKS: ${bookmarkList} +GROUPING DETAIL LEVEL: ${granularity}/5 +${granularityInstruction(granularity)} + RULES: -- Create logical folder categories (e.g., "Dev Tools", "News", "Shopping") - Use short, descriptive folder names (1-3 words) - Each bookmark should belong to exactly one folder - Identify duplicate bookmarks (same URL) @@ -49,9 +68,10 @@ export function buildBookmarkLocationPrompt( .map((f) => ` { id:"${f.id}", path:"${f.path}" }`) .join("\n"); - const bookmarkDesc = options.includeUrls && bookmark.url - ? `title:"${bookmark.title}", url:"${bookmark.url}"` - : `title:"${bookmark.title}"`; + const bookmarkDesc = + options.includeUrls && bookmark.url + ? `title:"${bookmark.title}", url:"${bookmark.url}"` + : `title:"${bookmark.title}"`; return `Suggest the best folder for this bookmark. @@ -70,7 +90,9 @@ Respond with ONLY valid JSON matching this schema: Return up to 3 suggestions, ranked by confidence (0-1).`; } -export function bookmarksToYoloInput(bookmarks: BookmarkInfo[]): BookmarkInput[] { +export function bookmarksToYoloInput( + bookmarks: BookmarkInfo[], +): BookmarkInput[] { return bookmarks.map((b) => ({ id: b.id, title: b.title, @@ -78,7 +100,9 @@ export function bookmarksToYoloInput(bookmarks: BookmarkInfo[]): BookmarkInput[] })); } -export function bookmarksToRelaxedInput(bookmarks: BookmarkInfo[]): BookmarkInput[] { +export function bookmarksToRelaxedInput( + bookmarks: BookmarkInfo[], +): BookmarkInput[] { return bookmarks.map((b) => ({ id: b.id, title: b.title, diff --git a/apps/extension/src/ai/prompts/tab-grouping.ts b/apps/extension/src/ai/prompts/tab-grouping.ts index 06d47b9..37709c1 100644 --- a/apps/extension/src/ai/prompts/tab-grouping.ts +++ b/apps/extension/src/ai/prompts/tab-grouping.ts @@ -1,4 +1,4 @@ -import type { TabInfo } from "@/shared/types"; +import type { TabInfo, GroupingGranularity } from "@/shared/types"; interface TabInput { id: number; @@ -7,9 +7,24 @@ interface TabInput { lastAccessed?: number; } +function granularityInstruction(g: GroupingGranularity): string { + switch (g) { + case 1: + return "Create very few, broad groups (2-4 max). Merge related topics aggressively. Prefer general categories like 'Work', 'Personal', 'Reference'."; + case 2: + return "Create fewer groups with broader categories. It's OK to combine loosely related tabs. Aim for 3-6 groups."; + case 3: + return "Create a balanced number of groups. Group by topic or project. Aim for a natural number of categories."; + case 4: + return "Create more specific groups. Split topics into distinct sub-categories where it makes sense. More groups is better than fewer."; + case 5: + return "Create many fine-grained groups. Each distinct topic, project, or domain should get its own group. Prefer specificity over brevity."; + } +} + export function buildTabGroupingPrompt( tabs: TabInput[], - options: { includeUrls: boolean }, + options: { includeUrls: boolean; granularity?: GroupingGranularity }, ): string { const tabList = tabs .map((t) => { @@ -25,11 +40,16 @@ export function buildTabGroupingPrompt( }) .join("\n"); + const granularity = options.granularity ?? 3; + return `You are a browser tab organizer. Analyze these open tabs and suggest logical groupings. TABS: ${tabList} +GROUPING DETAIL LEVEL: ${granularity}/5 +${granularityInstruction(granularity)} + RULES: - Group tabs by topic, project, or activity (not just domain) - Use short, descriptive group names (1-3 words) diff --git a/apps/extension/src/ai/providers/local.ts b/apps/extension/src/ai/providers/local.ts index 48c2a58..2867d05 100644 --- a/apps/extension/src/ai/providers/local.ts +++ b/apps/extension/src/ai/providers/local.ts @@ -12,7 +12,7 @@ import type { * For now, returns an error directing users to choose a different tier. */ export class LocalProvider implements AIProvider { - async organizeTabs(_tabs: TabInfo[]): Promise { + async organizeTabs(_tabs: TabInfo[], _granularity?: number): Promise { throw new Error( "Local AI not yet available. Use rule-based grouping or switch to Relaxed/YOLO tier in Settings.", ); @@ -20,6 +20,7 @@ export class LocalProvider implements AIProvider { async organizeBookmarks( _bookmarks: BookmarkInfo[], + _granularity?: number, ): Promise { throw new Error( "Local AI not yet available. Switch to Relaxed/YOLO tier in Settings.", diff --git a/apps/extension/src/ai/providers/openrouter.ts b/apps/extension/src/ai/providers/openrouter.ts index fa40b84..d14ae66 100644 --- a/apps/extension/src/ai/providers/openrouter.ts +++ b/apps/extension/src/ai/providers/openrouter.ts @@ -4,6 +4,7 @@ import type { BookmarkInfo, BookmarkOrganizationResult, LocationSuggestion, + GroupingGranularity, } from "@/shared/types"; import { buildTabGroupingPrompt, @@ -34,12 +35,13 @@ export class OpenRouterProvider implements AIProvider { private includeUrls: boolean, ) {} - async organizeTabs(tabs: TabInfo[]): Promise { + async organizeTabs(tabs: TabInfo[], granularity?: GroupingGranularity): Promise { const input = this.includeUrls ? tabsToYoloInput(tabs) : tabsToRelaxedInput(tabs); const prompt = buildTabGroupingPrompt(input, { includeUrls: this.includeUrls, + granularity, }); const response = await this.complete(prompt); return parseTabOrganization(response); @@ -47,12 +49,14 @@ export class OpenRouterProvider implements AIProvider { async organizeBookmarks( bookmarks: BookmarkInfo[], + granularity?: GroupingGranularity, ): Promise { const input = this.includeUrls ? bookmarksToYoloInput(bookmarks) : bookmarksToRelaxedInput(bookmarks); const prompt = buildBookmarkOrganizePrompt(input, { includeUrls: this.includeUrls, + granularity, }); const response = await this.complete(prompt); const parsed = parseBookmarkOrganization(response); diff --git a/apps/extension/src/ai/providers/relaxed.ts b/apps/extension/src/ai/providers/relaxed.ts index bc4ce68..907164c 100644 --- a/apps/extension/src/ai/providers/relaxed.ts +++ b/apps/extension/src/ai/providers/relaxed.ts @@ -5,6 +5,7 @@ import type { BookmarkOrganizationResult, LocationSuggestion, AIModelProvider, + GroupingGranularity, } from "@/shared/types"; import { buildTabGroupingPrompt, @@ -29,18 +30,19 @@ export class RelaxedProvider implements AIProvider { private openaiKey: string, ) {} - async organizeTabs(tabs: TabInfo[]): Promise { + async organizeTabs(tabs: TabInfo[], granularity?: GroupingGranularity): Promise { const input = tabsToRelaxedInput(tabs); - const prompt = buildTabGroupingPrompt(input, { includeUrls: false }); + const prompt = buildTabGroupingPrompt(input, { includeUrls: false, granularity }); const response = await this.complete(prompt); return parseTabOrganization(response); } async organizeBookmarks( bookmarks: BookmarkInfo[], + granularity?: GroupingGranularity, ): Promise { const input = bookmarksToRelaxedInput(bookmarks); - const prompt = buildBookmarkOrganizePrompt(input, { includeUrls: false }); + const prompt = buildBookmarkOrganizePrompt(input, { includeUrls: false, granularity }); const response = await this.complete(prompt); const parsed = parseBookmarkOrganization(response); return { diff --git a/apps/extension/src/ai/providers/yolo.ts b/apps/extension/src/ai/providers/yolo.ts index d86132b..a3c080e 100644 --- a/apps/extension/src/ai/providers/yolo.ts +++ b/apps/extension/src/ai/providers/yolo.ts @@ -5,6 +5,7 @@ import type { BookmarkOrganizationResult, LocationSuggestion, AIModelProvider, + GroupingGranularity, } from "@/shared/types"; import { buildTabGroupingPrompt, @@ -24,18 +25,19 @@ export class YoloProvider implements AIProvider { private openaiKey: string, ) {} - async organizeTabs(tabs: TabInfo[]): Promise { + async organizeTabs(tabs: TabInfo[], granularity?: GroupingGranularity): Promise { const input = tabsToYoloInput(tabs); - const prompt = buildTabGroupingPrompt(input, { includeUrls: true }); + const prompt = buildTabGroupingPrompt(input, { includeUrls: true, granularity }); const response = await this.complete(prompt); return parseTabOrganization(response); } async organizeBookmarks( bookmarks: BookmarkInfo[], + granularity?: GroupingGranularity, ): Promise { const input = bookmarksToYoloInput(bookmarks); - const prompt = buildBookmarkOrganizePrompt(input, { includeUrls: true }); + const prompt = buildBookmarkOrganizePrompt(input, { includeUrls: true, granularity }); const response = await this.complete(prompt); const parsed = parseBookmarkOrganization(response); return { diff --git a/apps/extension/src/ai/types.ts b/apps/extension/src/ai/types.ts index bec69b7..27e81c7 100644 --- a/apps/extension/src/ai/types.ts +++ b/apps/extension/src/ai/types.ts @@ -4,6 +4,7 @@ import type { BookmarkInfo, BookmarkOrganizationResult, LocationSuggestion, + GroupingGranularity, } from "@/shared/types"; export interface TabOrganizationAIResult { @@ -14,9 +15,13 @@ export interface TabOrganizationAIResult { } export interface AIProvider { - organizeTabs(tabs: TabInfo[]): Promise; + organizeTabs( + tabs: TabInfo[], + granularity?: GroupingGranularity, + ): Promise; organizeBookmarks( bookmarks: BookmarkInfo[], + granularity?: GroupingGranularity, ): Promise; suggestBookmarkLocation( bookmark: BookmarkInfo, diff --git a/apps/extension/src/background/service-worker.ts b/apps/extension/src/background/service-worker.ts index b203a85..8b8cc33 100644 --- a/apps/extension/src/background/service-worker.ts +++ b/apps/extension/src/background/service-worker.ts @@ -1,4 +1,4 @@ -import type { Message, MessageResponse } from "@/shared/messaging"; +import type { Message, MessageResponse, ProgressUpdate } from "@/shared/messaging"; import type { Settings, TabInfo, @@ -58,7 +58,7 @@ chrome.sidePanel .setPanelBehavior({ openPanelOnActionClick: true }) .catch(console.error); -// Message handler +// Message handler for quick operations chrome.runtime.onMessage.addListener( ( message: Message, @@ -72,6 +72,48 @@ chrome.runtime.onMessage.addListener( }, ); +// Port handler for long-running operations with progress +type ProgressSender = (current: number, total: number, msg: string) => void; + +chrome.runtime.onConnect.addListener((port) => { + if (port.name !== "long-running") return; + + port.onMessage.addListener(async (message: Message) => { + const sendProgress: ProgressSender = (current, total, msg) => { + try { + port.postMessage({ + type: "progress", + current, + total, + message: msg, + } satisfies ProgressUpdate); + } catch { + // Port may have disconnected + } + }; + + try { + let result: MessageResponse; + const payload = message.payload as Record | undefined; + const granularity = payload?.granularity as number | undefined; + + switch (message.action) { + case "organize-tabs": + result = await handleOrganizeTabs(granularity, sendProgress); + break; + case "organize-bookmarks": + result = await handleOrganizeBookmarks(granularity, sendProgress); + break; + default: + result = await handleMessage(message); + } + try { port.postMessage(result); } catch { /* disconnected */ } + } catch (err) { + try { port.postMessage({ success: false, error: String(err) }); } catch { /* disconnected */ } + } + }); +}); + async function handleMessage(message: Message): Promise { switch (message.action) { case "get-settings": @@ -82,7 +124,9 @@ async function handleMessage(message: Message): Promise { return { success: true }; case "organize-tabs": - return await handleOrganizeTabs(); + return await handleOrganizeTabs( + (message.payload as { granularity?: number })?.granularity, + ); case "apply-tab-suggestions": return await handleApplyTabSuggestions( @@ -93,7 +137,9 @@ async function handleMessage(message: Message): Promise { return await handleUndoTabChanges(); case "organize-bookmarks": - return await handleOrganizeBookmarks(); + return await handleOrganizeBookmarks( + (message.payload as { granularity?: number })?.granularity, + ); case "find-duplicate-bookmarks": return await handleFindDuplicateBookmarks(); @@ -147,11 +193,15 @@ async function handleMessage(message: Message): Promise { // ─── Tab Organization ─────────────────────────────────────────────── -async function handleOrganizeTabs(): Promise< - MessageResponse -> { +async function handleOrganizeTabs( + granularity?: number, + sendProgress?: ProgressSender, +): Promise> { const settings = await getSettings(); const allTabs = await queryAllTabs(); + const g = (granularity ?? 3) as import("@/shared/types").GroupingGranularity; + + sendProgress?.(0, 1, `Analyzing ${allTabs.length} tabs...`); const snapshot: TabSnapshot[] = allTabs.map((t) => ({ id: t.id, @@ -170,11 +220,11 @@ async function handleOrganizeTabs(): Promise< let result: TabOrganizationResult; if (settings.aiTier === "secure") { - result = ruleBasedOrganize(tabs, settings.staleDaysThreshold); + result = ruleBasedOrganize(tabs, settings.staleDaysThreshold, g); } else { try { const provider = await getAIProvider(); - const aiResult = await provider.organizeTabs(tabs); + const aiResult = await provider.organizeTabs(tabs, g); result = { tabs, groups: aiResult.groups, @@ -184,7 +234,7 @@ async function handleOrganizeTabs(): Promise< }; } catch (err) { console.warn("AI tab organization failed:", err); - result = ruleBasedOrganize(tabs, settings.staleDaysThreshold); + result = ruleBasedOrganize(tabs, settings.staleDaysThreshold, g); result.reasoning = `AI unavailable (${err instanceof Error ? err.message : String(err)}). ${result.reasoning}`; } } @@ -303,9 +353,13 @@ async function handleUndoTabChanges(): Promise { function ruleBasedOrganize( tabs: TabInfo[], staleDays: number, + granularity: import("@/shared/types").GroupingGranularity = 3, ): TabOrganizationResult { const domainMap = groupByDomain(tabs); - const groups = domainToTabGroups(domainMap); + // Lower granularity = larger minGroupSize (fewer groups) + // 1=Broad → min 4, 2→min 3, 3=Balanced → min 2, 4→min 2, 5=Fine → min 1 + const minGroupSize = granularity <= 1 ? 4 : granularity <= 2 ? 3 : granularity <= 3 ? 2 : 1; + const groups = domainToTabGroups(domainMap, minGroupSize); const duplicates = findDuplicatesByUrl(tabs); const stale = findStaleTabs(tabs, staleDays); @@ -448,10 +502,12 @@ interface BookmarkApplyPayload { cleanupEmptyFolders?: boolean; } -async function handleOrganizeBookmarks(): Promise< - MessageResponse -> { +async function handleOrganizeBookmarks( + granularity?: number, + sendProgress?: ProgressSender, +): Promise> { const settings = await getSettings(); + const g = (granularity ?? 3) as import("@/shared/types").GroupingGranularity; const tree = await getBookmarkTree(); const allBookmarks = flattenBookmarks(tree); const folders = extractFolders(tree); @@ -467,6 +523,8 @@ async function handleOrganizeBookmarks(): Promise< const lockedBookmarkIds = getDeepLockedBookmarkIds(lockedFolders, tree); const bookmarks = allBookmarks.filter((b) => !lockedBookmarkIds.has(b.id)); + sendProgress?.(0, 1, `Analyzing ${bookmarks.length} bookmarks...`); + if (settings.aiTier === "secure") { // Rule-based: no restructuring, just return current state return { @@ -488,7 +546,7 @@ async function handleOrganizeBookmarks(): Promise< try { const provider = await getAIProvider(); - const aiResult = await provider.organizeBookmarks(bookmarks); + const aiResult = await provider.organizeBookmarks(bookmarks, g); return { success: true, data: { bookmarks, folders, result: aiResult }, diff --git a/apps/extension/src/shared/messaging.ts b/apps/extension/src/shared/messaging.ts index 902d786..19bb4b9 100644 --- a/apps/extension/src/shared/messaging.ts +++ b/apps/extension/src/shared/messaging.ts @@ -28,6 +28,13 @@ export interface MessageResponse { error?: string; } +export interface ProgressUpdate { + type: "progress"; + current: number; + total: number; + message: string; +} + export function sendMessage( action: MessageAction, payload?: TReq, @@ -37,3 +44,41 @@ export function sendMessage( payload, }); } + +/** + * Send a message over a port for long-running operations. + * Avoids the MV3 message channel timeout and enables progress updates. + */ +export function sendLongRunningMessage( + action: MessageAction, + payload?: TReq, + onProgress?: (update: ProgressUpdate) => void, +): Promise> { + return new Promise((resolve) => { + const port = chrome.runtime.connect({ name: "long-running" }); + + port.onMessage.addListener( + (msg: MessageResponse | ProgressUpdate) => { + if ("type" in msg && msg.type === "progress") { + onProgress?.(msg as ProgressUpdate); + } else { + resolve(msg as MessageResponse); + port.disconnect(); + } + }, + ); + + port.onDisconnect.addListener(() => { + if (chrome.runtime.lastError) { + resolve({ + success: false, + error: + chrome.runtime.lastError.message ?? + "Connection lost to background worker", + } as MessageResponse); + } + }); + + port.postMessage({ action, payload }); + }); +} diff --git a/apps/extension/src/shared/types.ts b/apps/extension/src/shared/types.ts index a8c39b2..a9ff850 100644 --- a/apps/extension/src/shared/types.ts +++ b/apps/extension/src/shared/types.ts @@ -22,6 +22,16 @@ export const DEFAULT_SETTINGS: Settings = { staleDaysThreshold: 7, }; +export type GroupingGranularity = 1 | 2 | 3 | 4 | 5; + +export const GRANULARITY_LABELS: Record = { + 1: "Broad", + 2: "Relaxed", + 3: "Balanced", + 4: "Detailed", + 5: "Fine-grained", +}; + export type TabGroupColor = | "grey" | "blue" diff --git a/apps/extension/src/sidepanel/components/GranularitySlider.tsx b/apps/extension/src/sidepanel/components/GranularitySlider.tsx new file mode 100644 index 0000000..4dce14f --- /dev/null +++ b/apps/extension/src/sidepanel/components/GranularitySlider.tsx @@ -0,0 +1,41 @@ +import type { GroupingGranularity } from "@/shared/types"; +import { GRANULARITY_LABELS } from "@/shared/types"; + +const LEVELS: GroupingGranularity[] = [1, 2, 3, 4, 5]; + +export function GranularitySlider({ + value, + onChange, + disabled, +}: { + value: GroupingGranularity; + onChange: (v: GroupingGranularity) => void; + disabled?: boolean; +}) { + return ( +
+
+ Grouping detail + + {GRANULARITY_LABELS[value]} + +
+
+ Broad + + onChange(Number(e.target.value) as GroupingGranularity) + } + disabled={disabled} + className="h-1.5 flex-1 cursor-pointer appearance-none rounded-full bg-zinc-800 accent-brand-400 disabled:cursor-not-allowed disabled:opacity-50" + /> + Fine +
+
+ ); +} diff --git a/apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx b/apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx index 3dea152..144b6c6 100644 --- a/apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx +++ b/apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx @@ -1,5 +1,5 @@ -import { useState, useEffect } from "react"; -import { sendMessage } from "@/shared/messaging"; +import { useState, useEffect, useRef } from "react"; +import { sendMessage, sendLongRunningMessage, type ProgressUpdate } from "@/shared/messaging"; import type { BookmarkInfo, BookmarkOrganizationResult, @@ -7,7 +7,9 @@ import type { FolderInfo, LocationSuggestion, LockedBookmarkFolder, + GroupingGranularity, } from "@/shared/types"; +import { GranularitySlider } from "../components/GranularitySlider"; type Mode = "menu" | "organize" | "locate" | "duplicates" | "cleanup"; type Status = "idle" | "loading" | "preview" | "applying" | "done"; @@ -212,20 +214,70 @@ interface OrganizeData { result: BookmarkOrganizationResult; } +function useElapsedTimer(running: boolean) { + const [elapsed, setElapsed] = useState(0); + const startRef = useRef(0); + useEffect(() => { + if (!running) { setElapsed(0); return; } + startRef.current = Date.now(); + setElapsed(0); + const id = setInterval( + () => setElapsed(Math.floor((Date.now() - startRef.current) / 1000)), + 1000, + ); + return () => clearInterval(id); + }, [running]); + return elapsed; +} + +function formatElapsed(s: number): string { + if (s < 60) return `${s}s`; + return `${Math.floor(s / 60)}m ${s % 60}s`; +} + function OrganizeMode({ onBack }: { onBack: () => void }) { const [status, setStatus] = useState("idle"); const [data, setData] = useState(null); const [enabledFolders, setEnabledFolders] = useState>(new Set()); const [error, setError] = useState(null); const [undoAvailable, setUndoAvailable] = useState(false); + const [granularity, setGranularity] = useState(3); + const [progress, setProgress] = useState(null); + const elapsed = useElapsedTimer(status === "loading" || status === "applying"); + + // Persist preview for tab switching + useEffect(() => { + if (status === "preview" && data) { + chrome.storage.session.set({ + vxrtx_bm_preview: { data, enabledFolders: Array.from(enabledFolders) }, + }); + } else if (status === "idle" || status === "done") { + chrome.storage.session.remove("vxrtx_bm_preview"); + } + }, [status, data, enabledFolders]); + + // Restore on mount + useEffect(() => { + chrome.storage.session.get("vxrtx_bm_preview").then((stored) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const saved = stored.vxrtx_bm_preview as any; + if (saved?.data && status === "idle") { + setData(saved.data as OrganizeData); + setEnabledFolders(new Set(saved.enabledFolders as number[])); + setStatus("preview"); + } + }); + }, []); // eslint-disable-line react-hooks/exhaustive-deps async function handleAnalyze() { setStatus("loading"); setError(null); + setProgress(null); try { - const response = await sendMessage( - "organize-bookmarks", - ); + const response = await sendLongRunningMessage< + { granularity: GroupingGranularity }, + OrganizeData + >("organize-bookmarks", { granularity }, setProgress); if (response.success && response.data) { setData(response.data); setEnabledFolders( @@ -307,6 +359,7 @@ function OrganizeMode({ onBack }: { onBack: () => void }) {

AI will analyze your bookmarks and suggest a new folder structure.

+
)} - {status === "loading" && ( -
- - Analyzing bookmarks... + {(status === "loading" || status === "applying") && ( +
+
+ + + {status === "applying" + ? "Applying changes..." + : progress?.message ?? "Analyzing bookmarks..."} + + {elapsed > 0 && ( + + {formatElapsed(elapsed)} + + )} +
+ {progress && progress.total > 1 && ( +
+
+
+
+

+ Batch {progress.current} of {progress.total} +

+
+ )}
)} @@ -335,6 +414,21 @@ function OrganizeMode({ onBack }: { onBack: () => void }) {

{data.result.reasoning}

)} +
+
+ +
+ +
+ {data.result.folders.length > 0 ? ( <>

@@ -414,13 +508,6 @@ function OrganizeMode({ onBack }: { onBack: () => void }) {

)} - {status === "applying" && ( -
- - Applying changes... -
- )} - {status === "done" && (
diff --git a/apps/extension/src/sidepanel/pages/TabOrganizer.tsx b/apps/extension/src/sidepanel/pages/TabOrganizer.tsx index 7dc46c2..6f6311d 100644 --- a/apps/extension/src/sidepanel/pages/TabOrganizer.tsx +++ b/apps/extension/src/sidepanel/pages/TabOrganizer.tsx @@ -1,15 +1,38 @@ -import { useState, useCallback, useEffect } from "react"; -import { sendMessage } from "@/shared/messaging"; +import { useState, useCallback, useEffect, useRef } from "react"; +import { sendMessage, sendLongRunningMessage, type ProgressUpdate } from "@/shared/messaging"; import type { TabOrganizationResult, TabGroupSuggestion, TabGroupColor, TabInfo, LockedTabGroup, + GroupingGranularity, } from "@/shared/types"; +import { GranularitySlider } from "../components/GranularitySlider"; type Status = "idle" | "loading" | "preview" | "applying" | "done"; +function useElapsedTimer(running: boolean) { + const [elapsed, setElapsed] = useState(0); + const startRef = useRef(0); + useEffect(() => { + if (!running) { setElapsed(0); return; } + startRef.current = Date.now(); + setElapsed(0); + const id = setInterval( + () => setElapsed(Math.floor((Date.now() - startRef.current) / 1000)), + 1000, + ); + return () => clearInterval(id); + }, [running]); + return elapsed; +} + +function formatElapsed(s: number): string { + if (s < 60) return `${s}s`; + return `${Math.floor(s / 60)}m ${s % 60}s`; +} + interface EditableGroup extends TabGroupSuggestion { enabled: boolean; } @@ -47,6 +70,9 @@ export function TabOrganizer() { const [preview, setPreview] = useState(null); const [error, setError] = useState(null); const [undoAvailable, setUndoAvailable] = useState(false); + const [granularity, setGranularity] = useState(3); + const [progress, setProgress] = useState(null); + const elapsed = useElapsedTimer(status === "loading" || status === "applying"); // Lock state const [chromeGroups, setChromeGroups] = useState([]); @@ -58,6 +84,40 @@ export function TabOrganizer() { if (status === "idle") loadGroupsAndLocks(); }, [status]); + // Persist preview state so it survives side panel re-creation on tab switch + useEffect(() => { + if (status === "preview" && preview) { + const serializable = { + ...preview, + staleEnabled: Array.from(preview.staleEnabled), + duplicatesEnabled: Array.from(preview.duplicatesEnabled), + tabs: Array.from(preview.tabs.entries()), + }; + chrome.storage.session.set({ vxrtx_tab_preview: serializable }); + } else if (status === "idle" || status === "done") { + chrome.storage.session.remove("vxrtx_tab_preview"); + } + }, [status, preview]); + + // Restore preview on mount + useEffect(() => { + chrome.storage.session.get("vxrtx_tab_preview").then((data) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const saved = data.vxrtx_tab_preview as any; + if (saved?.groups && status === "idle") { + setPreview({ + groups: saved.groups, + reasoning: saved.reasoning, + allDuplicates: saved.allDuplicates, + staleEnabled: new Set(saved.staleEnabled), + duplicatesEnabled: new Set(saved.duplicatesEnabled), + tabs: new Map(saved.tabs), + }); + setStatus("preview"); + } + }); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + async function loadGroupsAndLocks() { try { // Get current tabs to find windowId and count tabs per group @@ -135,10 +195,12 @@ export function TabOrganizer() { async function handleOrganize() { setStatus("loading"); setError(null); + setProgress(null); try { - const response = await sendMessage( - "organize-tabs", - ); + const response = await sendLongRunningMessage< + { granularity: GroupingGranularity }, + TabOrganizationResult + >("organize-tabs", { granularity }, setProgress); if (response.success && response.data) { const data = response.data; const tabMap = new Map(data.tabs.map((t) => [t.id, t])); @@ -321,7 +383,18 @@ export function TabOrganizer() {
)} - {/* Idle state: show current groups with lock toggles */} + {/* Idle state: granularity slider first, then groups with lock toggles */} + {status === "idle" && !error && ( +
+ +

+ {chromeGroups.length > 0 + ? "Locked groups are excluded from all organization." + : 'Click "Organize Tabs" to analyze and group your open tabs.'} +

+
+ )} + {status === "idle" && chromeGroups.length > 0 && (

@@ -403,23 +476,36 @@ export function TabOrganizer() {

)} - {status === "idle" && !error && chromeGroups.length === 0 && ( -

- Click "Organize Tabs" to analyze and group your open tabs. -

- )} - - {status === "idle" && !error && chromeGroups.length > 0 && ( -

- Locked groups are excluded from all organization. Click "Organize - Tabs" to analyze unlocked tabs. -

- )} - - {status === "loading" && ( -
- - Analyzing tabs... + {(status === "loading" || status === "applying") && ( +
+
+ + + {status === "applying" + ? "Applying changes..." + : progress?.message ?? "Analyzing tabs..."} + + {elapsed > 0 && ( + + {formatElapsed(elapsed)} + + )} +
+ {progress && progress.total > 1 && ( +
+
+
+
+

+ Batch {progress.current} of {progress.total} +

+
+ )}
)} @@ -429,6 +515,21 @@ export function TabOrganizer() {

{preview.reasoning}

)} +
+
+ +
+ +
+ {/* Groups */} {preview.groups.length > 0 && (
@@ -550,12 +651,7 @@ export function TabOrganizer() {
)} - {status === "applying" && ( -
- - Applying changes... -
- )} + {/* applying state handled in the loading/applying block above */} {status === "done" && (
From 64b74e31d1f6f034e5ba1ff5f6049ddd111bb597 Mon Sep 17 00:00:00 2001 From: Joshua Butner <53810+heyjawrsh@users.noreply.github.com> Date: Mon, 30 Mar 2026 09:13:23 -0500 Subject: [PATCH 09/54] Updated to new direction branding --- apps/extension/public/icons/icon-128.png | Bin 3969 -> 2656 bytes apps/extension/public/icons/icon-16.png | Bin 538 -> 344 bytes apps/extension/public/icons/icon-48.png | Bin 1549 -> 982 bytes apps/extension/src/styles/globals.css | 22 +++++++++++----------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/extension/public/icons/icon-128.png b/apps/extension/public/icons/icon-128.png index fc10f1fdbff40645beabfe71e6d5d8a688f22507..3a8a63c2c21b63c25d4e7afa578a80d4508ca7c7 100644 GIT binary patch literal 2656 zcmb7``9Bj30LC}*h8&sKF_bHG%v*U?LlI_<2}y^D8j@(Xcga0-m$5lAIo3+dD98Is zt~tkS?+}?@N6bP>V(z)$-ap~}e4g*~{q6brdE(+^D=V!g4FCXS?d@Q$KaKwn$zOgp z)BFzir=-w!?pOfefZ~4u0eJit-$ z3&?NZ|!-tg~VG}8fml488#X30(+GPceJ6a!s1;% z?>nVaGj^fm5O%&LW@DqVagS2JK0i*FL7Ehq3PqOlG?4Sw~X}|92$%=R%&} zm3se&j0HNirAGW8vm`#0rD+;RFr56Jj~i>ViKek^~1(*1!V6nRW9c>WHJ)criQ4q{LNP6$E`ZX z9h~Yt;V6riscejl5@x@dRDWdBPDouR{5@a$Ly$ofK0=5VU-D;05s=ATB7R2NDnemA zo0yaNYQ3Hh`t`RrlcNj;QmxdtC2uv0jkZ;%ciPH6M1cNIv;*mOCdm~0$pOz?H z9&s%k>%8gZOS6n2g*nw#4|i7WO+M~ogmt`N<&#)?nMPDa>8W=FXEN?={c>LH-h!U( z4Hnf#a&OIgdqdvv)%(&%`V7mDtLa-10fzkITmwJswNDfxB3@VIhy&s)bEUJ)7ZYNr zO9Shgo7#LY$o;c7)DJ%Q2ut3fZT(zIA*a+gaVWJX`(_$F<*6CCuwp*iyDQAbNw)97 zW-pLdxH7nHP#yL0bb*?VIQe#tYJkn!Kc{g#P?UB9a6!eLwUG98y)2rSMz8j?Y7L{6 zYH_2Dvej&H_->E(Gi4v2M*!QXHfeb& z0#$x6xJ5lmQ*eD>S4_}&bA?dFyhCBPEhLM13201T{wv&Km!f;7#;*>-cTH!o-DJ9i zh|{S^oD4$L1pwALkq11Z^)7OS^XDJT4d_pPF%d)Ww#)5rrz~b*jZmyahG8p#6^nsO&9(1 zfuqQ$PfqRuTGrZVus60U56&leA*0D>Gv{_j6AbL3`51KH3f_h zZfS6p=b*cQ9twaF^D-K*?<8c)A3Phlyn-hBh z#D3=1GM$_g9o7HiX3}f1pxAtP64F1lnycp$gm>{9#YmYg=h9hOJcrICbO-2NjSQ%zw`VE~0G$fUKU$g%cI`4{CTXg$Bvx zC42qBV;oBC*sws(#&z@?3cP(%+x}vBFwqIr<6E3Rnx^Qd4DlZRWBp`-u@to{Ova$O zlK57_s8c|AvC!Fp2?yUAaz*D!esNSg9L43y*`Ghc(DnofuhoZY+jEGV^43u^v37TV7P4+q-Po7EB6$jvp-lhS;tJKz{4QP>-FBKsM3uhEriO_w~E%!CC!erNyz&9@L^$3#f8zt*vT8 z$+LzJ&iTz)(6Ciz6QrI%)4Aa^t}h0TsDm#V(zaJ{v~k_E8S*qTlk0c}q=({r(gh?``GNC^R9erBVpXa?`yhRG)3!)%%KPRGorXXjGF{EynaXy1k)$ z0QS1Dxg=i$C1pkvbKLm0leFr`Ix3?CsB1pUDcsQY!IWM`Zgct{TmfnbOqdQG-TL@n z4p>Tfj$-VZ&DuhMk6L^CemL(P4bdZBF(D;Gqe)a&I6I2_ITAmEufN5Lj2D~q$@9dE8);V<~`pmA?>UH_?Oj_E)2{N z6GJEnH4f2*TE^WTt@6zn)?tGls=3dH$OybnTy1={;4DWt-e4I=p;!;;S<$c)Yp)#6T9a%b~jtHj0wgFPEg0i9vRdsJfzE10DQu`D> z8I=F+`{m89H#c_~$=QRU`AH82MXn?IP&3qT6NhLh_CP)Y;OgH$wlyC)HK7=Ef+3k0 zeA2$UK$;FPmr?jcpU#+&J0=l4n=m(qE#Gqu`;+w4D}TWk6q2W$2x$#GJ6)1)IJfc9Zru>p!?>s#2h}NbtKARtWwbISLyQyPjvN?ExKTC3s;ES!2gj zLhK`;A+e!b?3YSX!?dhV%o(;kWOZ3Dw}>OuukWZC6{06P^jWnzcBZ$wuksyqC8V?8 z41SZhueup$oSAn4>*>KruU0{2{KK*ncP7&8q2Xn{;iCV4u(maY7TSl(_W3jMmw#SH NfW3_qtitMc+`nH(E)W0! literal 3969 zcmb7H_cz;*_kK~ei`X+p6Dz*CXvJPpo8pZcZ?jb+RikRe ztWtY?l-8CdOzyHDKhkNgN&Uwx~_lM^^&$;myW=1y{`56HKxPdS>u=+Ej{w;di zKfM?3OacH_V}yaObx6T(fkQBd5qJM1dn9HQk+v~1tcj`x!l=h2Vff~lG_Is(>I^Ob z2Gj%1)QO%2ji~6pUgn5i)mxrXB2(w}aGxrhckOULf#RU6(alzZvE7u-RlBgwKfHgv zR!Ii_GP@a-H%b3$Lz(*Zt^XMkfayq$FrDCYHsNjax@jjz@S}?-#y6aje)nEI8H`x! zA|4IQfk97F_k#V_9LTHVF)`xkF*x7Q8qD?nv?QqkgPd zkxwTwo4b*`e(>Hbl{%gs2;5!?8e_+%R%Et`)GAz4^uAb&jQGV~FTj4%zq*&dHt_@Q z(c&4es?g(bIB~iaoMflkTIwGf9pF>Utas-o-fH^l;?NoTAP(qnN_MkLS9l^Vm;%MY1cy2UqV)mx8u!SP|=uXX|kY6qbkzO1D z?7X~FHX)if*g(IuwCz)3ALb`ipxJM-d`C&hHRARqc|$1yf&hk3yEm1@PC<6Jp@l^d zO%d@52dFq~{xYJUhx}P@OL3sX@x%N1Spy}094vltB-t!Zexkx){2+7tAF}VD?}+}5 z53F)$L4B~~cuaapZ-;LWl#)ijCbtjq*rt;(YOR-D$9(P7=%m~&W;f;rnlHDGcu*mD z55*r{98!mk4Vz4hSN8L&hH3g#- zpN`A)kIVHhw?rC};)#blZ0=uP%5fURr|$^N%m>Z7g4S&<;b#iG=vicDvAL0#2PSTx z`rQOdcj&sNrbg%dB}1g$zO7sl7nj;e*IF{0D&^n>czvZ&7}s5{Hzhlw@z}3u(QENM z^zn1TKSYG=M0=-AQq?=hLU_}Q;Bn^OX5o5vMJ=+cB?r(kElujF%05x=>WJAb6BIyy zn7;J$lk;ES?FrL-#mBaE?>tmT0d=dOgw^PWes;kAOse5sAPLrHf_X^IjgZp#E_ST^ zY?;Y1ly^noYbRL5F66JZr$4OE_H^D9$5aEnlHv5t0tDTfsks1O@-UJ45?g>J%e?$S z^N;NHuF`Jdc1aHXbjn)W9@`fX%~UtZU`Oh0-6L*&YnD@Ly!njafP0Av^FNG>f*xF{ z7uL+OPtw?a+K(w+6FuGv$ZEPLbVggcn{ln&P;_prgq;e>(Q@9z_A`jIZboW4k&Cu# zpp!V`-2%I>G#BebsWH~<;8fDR$5r=3hMIm{T;lFhRsnTvEfRDcYO~)|L zraDg;fP@BqSxTa?cmLPlCQVG=ATk-$9PWF3McS+`V^*{9+SoUxV{5CC@9D|zyWM`TI$%}1pT?&wH)#ys%bMnR4i-@yELgXr9bssLF*v&H?Jkb8 zdeyD4D5Zz6Xm=v*C9~acmj=y$CsGTTnE(3t#Q6*jNDIg)*;rBq$GJOKFw@t5D_EHX zfZ^?p9=6_2MUH})UScnG&U%)oho%#I9VCV|s~k)GPsnpT|C+`b~?n2~G~>)7xf z0+@+$s-l7&>|q_U1cPhg>$Hv)H#MnMnzdsuRBe(gS^dY1rt`zEi_941EcIXfmW+$p zg;L4$!w}UERC<_+NjctspAY_g8wQa5U_q+Ss)mPfz)XiP0j0i+*s1ChZJXuXhkvnsdD zIg+LG`}Iywfj`T7%H5oxXvUWBD<4Q+wGh0{@LXfSi;#ZWbqh)FjbY-j$UHnlVpBop zG6hZ9X-++uHM$BT;N;q(G6=5ue8xl#=!y=4zfD)<05SXm*QK-DvP_CnR<3z^tT!JMK^fG(|5*)LQWW zy+o8mqQ`nzFEnGE`nDNc5_!?O-~1Xxc-WeQ3BIYmh8kYTj2hXUT)EP2*nPWOSaGlfdm?zHOduo|U+}gj zmd~7aM~+DjoF!cfF{{La?>)Z(t=v^)uJcm+YTnxFZ;vzM`h|Kfm89DP%s2lQ3t2TVGu$ug%uW}LqP$0EjZAnqf=k z`Zu5EFefqodXhGR>?a)oT(0Zm+_(fFXQ8PfK-1kBbc2GH;&XQbX;E3H$yePZJWKR< zv@nz{HR-~}mWl(k^R21AKieXni*Vw{Bu3j#DI(dmQe8!0;k6D*E@9|@i^7wZea96 zoZ}n(c9={SLsXk0Ov;7BE9H|CAPqUvdSP;1{sBTi{BAyBYK4r%#{DV*z6^w5yMPqf%lad%O7C&~}*rF0oAnn%%gnhWKZAyPb3_l79nw{8k~zWd5jz{*H}-O02AvPb6=l)~7__$K|-k zf$I_qZVXZ0J_lYZgzIWxN87t}3PCYBuOV+C^^!enY{akf%~}`3J3$KRO^Pb9np9o{ z>~r;w@H#oRA4Q2Yg=Vt!iZ9sa@r(719j);jG+cYPI5?phGEsyXI~u1;8hbKJ-gmFtIDpuC_;Mv1Xcza-ud*6%P_n} zVO&mx?5mt@Y+{iJd1gR+ks$G5r@7Mm4Ou5|;9+>s$1esH^vCyGK@rhwEY1=0HnE>A zGH9am!{hRIUhy%br@YUbQv>S9u8icr4mo|~6Xtl-gjX7Nt1UIO@(s+m4NiTwFOq+( zlWI$|MaQvuB=5cw_}JeF*YvQ~)^%7Us3(!rRr>ZRjaniOP(SKQg6U z{csix>0x623r7HLDF6Tf diff --git a/apps/extension/public/icons/icon-16.png b/apps/extension/public/icons/icon-16.png index 344cd24e7f9c4a5dd6efd394443f3c91cc53a788..9a9e724c169c8d8638ba010cf61825dfe8b72d4f 100644 GIT binary patch delta 318 zcmV-E0m1&71lR(QBYyx1a7bBm000XU000XU0RWnu7ytkP3Q0skRCt{2kg-a{KoExa zkW)bp#X=DzGYN`d;eq!6VtI{)=rf2<5wudm&g8(xR}fnjv9(duC%D(JNM>MUqlQy1 zg8lu~GXKuUx9i*_E-D8-P<}wA^mm7a?}G9%D&2zdLYO6%Eq^7JucPoqRM56_W-U;D zLZSD>&gScFyP5)H?Ac`-m0wYamo1yyu=7p@w+z72+3GHc-$i9Z<)hlnXyyA1!hH}b zL3qFMDX4r0O3(Gum-0ye2+H@(fYR>-i@3gH5BwB<|n&0G|?o56QqD QX8-^I07*qoM6N<$f=X+Yg8%>k delta 513 zcmV+c0{;Ej0-6MnBYy$mNklrTZ2*Mg+ z)QM`}Sett9=YTG+*}BVGQd9;Jjkl){p*jR~0x>URF3>`O0uTq00W|Ca(v5k3uQmc9 zjcGBVOI^h52Mv!%?ik1eMMxF0O$`KkEHG(+zKXeuIgk+csC_4m`Bw-3|;h#%6*{W2daB!RuU#pWD;x=bN>2E{(31F-u$7a&o^0_2$=lFJm> z1NppDPg%oXm&kS##%`R*ZeHIBKGd}Q{yKJKZOquG=6{!X0TDcAU>VODF7W3i@>}8d ztoXzZ35zdOwRy#CJ`D(UAI_eL$U-?frzk$QlUtNmpnCQk)iPBV7@=9-PPQ7e;)p+B z-eZ13xkH4SUf#Jm0kJYz2~^*(>UhY-CkBysk}mm-;zFyZJ#Rn?!Vit<=dMWTtC}%3 zHZqi@iyf-9Ej5XG)I`qsv!+73+{#g0`^sP)tZncU!DQ~0BU!EP00000NkvXXu0mjf DS!U=X diff --git a/apps/extension/public/icons/icon-48.png b/apps/extension/public/icons/icon-48.png index d2f8839f189bdd96815a3c2b8b94c3cdf68a15e2..811896d83aa114e8b3874a8e0c464353963fbc44 100644 GIT binary patch delta 961 zcmV;y13vtX4AuvbBYyx1a7bBm000XU000XU0RWnu7ytkRh)G02RCt{2S51f%M--m8 zD*hm9MAyh}SM}^B;;IR{5JJ|UsHdnw)PsqC2M-E*^5Q}g7~;jJSH%}zue0>j)a*=8&mls6P=7GfRrS92s@`Aaat;m- z4h{|u4i4`z=yHcgkoTfee{$r@;i@cRyQ$P;%B+m;GOTPyh8LCkrN%v;f^AX(N)3Mq z1w&Q3NsR}kMyeSKbT5^nrxM}Q=qFOaj^sC? zgKt?cmLW9Wn}3uc75rv1LMZsD75!Ook~~jC^QDG6RkAgVv!S;A%V~I?r^4N~Io4@H zH+<4b#kmt72^R5E{R;?Ps^iQDK^Gu8#^cS$Uy? zo#v(^_FKSRX$|moxWA5JdyN}@K6#1>zi0QEQoe$3YJU%wE_VnG4{8k1a4U`S9+mny zDOe||FimK@ZyeCcD-A3-R2j@^P?tz`pZ zAsVk+f_bRf{W1zRHs*lwY7V&CPv>U{4c}){J{DdcUBjntoYG4sXeRgj6m~XRCnMZo ziM5M<6n}i4*?^cp!?juhBkC|v0;ES~K{kT`cD@H7+d^uR1S%3LhDHUurZ^18zpW048PsJr^WcN_e z%NdKSE-P*DjJ3spxU1Co$fyY;=CW4W4WaO>#P193XkqL`8V{K@fg7sI{6}m;#^PA?J)|1xg-@0K4`NpQ1Oc?s1HIN@p7#{oZ; zjM&u_emy0R#vf{%N&Gdl0ri?Z9-K{t!b@@GR(Pt5g4yO?Nkx&5!gY-}I;l99W6bP^ zy$A-f8$h^%dv#+Rh!xa0C^X!hcq!pCsXFi+KPkB}gNwz&)7w)y`B@Wb%;-mm?~Da@~q?(mOz)c-CcgMUT<-Q&p{jJ_t42?>$3 zrXqcC@5xct7h^pBPwD=0u}X3}ASnk#0vGL%mfM?T&ZJdJD6LAUS>*wHLV>EVVq(Ut%<& z=>C!Y;(4$fD1ZNJ6^ZFdYP}z=JV{jYhInU8Hj=hSEA2OA;S7N3o{Td*ULYIeMbA#g zsU)!k6UZ!z?roU+=h^XiC>p zl9(RCY!W?KK86V=H$@XLI1D$RYNQ9h--a(~P%VzNOqK@f<}FOvi^S%U$Ym@TtH z5dD0<+}?ue^{FGz@XnY(VBVQe*@|_`HBbR!ej?6o z_%!!d-th5Mo)949Rh$b&08GF+gL9vqy{)cRDZQs07%dUidWUMQ!>prxIH_KFsiYDR zxPNBYJ$;5MSDCfTlpK|sWv%dWXZBi^n)!I-r2-^{6@dH2yjf-RupfLeDupU6!XTHA zF!zc_*V-qT1X-Rxq`=?Q@yn#_$y{3OmlS`xfU!ENQpzWTkE&GF68+uE@bSq@t$_@h z0Tj`@Wul|^?H8_;ZmXt`FwP5~uC8?<0w9R7OJ2Ie!E7LKc_}5}Xn(8M z0hQ9d`iqibwN0B~0Q>_4KO2WIZ;qi^f9IKxmLp6j!2p;*TMba}7{NUi0E;Wm1(;so z6Gly*!L>~;P+~kw$n085KK!@1BM!tNqkU+;2hA_Rc2G}(4}ohgzP4JR+n?Sq2uN#| z+a?yW(D?bOd_ocx>Rf;shePgJ(a%amla&WBxXak4xB%dx9O`veo?=wvPg zt{~=#>H;#TPG1DU)gWNDO`#4zn8iuYJ}fWasXRlLfm48B0N0uX;3c)q<_ucx3sPLK zIaD8rJ_If=;S$#|+a0ohN+e=3s8uTbDN;jA6s8kz09@+O(Qji0fUn6ERexK8X;A%& zx={c|;J9tz55Ul{#Xc6Zj%RJy1;Un)V}bVPKvP2a&s#|;Qof2Cn|=L(XoI<%xE4@S z_U6yii}gz5<(Qozw9o^oe_E=xNd~;XYUc~~2(|7fzN(uQHgN~w&@fgpA<^2>8&S)| zr%_XazRC3-gG@f+tmt7#jdjT_(t*uzw^f0wMm4cK!{Y>*NupLOwHHuUDc{3OWAdQB zfqK63Ge}m3b79kNmwaYSr}+S~8c3Yj80uiI4*snnaQ$Og2DWr;=K0KfK94U7;A`M6 ifZOISfZOJq58!XvCJPDkD`Has0000 Date: Mon, 30 Mar 2026 09:54:21 -0500 Subject: [PATCH 10/54] feat: add snapshot manager with persistent history, import/export, and folder filter Persistent snapshot system that auto-captures state before every destructive apply and supports manual named snapshots. Includes rename, per-snapshot and bulk export to JSON, import with dedup, selective restore (tabs/bookmarks/both), and auto-pruning at 20 snapshots. Adds type-to-filter input on the bookmark folders list. --- .../src/background/service-worker.ts | 197 +++++++- apps/extension/src/core/storage.ts | 84 +++- apps/extension/src/shared/constants.ts | 3 + apps/extension/src/shared/messaging.ts | 8 +- apps/extension/src/shared/types.ts | 15 + apps/extension/src/sidepanel/App.tsx | 5 +- .../src/sidepanel/pages/BookmarkOrganizer.tsx | 15 +- .../src/sidepanel/pages/Snapshots.tsx | 454 ++++++++++++++++++ 8 files changed, 772 insertions(+), 9 deletions(-) create mode 100644 apps/extension/src/sidepanel/pages/Snapshots.tsx diff --git a/apps/extension/src/background/service-worker.ts b/apps/extension/src/background/service-worker.ts index 8b8cc33..e9b1f92 100644 --- a/apps/extension/src/background/service-worker.ts +++ b/apps/extension/src/background/service-worker.ts @@ -12,6 +12,8 @@ import type { BookmarkDuplicateGroup, LocationSuggestion, FolderInfo, + Snapshot, + SnapshotType, } from "@/shared/types"; import { getSettings, @@ -23,6 +25,11 @@ import { saveLockedTabGroups, getLockedBookmarkFolders, saveLockedBookmarkFolders, + addSnapshot, + getSnapshotHistory, + deleteSnapshot, + renameSnapshot, + importSnapshots, } from "@/core/storage"; import { queryAllTabs, @@ -186,6 +193,38 @@ async function handleMessage(message: Message): Promise { message.payload as { chromeGroupId: number }, ); + case "get-snapshots": + return { success: true, data: await getSnapshotHistory() }; + + case "create-snapshot": + return await handleCreateSnapshot( + message.payload as { label: string; type: SnapshotType }, + ); + + case "restore-snapshot": + return await handleRestoreSnapshot( + message.payload as { id: string; restoreType: SnapshotType }, + ); + + case "delete-snapshot": + await deleteSnapshot( + (message.payload as { id: string }).id, + ); + return { success: true }; + + case "rename-snapshot": { + const rp = message.payload as { id: string; label: string }; + await renameSnapshot(rp.id, rp.label); + return { success: true }; + } + + case "import-snapshots": { + const imported = await importSnapshots( + (message.payload as { snapshots: Snapshot[] }).snapshots, + ); + return { success: true, data: { imported } }; + } + default: return { success: false, error: `Unknown action: ${message.action}` }; } @@ -266,6 +305,19 @@ async function handleApplyTabSuggestions( })); await setSessionData(STORAGE_KEYS.TAB_SNAPSHOT, snapshot); + // Persistent auto-snapshot for history + await addSnapshot({ + id: crypto.randomUUID(), + timestamp: Date.now(), + type: "tabs", + label: "Before tab organization", + source: "auto", + tabCount: snapshot.length, + bookmarkCount: 0, + tabs: snapshot, + bookmarks: [], + }); + for (const group of result.groups) { const validTabIds = group.tabIds.filter( (id) => !lockedTabIds.has(id) && tabs.some((t) => t.id === id), @@ -636,10 +688,21 @@ async function handleApplyBookmarkSuggestions( // Re-snapshot before applying const tree = await getBookmarkTree(); const bookmarks = flattenBookmarks(tree); - await setSessionData( - STORAGE_KEYS.BOOKMARK_SNAPSHOT, - snapshotBookmarks(bookmarks), - ); + const bmSnapshot = snapshotBookmarks(bookmarks); + await setSessionData(STORAGE_KEYS.BOOKMARK_SNAPSHOT, bmSnapshot); + + // Persistent auto-snapshot for history + await addSnapshot({ + id: crypto.randomUUID(), + timestamp: Date.now(), + type: "bookmarks", + label: "Before bookmark organization", + source: "auto", + tabCount: 0, + bookmarkCount: bmSnapshot.length, + tabs: [], + bookmarks: bmSnapshot, + }); // Build locked set so we never move/remove locked bookmarks const lockedFolders = await getLockedBookmarkFolders(); @@ -714,3 +777,129 @@ async function handleUndoBookmarkChanges(): Promise { await clearSessionData(STORAGE_KEYS.BOOKMARK_SNAPSHOT); return { success: true }; } + +// ─── Snapshot Management ────────────────────────────────────────────── + +async function handleCreateSnapshot( + payload: { label: string; type: SnapshotType }, +): Promise { + let tabs: TabSnapshot[] = []; + let bmSnapshots: BookmarkSnapshot[] = []; + + if (payload.type === "tabs" || payload.type === "both") { + const allTabs = await queryAllTabs(); + tabs = allTabs.map((t) => ({ + id: t.id, + groupId: t.groupId, + windowId: t.windowId, + })); + } + + if (payload.type === "bookmarks" || payload.type === "both") { + const tree = await getBookmarkTree(); + const allBookmarks = flattenBookmarks(tree); + bmSnapshots = snapshotBookmarks(allBookmarks); + } + + await addSnapshot({ + id: crypto.randomUUID(), + timestamp: Date.now(), + type: payload.type, + label: payload.label, + source: "manual", + tabCount: tabs.length, + bookmarkCount: bmSnapshots.length, + tabs, + bookmarks: bmSnapshots, + }); + + return { success: true }; +} + +async function handleRestoreSnapshot( + payload: { id: string; restoreType: SnapshotType }, +): Promise> { + const history = await getSnapshotHistory(); + const snapshot = history.find((s) => s.id === payload.id); + if (!snapshot) { + return { success: false, error: "Snapshot not found" }; + } + + let tabsRestored = 0; + let tabsSkipped = 0; + let bookmarksRestored = 0; + let bookmarksSkipped = 0; + + // Restore tabs + if ( + (payload.restoreType === "tabs" || payload.restoreType === "both") && + snapshot.tabs.length > 0 + ) { + const currentTabs = await queryAllTabs(); + const windowId = currentTabs[0]?.windowId; + + const lockedGroups = + windowId !== undefined ? await resolveLockedGroups(windowId) : []; + const lockedTabIds = getLockedTabIds(lockedGroups, currentTabs); + + // Ungroup all non-locked grouped tabs + const groupedTabIds = currentTabs + .filter((t) => t.groupId !== -1 && !lockedTabIds.has(t.id)) + .map((t) => t.id); + if (groupedTabIds.length > 0) { + await ungroupTabs(groupedTabIds); + } + + // Re-group from snapshot + const groupMap = new Map(); + for (const entry of snapshot.tabs) { + if (entry.groupId !== -1 && !lockedTabIds.has(entry.id)) { + const existing = groupMap.get(entry.groupId); + if (existing) { + existing.push(entry.id); + } else { + groupMap.set(entry.groupId, [entry.id]); + } + } + } + + if (windowId !== undefined) { + for (const tabIds of groupMap.values()) { + const validIds = tabIds.filter((id) => + currentTabs.some((t) => t.id === id), + ); + tabsRestored += validIds.length; + tabsSkipped += tabIds.length - validIds.length; + if (validIds.length > 0) { + await chrome.tabs.group({ + tabIds: validIds as [number, ...number[]], + createProperties: { windowId }, + }); + } + } + } + } + + // Restore bookmarks + if ( + (payload.restoreType === "bookmarks" || payload.restoreType === "both") && + snapshot.bookmarks.length > 0 + ) { + for (const entry of snapshot.bookmarks) { + try { + await moveBookmark(entry.id, { + parentId: entry.parentId, + index: entry.index, + }); + bookmarksRestored++; + } catch { + bookmarksSkipped++; + } + } + } + + return { + success: true, + data: { tabsRestored, tabsSkipped, bookmarksRestored, bookmarksSkipped }, + }; +} diff --git a/apps/extension/src/core/storage.ts b/apps/extension/src/core/storage.ts index 4889098..f7be4c2 100644 --- a/apps/extension/src/core/storage.ts +++ b/apps/extension/src/core/storage.ts @@ -1,5 +1,5 @@ -import { STORAGE_KEYS } from "@/shared/constants"; -import { DEFAULT_SETTINGS, type Settings, type LockedTabGroup, type LockedBookmarkFolder } from "@/shared/types"; +import { STORAGE_KEYS, MAX_SNAPSHOTS } from "@/shared/constants"; +import { DEFAULT_SETTINGS, type Settings, type LockedTabGroup, type LockedBookmarkFolder, type Snapshot } from "@/shared/types"; export async function getSettings(): Promise { const result = await chrome.storage.local.get(STORAGE_KEYS.SETTINGS); @@ -57,3 +57,83 @@ export async function saveLockedTabGroups( [STORAGE_KEYS.LOCKED_TAB_GROUPS]: groups, }); } + +// ─── Snapshot History ───────────────────────────────────────────────── + +export async function getSnapshotHistory(): Promise { + const result = await chrome.storage.local.get(STORAGE_KEYS.SNAPSHOT_HISTORY); + return (result[STORAGE_KEYS.SNAPSHOT_HISTORY] as Snapshot[]) ?? []; +} + +export async function addSnapshot(snapshot: Snapshot): Promise { + const history = await getSnapshotHistory(); + history.push(snapshot); + await chrome.storage.local.set({ + [STORAGE_KEYS.SNAPSHOT_HISTORY]: pruneSnapshots(history, MAX_SNAPSHOTS), + }); +} + +export async function deleteSnapshot(id: string): Promise { + const history = await getSnapshotHistory(); + await chrome.storage.local.set({ + [STORAGE_KEYS.SNAPSHOT_HISTORY]: history.filter((s) => s.id !== id), + }); +} + +export async function renameSnapshot( + id: string, + label: string, +): Promise { + const history = await getSnapshotHistory(); + const snap = history.find((s) => s.id === id); + if (snap) { + snap.label = label; + await chrome.storage.local.set({ + [STORAGE_KEYS.SNAPSHOT_HISTORY]: history, + }); + } +} + +export async function importSnapshots( + incoming: Snapshot[], +): Promise { + const history = await getSnapshotHistory(); + const existingIds = new Set(history.map((s) => s.id)); + const newSnapshots = incoming.filter((s) => !existingIds.has(s.id)); + if (newSnapshots.length === 0) return 0; + const merged = [...history, ...newSnapshots]; + await chrome.storage.local.set({ + [STORAGE_KEYS.SNAPSHOT_HISTORY]: pruneSnapshots(merged, MAX_SNAPSHOTS), + }); + return newSnapshots.length; +} + +function pruneSnapshots(snapshots: Snapshot[], max: number): Snapshot[] { + if (snapshots.length <= max) return snapshots; + + // Remove oldest auto-snapshots first, then oldest manual if still over + const removeIds = new Set(); + let toRemove = snapshots.length - max; + + const auto = snapshots + .filter((s) => s.source === "auto") + .sort((a, b) => a.timestamp - b.timestamp); + for (const s of auto) { + if (toRemove <= 0) break; + removeIds.add(s.id); + toRemove--; + } + + if (toRemove > 0) { + const manual = snapshots + .filter((s) => s.source === "manual") + .sort((a, b) => a.timestamp - b.timestamp); + for (const s of manual) { + if (toRemove <= 0) break; + removeIds.add(s.id); + toRemove--; + } + } + + return snapshots.filter((s) => !removeIds.has(s.id)); +} diff --git a/apps/extension/src/shared/constants.ts b/apps/extension/src/shared/constants.ts index fd88f6a..9ac3e32 100644 --- a/apps/extension/src/shared/constants.ts +++ b/apps/extension/src/shared/constants.ts @@ -4,8 +4,11 @@ export const STORAGE_KEYS = { BOOKMARK_SNAPSHOT: "vxrtx_bookmark_snapshot", LOCKED_TAB_GROUPS: "vxrtx_locked_tab_groups", LOCKED_BOOKMARK_FOLDERS: "vxrtx_locked_bookmark_folders", + SNAPSHOT_HISTORY: "vxrtx_snapshot_history", } as const; +export const MAX_SNAPSHOTS = 20; + export const TAB_GROUP_COLORS: readonly string[] = [ "grey", "blue", diff --git a/apps/extension/src/shared/messaging.ts b/apps/extension/src/shared/messaging.ts index 19bb4b9..b9279d2 100644 --- a/apps/extension/src/shared/messaging.ts +++ b/apps/extension/src/shared/messaging.ts @@ -15,7 +15,13 @@ export type MessageAction = | "lock-bookmark-folder" | "unlock-bookmark-folder" | "get-settings" - | "save-settings"; + | "save-settings" + | "get-snapshots" + | "create-snapshot" + | "restore-snapshot" + | "delete-snapshot" + | "rename-snapshot" + | "import-snapshots"; export interface Message { action: MessageAction; diff --git a/apps/extension/src/shared/types.ts b/apps/extension/src/shared/types.ts index a9ff850..b9ddec2 100644 --- a/apps/extension/src/shared/types.ts +++ b/apps/extension/src/shared/types.ts @@ -134,3 +134,18 @@ export interface BookmarkDuplicateGroup { url: string; bookmarks: BookmarkInfo[]; } + +export type SnapshotSource = "auto" | "manual"; +export type SnapshotType = "tabs" | "bookmarks" | "both"; + +export interface Snapshot { + id: string; + timestamp: number; + type: SnapshotType; + label: string; + source: SnapshotSource; + tabCount: number; + bookmarkCount: number; + tabs: TabSnapshot[]; + bookmarks: BookmarkSnapshot[]; +} diff --git a/apps/extension/src/sidepanel/App.tsx b/apps/extension/src/sidepanel/App.tsx index 8aec0ba..da663c8 100644 --- a/apps/extension/src/sidepanel/App.tsx +++ b/apps/extension/src/sidepanel/App.tsx @@ -1,13 +1,15 @@ import { useState } from "react"; import { TabOrganizer } from "./pages/TabOrganizer"; import { BookmarkOrganizer } from "./pages/BookmarkOrganizer"; +import { Snapshots } from "./pages/Snapshots"; import { Settings } from "./pages/Settings"; -type Page = "tabs" | "bookmarks" | "settings"; +type Page = "tabs" | "bookmarks" | "snapshots" | "settings"; const NAV_ITEMS: { id: Page; label: string }[] = [ { id: "tabs", label: "Tabs" }, { id: "bookmarks", label: "Bookmarks" }, + { id: "snapshots", label: "Snapshots" }, { id: "settings", label: "Settings" }, ]; @@ -35,6 +37,7 @@ export function App() {
{page === "tabs" && } {page === "bookmarks" && } + {page === "snapshots" && } {page === "settings" && }
diff --git a/apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx b/apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx index 144b6c6..abf4a7a 100644 --- a/apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx +++ b/apps/extension/src/sidepanel/pages/BookmarkOrganizer.tsx @@ -47,6 +47,7 @@ export function BookmarkOrganizer() { function ModeMenu({ onSelect }: { onSelect: (mode: Mode) => void }) { const [folders, setFolders] = useState([]); const [lockedIds, setLockedIds] = useState>(new Set()); + const [folderFilter, setFolderFilter] = useState(""); useEffect(() => { loadFoldersAndLocks(); @@ -164,7 +165,19 @@ function ModeMenu({ onSelect }: { onSelect: (mode: Mode) => void }) {

Locked folders are excluded from all organization.

- {folders.map((folder) => { + setFolderFilter(e.target.value)} + placeholder="Filter folders..." + className="w-full rounded-md border border-zinc-800 bg-zinc-900 px-3 py-1.5 text-xs text-zinc-100 placeholder-zinc-600 focus:border-brand-400 focus:outline-none" + /> + {folders.filter((f) => + folderFilter + ? f.title.toLowerCase().includes(folderFilter.toLowerCase()) || + f.path.toLowerCase().includes(folderFilter.toLowerCase()) + : true, + ).map((folder) => { const isLocked = lockedIds.has(folder.id); return (
= { + tabs: "Tabs", + bookmarks: "Bookmarks", + both: "Both", +}; + +const TYPE_COLORS: Record = { + tabs: "bg-blue-900/50 text-blue-300", + bookmarks: "bg-purple-900/50 text-purple-300", + both: "bg-brand-900/50 text-brand-300", +}; + +export function Snapshots() { + const [snapshots, setSnapshots] = useState([]); + const [creating, setCreating] = useState(false); + const [newLabel, setNewLabel] = useState(""); + const [newType, setNewType] = useState("both"); + const [saving, setSaving] = useState(false); + const [expandedId, setExpandedId] = useState(null); + const [restoring, setRestoring] = useState(null); + const [confirmDeleteId, setConfirmDeleteId] = useState(null); + const [renamingId, setRenamingId] = useState(null); + const [renameValue, setRenameValue] = useState(""); + const importRef = useRef(null); + const [message, setMessage] = useState<{ + text: string; + type: "success" | "error"; + } | null>(null); + + const loadSnapshots = useCallback(async () => { + const response = await sendMessage("get-snapshots"); + if (response.success && response.data) { + setSnapshots(response.data); + } + }, []); + + useEffect(() => { + loadSnapshots(); + }, [loadSnapshots]); + + async function handleCreate() { + if (!newLabel.trim()) return; + setSaving(true); + const response = await sendMessage< + { label: string; type: SnapshotType }, + void + >("create-snapshot", { label: newLabel.trim(), type: newType }); + setSaving(false); + if (response.success) { + setCreating(false); + setNewLabel(""); + setNewType("both"); + await loadSnapshots(); + } + } + + async function handleRestore(id: string, restoreType: SnapshotType) { + setRestoring(id); + setMessage(null); + const response = await sendMessage< + { id: string; restoreType: SnapshotType }, + { + tabsRestored: number; + tabsSkipped: number; + bookmarksRestored: number; + bookmarksSkipped: number; + } + >("restore-snapshot", { id, restoreType }); + setRestoring(null); + + if (response.success && response.data) { + const d = response.data; + const parts: string[] = []; + if (d.tabsRestored > 0 || d.tabsSkipped > 0) { + parts.push( + `${d.tabsRestored} tab${d.tabsRestored !== 1 ? "s" : ""} restored${d.tabsSkipped > 0 ? `, ${d.tabsSkipped} skipped` : ""}`, + ); + } + if (d.bookmarksRestored > 0 || d.bookmarksSkipped > 0) { + parts.push( + `${d.bookmarksRestored} bookmark${d.bookmarksRestored !== 1 ? "s" : ""} restored${d.bookmarksSkipped > 0 ? `, ${d.bookmarksSkipped} skipped` : ""}`, + ); + } + setMessage({ + text: parts.join(". ") || "Nothing to restore.", + type: "success", + }); + } else { + setMessage({ + text: response.error ?? "Restore failed", + type: "error", + }); + } + } + + async function handleDelete(id: string) { + await sendMessage<{ id: string }, void>("delete-snapshot", { id }); + setConfirmDeleteId(null); + setExpandedId(null); + await loadSnapshots(); + } + + async function handleRename(id: string) { + if (!renameValue.trim()) return; + await sendMessage<{ id: string; label: string }, void>("rename-snapshot", { + id, + label: renameValue.trim(), + }); + setRenamingId(null); + setRenameValue(""); + await loadSnapshots(); + } + + function handleExport(snap: Snapshot) { + const blob = new Blob([JSON.stringify(snap, null, 2)], { + type: "application/json", + }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `vxrtx-snapshot-${snap.label.replace(/[^a-zA-Z0-9-_]/g, "_")}.json`; + a.click(); + URL.revokeObjectURL(url); + } + + function handleExportAll() { + const blob = new Blob([JSON.stringify(snapshots, null, 2)], { + type: "application/json", + }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `vxrtx-snapshots-${new Date().toISOString().slice(0, 10)}.json`; + a.click(); + URL.revokeObjectURL(url); + } + + async function handleImport(file: File) { + try { + const text = await file.text(); + const parsed = JSON.parse(text); + + // Accept either a single snapshot or an array + const incoming: Snapshot[] = Array.isArray(parsed) ? parsed : [parsed]; + + // Basic validation + for (const s of incoming) { + if (!s.id || !s.timestamp || !s.type || !s.label) { + setMessage({ text: "Invalid snapshot file format.", type: "error" }); + return; + } + } + + const response = await sendMessage< + { snapshots: Snapshot[] }, + { imported: number } + >("import-snapshots", { snapshots: incoming }); + + if (response.success && response.data) { + setMessage({ + text: `Imported ${response.data.imported} snapshot${response.data.imported !== 1 ? "s" : ""}.`, + type: "success", + }); + await loadSnapshots(); + } else { + setMessage({ + text: response.error ?? "Import failed.", + type: "error", + }); + } + } catch { + setMessage({ text: "Failed to read snapshot file.", type: "error" }); + } + } + + return ( +
+
+

Snapshots

+
+ + {snapshots.length > 0 && ( + + )} + +
+
+ + {message && ( +
+ {message.text} + +
+ )} + + {creating && ( +
+ setNewLabel(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && handleCreate()} + className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 placeholder-zinc-500 focus:border-brand-400 focus:outline-none" + autoFocus + /> +
+ Include: + {(["tabs", "bookmarks", "both"] as SnapshotType[]).map((t) => ( + + ))} +
+ +
+ )} + + {snapshots.length === 0 && !creating && ( +

+ No snapshots yet. Snapshots are created automatically before each + apply, or you can create one manually. +

+ )} + +
+ {[...snapshots].reverse().map((snap) => { + const isExpanded = expandedId === snap.id; + const isRestoring = restoring === snap.id; + + return ( +
+ + + {isExpanded && ( +
+ {renamingId === snap.id ? ( +
+ setRenameValue(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") handleRename(snap.id); + if (e.key === "Escape") setRenamingId(null); + }} + className="flex-1 rounded-md border border-zinc-700 bg-zinc-800 px-2 py-1 text-xs text-zinc-100 focus:border-brand-400 focus:outline-none" + autoFocus + /> + + +
+ ) : null} + +
+ {(snap.type === "tabs" || snap.type === "both") && ( + + )} + {(snap.type === "bookmarks" || snap.type === "both") && ( + + )} + {snap.type === "both" && ( + + )} + + + + + {confirmDeleteId === snap.id ? ( +
+ + +
+ ) : ( + + )} +
+
+ )} +
+ ); + })} +
+
+ ); +} From c916d0c2fba105159c3535afe7b021e1822636d3 Mon Sep 17 00:00:00 2001 From: Joshua Butner <53810+heyjawrsh@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:15:11 -0500 Subject: [PATCH 11/54] Ignoring .screens dir --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 754de99..ac9f2ef 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,7 @@ coverage/ # Wrong lockfile (pnpm project) package-lock.json + +# Test / binary data +.screens/ +!.screens/DIRECTIVE.md From e4241dc9ed0c70956c31f190b07182a1ff480076 Mon Sep 17 00:00:00 2001 From: Joshua Butner <53810+heyjawrsh@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:16:14 -0500 Subject: [PATCH 12/54] Adding screenshot directive so that we can always obtain good screens for review, case study, etc --- .screens/DIRECTIVE.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .screens/DIRECTIVE.md diff --git a/.screens/DIRECTIVE.md b/.screens/DIRECTIVE.md new file mode 100644 index 0000000..5dbb6fb --- /dev/null +++ b/.screens/DIRECTIVE.md @@ -0,0 +1,41 @@ +# Screenshot Directive + +Look at the `.screens/` folder and identify the last commit that was screenshotted (the highest-numbered subfolder). Then find all commits after that one in `git log --oneline --reverse`. For each new commit: + +1. Check `git diff .. --stat -- apps/extension/src/sidepanel/ apps/extension/src/styles/` for UI-relevant changes. If no sidepanel/style changes exist, check for icon or CSS changes in the full diff. If there are truly no visual changes, create the numbered subfolder with a single reference screenshot and a `NOTE.md` explaining no UI change. + +2. For commits with UI changes: + - `git checkout ` (stash if needed) + - Build: use `pnpm build:extension` (post-monorepo) or `npm install --legacy-peer-deps && npx vite build` then copy `dist/` to `apps/extension/dist/` (pre-monorepo) + - Serve from `apps/extension/dist/` via `python3 -m http.server 8765` + - Navigate Chrome to `http://localhost:8765/src/sidepanel/index.html` with this initScript to mock Chrome extension APIs: + ```js + const mockEvent = () => ({ addListener: () => {}, removeListener: () => {}, hasListener: () => false }); + const mockStorage = { get: () => Promise.resolve({}), set: () => Promise.resolve(), remove: () => Promise.resolve(), clear: () => Promise.resolve(), onChanged: mockEvent() }; + window.chrome = { + storage: { local: {...mockStorage}, sync: {...mockStorage}, session: {...mockStorage}, onChanged: mockEvent() }, + runtime: { sendMessage: () => Promise.resolve({}), onMessage: mockEvent(), getURL: (p) => p, id: 'mock', lastError: null }, + tabs: { query: () => Promise.resolve([]), onUpdated: mockEvent(), onRemoved: mockEvent(), onCreated: mockEvent(), remove: () => Promise.resolve() }, + tabGroups: { query: () => Promise.resolve([]), onUpdated: mockEvent(), onRemoved: mockEvent(), onCreated: mockEvent(), TAB_GROUP_ID_NONE: -1 }, + bookmarks: { getTree: () => Promise.resolve([{id:'0',title:'',children:[{id:'1',title:'Bookmarks Bar',children:[{id:'10',title:'Dev Tools',children:[{id:'100',title:'GitHub',url:'https://github.com'}]}]},{id:'2',title:'Other Bookmarks',children:[]}]}]), onChanged: mockEvent(), onCreated: mockEvent(), onRemoved: mockEvent(), onMoved: mockEvent(), remove: () => Promise.resolve(), move: () => Promise.resolve(), create: () => Promise.resolve() }, + contextMenus: { create: () => {}, remove: () => Promise.resolve(), removeAll: () => Promise.resolve(), onClicked: mockEvent() }, + sidePanel: {} + }; + ``` + - Emulate viewport `400x700x2` + - Screenshot every distinct screen: each nav tab, each sub-mode, each interactive state (expanded settings, form states, etc.) + - Save to `.screens/NN__/` with descriptive filenames showing position in UI flow + - Use `fullPage: true` for screens with scrollable content + +3. After all commits are captured, return to the original branch and kill the HTTP server. + +## Naming conventions + +- **Subfolder**: sequential two-digit prefix continuing from the last existing folder, then `<7-char hash>_` (e.g., `12_a1b2c3d_add-dark-mode-toggle/`) +- **Screenshots**: sequential two-digit prefix + kebab-case description of what's shown (e.g., `01_tabs-organizer-idle.png`, `03_settings-relaxed-openrouter.png`) + +## Notes + +- Chrome must be running with `--remote-debugging-port=9222` +- If the initScript mock causes errors, check for new Chrome API usage in the diff and extend the mock accordingly +- `chrome.storage.session` was added at commit `483b28d` — the mock above already covers it From d97d4cad3b337e478e0788bbcf28fb2f36ac98da Mon Sep 17 00:00:00 2001 From: Joshua Butner <53810+heyjawrsh@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:27:13 -0500 Subject: [PATCH 13/54] Big UI hugs --- apps/extension/src/sidepanel/App.tsx | 112 +++++++--- .../components/GranularitySlider.tsx | 18 +- .../src/sidepanel/pages/BookmarkOrganizer.tsx | 12 +- .../src/sidepanel/pages/Settings.tsx | 2 +- .../src/sidepanel/pages/Snapshots.tsx | 25 ++- .../src/sidepanel/pages/TabOrganizer.tsx | 14 +- apps/extension/src/styles/globals.css | 200 ++++++++++++++++++ 7 files changed, 329 insertions(+), 54 deletions(-) diff --git a/apps/extension/src/sidepanel/App.tsx b/apps/extension/src/sidepanel/App.tsx index da663c8..e86210a 100644 --- a/apps/extension/src/sidepanel/App.tsx +++ b/apps/extension/src/sidepanel/App.tsx @@ -6,39 +6,101 @@ import { Settings } from "./pages/Settings"; type Page = "tabs" | "bookmarks" | "snapshots" | "settings"; -const NAV_ITEMS: { id: Page; label: string }[] = [ - { id: "tabs", label: "Tabs" }, - { id: "bookmarks", label: "Bookmarks" }, - { id: "snapshots", label: "Snapshots" }, - { id: "settings", label: "Settings" }, +const NAV_ITEMS: { id: Page; label: string; icon: React.ReactNode }[] = [ + { + id: "tabs", + label: "Tabs", + icon: ( + + + + + + ), + }, + { + id: "bookmarks", + label: "Bookmarks", + icon: ( + + + + ), + }, + { + id: "snapshots", + label: "Snapshots", + icon: ( + + + + + + ), + }, + { + id: "settings", + label: "Settings", + icon: ( + + + + + ), + }, ]; export function App() { const [page, setPage] = useState("tabs"); return ( -
-