diff --git a/.gitignore b/.gitignore index 8a7fcd1..d19106e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,68 +1,33 @@ -# Dependencies -node_modules/ -.pnp/ -.pnp.js +test-results +node_modules -# Build outputs -.svelte-kit/ -build/ -dist/ -.nuxt/ -.output/ -.vercel/ -.netlify/ +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build -# Environment files +# OS +.DS_Store +Thumbs.db + +# Env .env -.env.local -.env.development.local -.env.test.local -.env.production.local +.env.* +!.env.example +!.env.test -# IDE and Editor files -.vscode/ -.idea/ -*.swp -*.swo -*~ -.DS_Store -Thumbs.db -*.sublime-project -*.sublime-workspace +# Yarn +.yarn +.pnp.* -# Testing -coverage/ -.nyc_output/ -*.lcov +# Zed +.zed -# Logs -logs/ -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* -# OS files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Temporary files -*.tmp -*.temp -.cache/ -.parcel-cache/ - -# TypeScript -*.tsbuildinfo - -# Misc -*.pid -*.seed -*.pid.lock +/docs diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz new file mode 100644 index 0000000..7f625d0 Binary files /dev/null and b/.yarn/install-state.gz differ diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/GLYPHDIFF_PROJECT_PLAN.md b/GLYPHDIFF_PROJECT_PLAN.md deleted file mode 100644 index 932baeb..0000000 --- a/GLYPHDIFF_PROJECT_PLAN.md +++ /dev/null @@ -1,2997 +0,0 @@ -# GlyphDiff.com - Font Comparison Project Plan - -> **Status:** Learning-Focused MVP | **Tech Stack:** SvelteKit + TypeScript + Tailwind CSS | **Transition Path:** React → Svelte - ---- - -## Table of Contents - -1. [Project Overview](#project-overview) -2. [Executive Summary](#executive-summary) -3. [Architecture Decisions](#architecture-decisions) -4. [Font Provider Integration Strategy](#font-provider-integration-strategy) -5. [Performance Optimization](#performance-optimization) -6. [Frontend Architecture](#frontend-architecture) -7. [Data Models](#data-models) -8. [Implementation Roadmap](#implementation-roadmap) -9. [TanStack Query Research](#tanstack-query-research) -10. [Learning Guide: React → Svelte](#learning-guide-react--svelte) -11. [Scalability & Commercialization](#scalability--commercialization) -12. [Deployment](#deployment) -13. [Risk Assessment](#risk-assessment) -14. [Success Metrics](#success-metrics) -15. [Appendix](#appendix) - ---- - -## Project Overview - -### Vision - -Create a modern, fast, and intuitive web application for comparing fonts side-by-side. The tool will help designers, developers, and typographers make informed decisions about font choices by providing real-time visual comparisons, filtering, and customization options. - -### Target Audience - -- **UI/UX Designers:** Selecting typefaces for web and mobile applications -- **Web Developers:** Choosing fonts for implementation in projects -- **Graphic Designers:** Finding complementary fonts for branding -- **Typography Enthusiasts:** Exploring and comparing font characteristics - -### Core Features (MVP) - -1. **Font Catalog:** Browse available fonts from multiple providers -2. **Side-by-Side Comparison:** Compare up to 4 fonts simultaneously -3. **Custom Preview Text:** Enter custom text for realistic comparison -4. **Filter & Search:** Filter by category, popularity, weight, width -5. **Responsive Design:** Seamless experience across all devices -6. **Dark Mode:** Built-in theme toggle for comfortable viewing -7. **URL Sharing:** Share comparison sessions via shareable URLs - -### Tech Stack - -| Layer | Technology | Purpose | -|-------|-----------|---------| -| Framework | SvelteKit 2.x | Full-stack framework with SSR/SSG support | -| UI Language | Svelte 5 | Reactive component framework | -| Styling | Tailwind CSS 4.x | Utility-first CSS framework | -| Component Library | Bits UI | Accessible Svelte components (Radix UI port) | -| Type Safety | TypeScript 5.x | Static type checking | -| Hosting | Vercel | Edge deployment & preview environments | - -### Project Goals - -#### Learning Goals (Primary) -- Gain proficiency in Svelte 5 and its reactive primitives (`$state`, `$derived`, `$effect`) -- Understand SvelteKit's routing and data loading architecture -- Learn client-side state management patterns in Svelte ecosystem -- Explore performance optimization techniques for static web apps - -#### Project Goals (Secondary) -- Build a functional, visually appealing font comparison tool -- Create a performant, accessible user interface -- Establish a foundation for future commercialization -- Demonstrate mastery of modern frontend development practices - ---- - -## Executive Summary - -### Recommended Approach: Pure Client-Side Architecture - -**Recommendation:** Build the MVP as a pure client-side application using SvelteKit's static site generation (SSG) capabilities, with no backend infrastructure. - -### Key Rationale - -1. **Learning Focus:** Minimize backend complexity to concentrate on Svelte/SvelteKit mastery -2. **Data Availability:** Font metadata is publicly available via Google Fonts API and Fontshare CDN -3. **Performance:** Static generation provides instant page loads and optimal Core Web Vitals -4. **Cost Efficiency:** No server costs during MVP phase -5. **Future Flexibility:** Can easily add backend later when user data collection becomes necessary - -### Architecture at a Glance - -``` -┌─────────────────────────────────────────────────────────────┐ -│ User Browser │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ SvelteKit Client Application │ │ -│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ -│ │ │ Font Store │ │ Filter Store│ │ Comp Store │ │ │ -│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ -│ │ ┌─────────────────────────────────────────────────┐ │ │ -│ │ │ Font Provider Services │ │ │ -│ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ -│ │ │ │ Google Fonts │ │ Fontshare │ │ │ │ -│ │ │ │ API │ │ CDN │ │ │ │ -│ │ │ └──────────────┘ └──────────────┘ │ │ │ -│ │ └─────────────────────────────────────────────────┘ │ │ -│ └─────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ - │ │ - │ HTTP Requests │ CSS Font Files - ▼ ▼ -┌──────────────────┐ ┌──────────────────┐ -│ Google Fonts API │ │ Fontshare CDN │ -└──────────────────┘ └──────────────────┘ -``` - ---- - -## Architecture Decisions - -### Backend Necessity Analysis - -#### Option A: Pure Client-Side (Recommended for MVP) - -**Pros:** -- **Simplicity:** No server code, database, or API endpoints to maintain -- **Performance:** Static sites load faster and score better on Core Web Vitals -- **Cost:** Zero hosting costs on Vercel free tier -- **Learning Focus:** Maximum time spent on Svelte/SvelteKit fundamentals -- **Deployment:** Instant deployments with zero configuration -- **Scalability:** Vercel Edge handles CDN distribution automatically - -**Cons:** -- **No User Data:** Cannot save user preferences or comparison history -- **No Analytics:** Limited to client-side analytics (e.g., Google Analytics) -- **Rate Limiting:** Google Fonts API has usage limits (1,000 requests/day) -- **Caching:** Cannot implement server-side caching strategies -- **Data Freshness:** Font metadata fetched on each session (mitigated by browser caching) - -**Use Cases This Supports:** -- ✅ Browse and compare fonts -- ✅ Share comparisons via URL -- ✅ Filter and search fonts -- ✅ Save preferences locally (localStorage) -- ✅ Responsive, dark mode UI - -**Use Cases This Does NOT Support:** -- ❌ User accounts and saved comparisons -- ❌ Custom font uploads -- ❌ Collaborative comparisons -- ❌ Advanced analytics - ---- - -#### Option B: Hybrid with Serverless Backend - -**Pros:** -- **Caching Layer:** Server-side caching reduces API calls and improves load times -- **Rate Limit Management:** Proxy requests to avoid Google Fonts API limits -- **User Data:** Save user preferences and comparison history -- **Custom Endpoints:** Create specialized API endpoints for font data -- **Preprocessing:** Transform and normalize font data before sending to client - -**Cons:** -- **Complexity:** Additional infrastructure to build and maintain -- **Cost:** Vercel Pro plan may be required for serverless functions -- **Latency:** Server round-trip adds latency to initial page load -- **Development Overhead:** Testing backend code requires additional considerations -- **Learning Curve:** Takes time away from Svelte learning goals - -**Recommended Backend Stack (if needed later):** -- **API Routes:** SvelteKit server endpoints (`+page.server.ts`, `+server.ts`) -- **Database:** Vercel Postgres (Neon) or SQLite (better-sqlite3) -- **Caching:** Vercel KV (Redis) or in-memory caching -- **Auth:** Lucia Auth or Clerk for user authentication - ---- - -### Recommendation - -**Build Option A (Pure Client-Side) for MVP with these considerations:** - -1. **Design for future backend:** Structure data fetching and state management to be easily adapted to server-side later -2. **Implement smart caching:** Use browser localStorage for font metadata cache with TTL -3. **Optimize API usage:** Batch requests and use Google Fonts API efficiently -4. **Monitor usage:** Track API usage and user engagement to inform backend decision -5. **Migration path:** Keep server components clear of logic that could be moved to `+page.server.ts` later - -### When to Add Backend - -Consider adding backend when: -- ✅ User growth exceeds 1,000 daily active users -- ✅ Analytics show users repeatedly saving comparisons manually -- ✅ Feedback indicates desire for user accounts -- ✅ Performance degrades due to repeated API calls -- ✅ Planning to add premium features requiring payment processing - ---- - -## Font Provider Integration Strategy - -### Provider Overview - -| Provider | API Type | License | Font Count | Integration Complexity | -|----------|----------|---------|------------|------------------------| -| Google Fonts | REST API | Open Source (SIL/OFL) | 1,600+ | Low - Official API available | -| Fontshare | GitHub/CDN | Free License | 100+ | Very Low - Static JSON available | - ---- - -### Google Fonts Integration - -#### API Endpoint - -``` -https://www.googleapis.com/webfonts/v1/webfonts?key=YOUR_API_KEY -``` - -#### TypeScript Implementation - -```typescript -// src/lib/services/google-fonts.ts -import type { FontMetadata, FontVariant, FontCategory } from '$lib/types'; - -export interface GoogleFontsResponse { - kind: string; - items: GoogleFontItem[]; -} - -export interface GoogleFontItem { - family: string; - variants: string[]; - subsets: string[]; - version: string; - lastModified: string; - files: Record; - category: 'sans-serif' | 'serif' | 'display' | 'handwriting' | 'monospace'; -} - -export async function fetchGoogleFonts(apiKey: string): Promise { - const response = await fetch( - `https://www.googleapis.com/webfonts/v1/webfonts?key=${apiKey}` - ); - - if (!response.ok) { - throw new Error(`Google Fonts API error: ${response.statusText}`); - } - - const data: GoogleFontsResponse = await response.json(); - - return data.items.map(mapGoogleFontToMetadata); -} - -function mapGoogleFontToMetadata(item: GoogleFontItem): FontMetadata { - const category = mapGoogleCategory(item.category); - - return { - id: `google-${item.family.toLowerCase().replace(/\s+/g, '-')}`, - provider: 'google-fonts', - family: item.family, - displayName: item.family, - category, - variants: mapGoogleVariants(item.variants), - license: { - type: 'open-source', - url: 'https://fonts.google.com/attribution', - attribution: 'Google Fonts', - commercial: true, - embedding: 'webfont' - }, - languages: item.subsets, - previewUrl: getGoogleFontPreviewUrl(item.family), - cssUrl: getGoogleFontCssUrl(item.family, '400'), - metadata: { - version: item.version, - lastModified: item.lastModified, - files: item.files - } - }; -} - -function mapGoogleCategory( - category: GoogleFontItem['category'] -): FontCategory { - const mapping = { - 'sans-serif': 'sans-serif', - 'serif': 'serif', - 'display': 'display', - 'handwriting': 'handwriting', - 'monospace': 'monospace' - } as const; - return mapping[category] || 'display'; -} - -function mapGoogleVariants(variants: string[]): FontVariant[] { - return variants.map(v => { - const weight = parseInt(v) || 400; - const style = v.includes('italic') ? 'italic' : 'normal'; - return { weight, style, name: v }; - }); -} - -function getGoogleFontPreviewUrl(family: string): string { - return `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@400&display=swap`; -} - -function getGoogleFontCssUrl(family: string, weight: string): string { - return `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@${weight}&display=swap`; -} -``` - -#### Dynamic Font Loading - -```typescript -// src/lib/utils/font-loader.ts -let loadedFonts = new Set(); - -export async function loadFont(family: string, weight: number = 400): Promise { - const fontKey = `${family}-${weight}`; - - if (loadedFonts.has(fontKey)) { - return; - } - - const cssUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@${weight}&display=swap`; - - try { - // Create and inject link element - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.href = cssUrl; - link.crossOrigin = 'anonymous'; - - await new Promise((resolve, reject) => { - link.onload = () => { - loadedFonts.add(fontKey); - resolve(); - }; - link.onerror = () => reject(new Error(`Failed to load font: ${family}`)); - document.head.appendChild(link); - }); - } catch (error) { - console.error(`Failed to load font ${family}:`, error); - throw error; - } -} - -export function unloadFont(family: string, weight: number = 400): void { - const cssUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@${weight}&display=swap`; - const links = document.querySelectorAll(`link[href="${cssUrl}"]`); - links.forEach(link => link.remove()); - loadedFonts.delete(`${family}-${weight}`); -} - -export function preloadFonts(fonts: { family: string; weight: number }[]): void { - fonts.forEach(({ family, weight }) => { - const link = document.createElement('link'); - link.rel = 'preload'; - link.as = 'font'; - link.type = 'font/woff2'; - link.href = `https://fonts.gstatic.com/s/${family.toLowerCase()}/v${weight}`; - link.crossOrigin = 'anonymous'; - document.head.appendChild(link); - }); -} -``` - ---- - -### Fontshare Integration - -#### Data Source - -Fontshare provides a static JSON file with all font metadata: - -``` -https://api.fontshare.com/v2/css?f[]= -``` - -Alternative: Use the GitHub repository data or scrape from the website. - -#### TypeScript Implementation - -```typescript -// src/lib/services/fontshare.ts -import type { FontMetadata } from '$lib/types'; - -export interface FontshareMetadata { - family: string; - slug: string; - styles: FontshareStyle[]; - subsets: string[]; - category: string; -} - -export interface FontshareStyle { - name: string; - weight: number; - file: string; - unicodeRange?: string; -} - -// Fontshare static font list (maintained manually or fetched from their docs) -const FONTHARE_FONTS: FontshareMetadata[] = [ - { - family: 'Albert Sans', - slug: 'albert-sans', - styles: [ - { name: 'Regular', weight: 400, file: 'https://api.fontshare.com/v2/css?f[]=albert-sans@400' }, - { name: 'Medium', weight: 500, file: 'https://api.fontshare.com/v2/css?f[]=albert-sans@500' }, - { name: 'Bold', weight: 700, file: 'https://api.fontshare.com/v2/css?f[]=albert-sans@700' } - ], - subsets: ['latin', 'latin-ext'], - category: 'sans-serif' - }, - // Add more Fontshare fonts as needed... -]; - -export async function fetchFontshareFonts(): Promise { - // In a real implementation, you might fetch this from a maintained JSON file - // For MVP, we use the static list or fetch from Fontshare's API - - // Option 1: Use static list - return FONTHARE_FONTS.map(mapFontshareToMetadata); - - // Option 2: Fetch from Fontshare API (if available) - // const response = await fetch('https://api.fontshare.com/v2/fonts'); - // const data = await response.json(); - // return data.map(mapFontshareToMetadata); -} - -function mapFontshareToMetadata(font: FontshareMetadata): FontMetadata { - return { - id: `fontshare-${font.slug}`, - provider: 'fontshare', - family: font.family, - displayName: font.family, - category: mapFontshareCategory(font.category), - variants: font.styles.map(style => ({ - weight: style.weight, - style: 'normal', - name: style.name, - url: style.file - })), - license: { - type: 'open-source', - url: 'https://www.fontshare.com/license', - attribution: 'Fontshare', - commercial: true, - embedding: 'webfont' - }, - languages: font.subsets, - previewUrl: font.styles[0]?.file || '', - cssUrl: font.styles[0]?.file || '', - metadata: { - slug: font.slug, - styles: font.styles - } - }; -} - -function mapFontshareCategory(category: string): FontCategory { - const validCategories: FontCategory[] = ['sans-serif', 'serif', 'display', 'handwriting', 'monospace']; - const normalized = category.toLowerCase().replace(' ', '-'); - return validCategories.includes(normalized as FontCategory) - ? (normalized as FontCategory) - : 'display'; -} -``` - ---- - -### Data Normalization - -Both providers return different data structures. Normalize to a unified interface: - -```typescript -// src/lib/types/fonts.ts -export type FontCategory = 'sans-serif' | 'serif' | 'display' | 'handwriting' | 'monospace'; -export type FontProvider = 'google-fonts' | 'fontshare'; - -export interface LicenseInfo { - type: 'open-source' | 'commercial' | 'free-for-personal'; - url: string; - attribution: string; - commercial: boolean; - embedding: 'webfont' | 'self-host' | 'both'; -} - -export interface FontVariant { - weight: number; - style: 'normal' | 'italic'; - name: string; - url?: string; -} - -export interface FontMetadata { - id: string; - provider: FontProvider; - family: string; - displayName: string; - category: FontCategory; - variants: FontVariant[]; - license: LicenseInfo; - languages: string[]; - previewUrl: string; - cssUrl: string; - popularity?: number; // For sorting - trending?: boolean; - metadata: Record; -} - -export interface ComparisonFont { - font: FontMetadata; - variant: FontVariant; - size: number; - lineHeight: number; - letterSpacing: number; - color: string; - text?: string; // Override default preview text -} - -export interface ComparisonSettings { - text: string; - backgroundColor: string; - showGrid: boolean; - watermark: boolean; -} -``` - ---- - -## Performance Optimization - -### Lazy Loading Strategy - -#### Font Lazy Loading - -```typescript -// src/lib/hooks/useLazyFontLoader.ts -import { tick } from 'svelte'; -import { loadFont } from '$lib/utils/font-loader'; - -export function useLazyFontLoader() { - let observer: IntersectionObserver | null = null; - let elementsToLoad = new Map(); - - function observe( - element: HTMLElement, - family: string, - weight: number = 400 - ) { - if (!observer) { - observer = new IntersectionObserver( - (entries) => { - entries.forEach(async (entry) => { - if (entry.isIntersecting) { - const data = elementsToLoad.get(entry.target as HTMLElement); - if (data) { - await loadFont(data.family, data.weight); - observer?.unobserve(entry.target); - elementsToLoad.delete(entry.target as HTMLElement); - } - } - }); - }, - { - rootMargin: '200px', // Load before element enters viewport - threshold: 0.01 - } - ); - } - - elementsToLoad.set(element, { family, weight }); - observer.observe(element); - } - - function destroy() { - observer?.disconnect(); - observer = null; - elementsToLoad.clear(); - } - - return { observe, destroy }; -} -``` - -#### Component-Level Usage - -```svelte - - - -
- {text} -
- - -``` - -### Caching Strategy - -#### localStorage Caching with TTL - -```typescript -// src/lib/utils/cache.ts -const CACHE_KEY = 'glyphdiff:font-cache'; -const CACHE_VERSION = 1; -const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours - -interface CacheEntry { - version: number; - timestamp: number; - data: unknown; -} - -export function setCache(key: string, data: T, ttl: number = CACHE_TTL): void { - const entry: CacheEntry = { - version: CACHE_VERSION, - timestamp: Date.now(), - data - }; - - const cache = getCacheData(); - cache[key] = entry; - - try { - localStorage.setItem(CACHE_KEY, JSON.stringify(cache)); - } catch (error) { - console.warn('Failed to set cache:', error); - } -} - -export function getCache(key: string): T | null { - const cache = getCacheData(); - const entry = cache[key]; - - if (!entry) { - return null; - } - - // Check version - if (entry.version !== CACHE_VERSION) { - delete cache[key]; - saveCacheData(cache); - return null; - } - - // Check TTL - if (Date.now() - entry.timestamp > CACHE_TTL) { - delete cache[key]; - saveCacheData(cache); - return null; - } - - return entry.data as T; -} - -export function clearCache(key?: string): void { - if (key) { - const cache = getCacheData(); - delete cache[key]; - saveCacheData(cache); - } else { - localStorage.removeItem(CACHE_KEY); - } -} - -function getCacheData(): Record { - try { - const cached = localStorage.getItem(CACHE_KEY); - return cached ? JSON.parse(cached) : {}; - } catch { - return {}; - } -} - -function saveCacheData(data: Record): void { - try { - localStorage.setItem(CACHE_KEY, JSON.stringify(data)); - } catch (error) { - console.warn('Failed to save cache:', error); - } -} -``` - -#### Service Worker Caching (Optional for Enhanced PWA) - -```typescript -// src/lib/service-worker.ts -const CACHE_NAME = 'glyphdiff-v1'; -const FONT_CACHE_PREFIX = 'font-'; - -self.addEventListener('install', (event) => { - event.waitUntil( - caches.open(CACHE_NAME).then((cache) => { - return cache.addAll([ - '/', - '/fonts', - '/compare', - // Add other static assets - ]); - }) - ); -}); - -self.addEventListener('fetch', (event) => { - const url = new URL(event.request.url); - - // Cache Google Fonts - if (url.hostname === 'fonts.googleapis.com' || url.hostname === 'fonts.gstatic.com') { - event.respondWith( - caches.open(`${FONT_CACHE_PREFIX}${CACHE_NAME}`).then((cache) => { - return cache.match(event.request).then((response) => { - if (response) { - return response; - } - return fetch(event.request).then((networkResponse) => { - cache.put(event.request, networkResponse.clone()); - return networkResponse; - }); - }); - }) - ); - return; - } - - // Network-first for API calls - if (url.pathname.includes('/api/')) { - event.respondWith( - fetch(event.request) - .then((response) => { - const responseClone = response.clone(); - caches.open(CACHE_NAME).then((cache) => { - cache.put(event.request, responseClone); - }); - return response; - }) - .catch(() => caches.match(event.request)) - ); - return; - } - - // Cache-first for static assets - event.respondWith( - caches.match(event.request).then((response) => { - return response || fetch(event.request); - }) - ); -}); -``` - -### Performance Metrics - -#### Target Core Web Vitals - -| Metric | Target | Why It Matters | -|--------|--------|----------------| -| LCP (Largest Contentful Paint) | < 2.5s | Main content loads quickly | -| FID (First Input Delay) | < 100ms | Responsive to user input | -| CLS (Cumulative Layout Shift) | < 0.1 | Visual stability | -| TTFB (Time to First Byte) | < 600ms | Fast initial response | - -#### Performance Monitoring - -```typescript -// src/lib/utils/performance.ts -export function reportWebVitals(): void { - if (typeof window === 'undefined' || !('PerformanceObserver' in window)) { - return; - } - - // LCP - const lcpObserver = new PerformanceObserver((list) => { - const entries = list.getEntries(); - const lastEntry = entries[entries.length - 1] as any; - console.log('LCP:', lastEntry.startTime); - // Send to analytics - }); - lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] }); - - // FID - const fidObserver = new PerformanceObserver((list) => { - const entries = list.getEntries(); - const fid = entries[0] as any; - console.log('FID:', fid.processingStart - fid.startTime); - // Send to analytics - }); - fidObserver.observe({ entryTypes: ['first-input'] }); - - // CLS - const clsObserver = new PerformanceObserver((list) => { - let clsValue = 0; - for (const entry of list.getEntries() as any[]) { - if (!entry.hadRecentInput) { - clsValue += entry.value; - } - } - console.log('CLS:', clsValue); - // Send to analytics - }); - clsObserver.observe({ entryTypes: ['layout-shift'] }); -} -``` - ---- - -## Frontend Architecture - -### Project Structure - -``` -glyphdiff/ -├── src/ -│ ├── lib/ -│ │ ├── components/ -│ │ │ ├── FontCard.svelte -│ │ │ ├── FontPreview.svelte -│ │ │ ├── ComparisonGrid.svelte -│ │ │ ├── FilterBar.svelte -│ │ │ └── DarkModeToggle.svelte -│ │ ├── stores/ -│ │ │ ├── fontStore.ts -│ │ │ ├── comparisonStore.ts -│ │ │ ├── filterStore.ts -│ │ │ └── uiStore.ts -│ │ ├── services/ -│ │ │ ├── google-fonts.ts -│ │ │ ├── fontshare.ts -│ │ │ └── font-loader.ts -│ │ ├── hooks/ -│ │ │ ├── useLazyFontLoader.ts -│ │ │ ├── useKeyboardShortcuts.ts -│ │ │ └── useLocalStorage.ts -│ │ ├── utils/ -│ │ │ ├── cache.ts -│ │ │ ├── format.ts -│ │ │ └── url.ts -│ │ ├── types/ -│ │ │ ├── fonts.ts -│ │ │ ├── comparison.ts -│ │ │ └── index.ts -│ │ └── constants.ts -│ ├── routes/ -│ │ ├── +layout.svelte -│ │ ├── +layout.ts -│ │ ├── +page.svelte # Home page -│ │ ├── fonts/ -│ │ │ ├── +page.svelte # Font catalog -│ │ │ ├── +page.ts # Font data loading -│ │ │ └── [slug]/ -│ │ │ └── +page.svelte # Font detail page -│ │ └── compare/ -│ │ ├── +page.svelte # Comparison page -│ │ └── +page.ts # URL state parsing -│ ├── app.css -│ └── app.d.ts -├── static/ -│ ├── favicon.ico -│ └── og-image.png -├── tests/ -│ ├── unit/ -│ └── e2e/ -├── package.json -├── svelte.config.js -├── tailwind.config.js -├── tsconfig.json -└── vite.config.ts -``` - -### Component Architecture - -``` -┌────────────────────────────────────────────────────────────┐ -│ +layout.svelte │ -│ ┌──────────────────────────────────────────────────────┐ │ -│ │ Header │ │ -│ │ Logo | Search | DarkModeToggle │ │ -│ └──────────────────────────────────────────────────────┘ │ -│ ┌──────────────────────────────────────────────────────┐ │ -│ │ Main │ │ -│ │ ┌────────────────────────────────────────────────┐ │ │ -│ │ │ +page.svelte (Home) │ │ │ -│ │ │ Hero section → Featured fonts → CTA │ │ │ -│ │ └────────────────────────────────────────────────┘ │ │ -│ │ ┌────────────────────────────────────────────────┐ │ │ -│ │ │ fonts/+page.svelte │ │ │ -│ │ │ FilterBar → FontGrid → FontCard │ │ │ -│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ -│ │ │ │FontCard │ │FontCard │ │FontCard │ ... │ │ │ -│ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ │ -│ │ └────────────────────────────────────────────────┘ │ │ -│ │ ┌────────────────────────────────────────────────┐ │ │ -│ │ │ compare/+page.svelte │ │ │ -│ │ │ ComparisonGrid → FontPreview × 4 │ │ │ -│ │ │ Toolbar (text, size, color, settings) │ │ │ -│ │ └────────────────────────────────────────────────┘ │ │ -│ └──────────────────────────────────────────────────────┘ │ -│ ┌──────────────────────────────────────────────────────┐ │ -│ │ Footer │ │ -│ └──────────────────────────────────────────────────────┘ │ -└────────────────────────────────────────────────────────────┘ -``` - -### Routing Strategy - -#### SvelteKit File-Based Routing - -| Route | File | Purpose | SSR/CSR | -|-------|------|---------|---------| -| `/` | `routes/+page.svelte` | Home/Landing page | SSR | -| `/fonts` | `routes/fonts/+page.svelte` | Font catalog | SSG | -| `/fonts/[slug]` | `routes/fonts/[slug]/+page.svelte` | Font details | SSG | -| `/compare` | `routes/compare/+page.svelte` | Comparison tool | CSR | -| `/api/...` | `routes/api/+server.ts` | Future API routes | Serverless | - -#### URL State for Comparisons - -```typescript -// src/routes/compare/+page.ts -import type { PageLoad } from './$types'; - -export const load: PageLoad = ({ url }) => { - const searchParams = url.searchParams; - - // Parse comparison state from URL - const fontsParam = searchParams.get('fonts'); - const text = searchParams.get('text') || 'The quick brown fox...'; - const size = parseInt(searchParams.get('size') || '24'); - const darkMode = searchParams.get('dark') === 'true'; - - let comparisonFonts: Array<{ family: string; weight: number }> = []; - - if (fontsParam) { - try { - comparisonFonts = JSON.parse(decodeURIComponent(fontsParam)); - } catch (e) { - console.error('Failed to parse fonts param:', e); - } - } - - return { - initialFonts: comparisonFonts, - settings: { text, size, darkMode } - }; -}; -``` - -```svelte - - -``` - ---- - -## Data Models - -### Core Type Definitions - -```typescript -// src/lib/types/fonts.ts - -/** - * Font provider sources - */ -export type FontProvider = 'google-fonts' | 'fontshare'; - -/** - * Standard font categories - */ -export type FontCategory = - | 'sans-serif' - | 'serif' - | 'display' - | 'handwriting' - | 'monospace'; - -/** - * License information for fonts - */ -export interface LicenseInfo { - /** License type classification */ - type: 'open-source' | 'commercial' | 'free-for-personal'; - - /** URL to full license text */ - url: string; - - /** Attribution required for display */ - attribution: string; - - /** Commercial use permitted */ - commercial: boolean; - - /** Allowed embedding methods */ - embedding: 'webfont' | 'self-host' | 'both'; -} - -/** - * Individual font variant (weight/style combination) - */ -export interface FontVariant { - /** Font weight (100-900) */ - weight: number; - - /** Font style */ - style: 'normal' | 'italic'; - - /** Display name for the variant */ - name: string; - - /** URL to the font file (CSS or WOFF2) */ - url?: string; - - /** Subset information (optional) */ - subset?: string; -} - -/** - * Complete metadata for a font family - */ -export interface FontMetadata { - /** Unique identifier for the font */ - id: string; - - /** Source provider */ - provider: FontProvider; - - /** Font family name (CSS property value) */ - family: string; - - /** Display name for UI */ - displayName: string; - - /** Font category */ - category: FontCategory; - - /** Available variants */ - variants: FontVariant[]; - - /** License information */ - license: LicenseInfo; - - /** Supported language subsets */ - languages: string[]; - - /** URL for font preview (CSS) */ - previewUrl: string; - - /** URL for CSS import */ - cssUrl: string; - - /** Popularity score (for sorting) */ - popularity?: number; - - /** Trending status */ - trending?: boolean; - - /** Additional provider-specific metadata */ - metadata: Record; -} - -/** - * Font with selected variant for display - */ -export interface ActiveFont { - /** Font metadata */ - font: FontMetadata; - - /** Selected variant */ - variant: FontVariant; -} - -/** - * Filter options for font catalog - */ -export interface FontFilters { - /** Category filter */ - categories: FontCategory[]; - - /** Weight range filter */ - weightRange: [number, number]; - - /** Provider filter */ - providers: FontProvider[]; - - /** Language subset filter */ - languages: string[]; - - /** Search query */ - search: string; - - /** Sort option */ - sortBy: 'popularity' | 'name' | 'recent' | 'trending'; - - /** Sort direction */ - sortDirection: 'asc' | 'desc'; -} -``` - -```typescript -// src/lib/types/comparison.ts - -import type { FontMetadata, FontVariant } from './fonts'; - -/** - * Display settings for comparison preview - */ -export interface FontDisplaySettings { - /** Font size in pixels */ - size: number; - - /** Line height (unitless multiplier) */ - lineHeight: number; - - /** Letter spacing in pixels */ - letterSpacing: number; - - /** Text color */ - color: string; - - /** Custom preview text override */ - text?: string; - - /** Text alignment */ - textAlign: 'left' | 'center' | 'right'; -} - -/** - * Global comparison settings - */ -export interface ComparisonSettings { - /** Default preview text */ - text: string; - - /** Background color */ - backgroundColor: string; - - /** Show comparison grid lines */ - showGrid: boolean; - - /** Show watermarks/attribution */ - watermark: boolean; - - /** Default display settings */ - display: FontDisplaySettings; -} - -/** - * Font in a comparison session - */ -export interface ComparisonFont { - /** Font metadata */ - font: FontMetadata; - - /** Selected variant */ - variant: FontVariant; - - /** Display settings (overrides global) */ - settings: Partial; -} - -/** - * Complete comparison session - */ -export interface ComparisonSession { - /** Unique session identifier */ - id: string; - - /** Fonts being compared (max 4) */ - fonts: ComparisonFont[]; - - /** Global comparison settings */ - settings: ComparisonSettings; - - /** Session metadata */ - metadata: { - /** Creation timestamp */ - createdAt: number; - - /** Last modified timestamp */ - updatedAt: number; - - /** Session name */ - name?: string; - - /** Whether session is saved */ - saved: boolean; - }; -} - -/** - * Shareable comparison data (for URL encoding) - */ -export interface ShareableComparison { - /** Font identifiers and weights */ - fonts: Array<{ family: string; weight: number }>; - - /** Preview text */ - text?: string; - - /** Font size */ - size?: number; - - /** Dark mode flag */ - dark?: boolean; -} -``` - -```typescript -// src/lib/types/ui.ts - -/** - * UI theme options - */ -export type Theme = 'light' | 'dark' | 'system'; - -/** - * UI settings persisted in localStorage - */ -export interface UISettings { - /** Selected theme */ - theme: Theme; - - /** Grid view density */ - gridDensity: 'compact' | 'comfortable' | 'spacious'; - - /** Show font variants in catalog */ - showVariants: boolean; - - /** Enable animations */ - animationsEnabled: boolean; -} - -/** - * Toast notification types - */ -export type ToastType = 'success' | 'error' | 'info' | 'warning'; - -/** - * Toast notification - */ -export interface Toast { - id: string; - type: ToastType; - message: string; - duration?: number; - action?: { - label: string; - handler: () => void; - }; -} - -/** - * Keyboard shortcut definition - */ -export interface KeyboardShortcut { - key: string; - ctrl?: boolean; - shift?: boolean; - alt?: boolean; - description: string; - action: () => void; -} - -/** - * Modal/Dialog state - */ -export interface ModalState { - id: string | null; - open: boolean; - props?: Record; -} -``` - -```typescript -// src/lib/types/index.ts - -// Re-export all types for convenient importing -export * from './fonts'; -export * from './comparison'; -export * from './ui'; -``` - ---- - -## Implementation Roadmap - -### Week 1: Svelte Fundamentals - -**Goal:** Build foundational understanding of Svelte 5 reactive primitives and set up development environment. - -#### Tasks - -- [ ] **Project Setup** - - [ ] Create SvelteKit project with TypeScript template - - [ ] Install and configure Tailwind CSS 4.x - - [ ] Install and set up Bits UI component library - - [ ] Configure Prettier and ESLint with Svelte plugin - - [ ] Set up Git repository and .gitignore - -- [ ] **Environment Configuration** - - [ ] Create `.env.example` with GOOGLE_FONTS_API_KEY - - [ ] Add `.env` to `.gitignore` - - [ ] Configure environment variable loading in `vite.config.ts` - -- [ ] **Core Types Definition** - - [ ] Create `src/lib/types/fonts.ts` - - [ ] Create `src/lib/types/comparison.ts` - - [ ] Create `src/lib/types/ui.ts` - - [ ] Create `src/lib/types/index.ts` for re-exports - -- [ ] **Svelte 5 Reactive Primitives Practice** - - [ ] Build Counter component using `$state` - - [ ] Create DerivedCounter component using `$derived` - - [ ] Implement EffectLogger component using `$effect` - - [ ] Practice with reactive arrays and objects - -- [ ] **Practice Components** - - [ ] Build simple Button component - - [ ] Create Toggle component - - [ ] Implement Select dropdown component - - [ ] Build simple FilterBar component - -#### Learning Outcomes -- Understand Svelte 5's new runes system (`$state`, `$derived`, `$effect`) -- Comfortable with Svelte component structure and syntax -- Familiar with Bits UI components -- Tailwind CSS integration working - -#### Week 1 Deliverables -- Working development environment -- 4 practice components demonstrating Svelte reactivity -- Core TypeScript types defined - ---- - -### Week 2: SvelteKit & Routing - -**Goal:** Build font catalog page with routing, data fetching, and understand SvelteKit's SSR capabilities. - -#### Tasks - -- [ ] **Google Fonts Service** - - [ ] Create `fetchGoogleFonts()` function - - [ ] Implement `mapGoogleFontToMetadata()` mapper - - [ ] Add TypeScript interfaces for Google Fonts API response - - [ ] Test with Google Fonts API key - -- [ ] **Fontshare Service** - - [ ] Create `fetchFontshareFonts()` function - - [ ] Build static font list for Fontshare - - [ ] Implement `mapFontshareToMetadata()` mapper - - [ ] Combine both providers into single font catalog - -- [ ] **Font Catalog Page** - - [ ] Create `routes/fonts/+page.svelte` - - [ ] Implement `routes/fonts/+page.ts` for data loading - - [ ] Build FontCard component - - [ ] Create responsive FontGrid layout - -- [ ] **Font Detail Page** - - [ ] Create `routes/fonts/[slug]/+page.svelte` - - [ ] Implement dynamic route parameter extraction - - [ ] Build font detail view with all variants - - [ ] Add "Add to Comparison" button - -- [ ] **Comparison Page Skeleton** - - [ ] Create `routes/compare/+page.svelte` - - [ ] Implement URL state parsing in `+page.ts` - - [ ] Build basic layout for comparison grid - -- [ ] **SSR Understanding** - - [ ] Explore SvelteKit rendering modes (SSR, CSR, SSG) - - [ ] Test page performance with SSR enabled - - [ ] Understand hydration process - - [ ] Configure static generation for font catalog - -#### Learning Outcomes -- Understand SvelteKit file-based routing -- Learn data loading patterns (`+page.ts`, `+page.server.ts`) -- Grasp SSR vs CSR concepts and trade-offs -- Comfortable with dynamic routes and parameters - -#### Week 2 Deliverables -- Working font catalog page with Google Fonts data -- Font detail pages with all metadata -- Basic comparison page with URL state -- Understanding of SvelteKit rendering modes - ---- - -### Week 3: State Management - -**Goal:** Implement comprehensive state management with Svelte stores, localStorage persistence, and URL state synchronization. - -#### Tasks - -- [ ] **Font Store** - - [ ] Create `src/lib/stores/fontStore.ts` - - [ ] Implement `$state` for fonts array - - [ ] Add derived state for filtered fonts - - [ ] Implement localStorage persistence - - [ ] Add loading and error states - -- [ ] **Filter Store** - - [ ] Create `src/lib/stores/filterStore.ts` - - [ ] Implement `$state` for all filter options - - [ ] Add derived state for active filter count - - [ ] Implement reset filters function - - [ ] Add localStorage persistence - -- [ ] **Comparison Store** - - [ ] Create `src/lib/stores/comparisonStore.ts` - - [ ] Implement `$state` for comparison fonts (max 4) - - [ ] Add functions to add/remove fonts - - [ ] Implement comparison settings state - - [ ] Add shareable URL generation - - [ ] Implement localStorage persistence - -- [ ] **UI Store** - - [ ] Create `src/lib/stores/uiStore.ts` - - [ ] Implement theme toggle logic - - [ ] Add settings for grid density, animations - - [ ] Implement system theme detection - - [ ] Add localStorage persistence - -- [ ] **Cache Utility** - - [ ] Implement `cache.ts` with TTL support - - [ ] Add caching to Google Fonts fetcher - - [ ] Implement cache invalidation logic - - [ ] Add cache management UI (optional) - -- [ ] **URL State Integration** - - [ ] Sync comparison state with URL - - [ ] Sync filter state with URL (optional) - - [ ] Implement deep linking to comparisons - - [ ] Handle URL encoding/decoding - -#### Learning Outcomes -- Master Svelte 5 stores with `$state` runes -- Understand localStorage persistence patterns -- Learn URL state synchronization -- Practice derived state and computed values - -#### Week 3 Deliverables -- Fully functional stores for fonts, filters, comparisons, UI -- localStorage persistence working -- URL state synchronization working -- Caching layer implemented - ---- - -### Week 4: UI & Polish - -**Goal:** Build polished UI with Bits UI components, dark mode, responsive design, and smooth animations. - -#### Tasks - -- [ ] **Layout Components** - - [ ] Build Header component with logo and navigation - - [ ] Create Footer component with links and attribution - - [ ] Implement global layout in `+layout.svelte` - - [ ] Add mobile navigation menu - -- [ ] **FilterBar Component** - - [ ] Use Bits UI Select for category filter - - [ ] Use Bits UI Dialog for advanced filters - - [ ] Add search input with debouncing - - [ ] Implement active filter indicators - -- [ ] **ComparisonGrid Component** - - [ ] Use Bits UI Grid for responsive layout - - [ ] Implement drag-and-drop reordering (optional) - - [ ] Add font settings panel per column - - [ ] Implement "Remove font" button - -- [ ] **FontPreview Component** - - [ ] Build preview text area - - [ ] Add size, weight, line-height controls - - [ ] Implement color picker - - [ ] Add lazy loading for fonts - -- [ ] **Dark Mode** - - [ ] Implement dark mode toggle - - [ ] Configure Tailwind dark mode classes - - [ ] Add system preference detection - - [ ] Test color contrast in both themes - -- [ ] **Responsive Design** - - [ ] Test on mobile (375px) - - [ ] Test on tablet (768px) - - [ ] Test on desktop (1280px+) - - [ ] Adjust grid columns for breakpoints - -- [ ] **Animations & Transitions** - - [ ] Add page transitions using Svelte transitions - - [ ] Implement loading skeletons - - [ ] Add hover effects on FontCard - - [ ] Create toast notification system - -- [ ] **Accessibility** - - [ ] Add ARIA labels to interactive elements - - [ ] Ensure keyboard navigation works - - [ ] Test with screen reader - - [ ] Verify color contrast ratios - -- [ ] **Performance Optimization** - - [ ] Implement image/font lazy loading - - [ ] Add code splitting where appropriate - - [ ] Test Lighthouse scores - - [ ] Optimize bundle size - -#### Learning Outcomes -- Proficient with Bits UI component library -- Comfortable building responsive layouts -- Understand accessibility best practices -- Experience with performance optimization - -#### Week 4 Deliverables -- Polished, production-ready UI -- Dark mode fully implemented -- Responsive design across all breakpoints -- Accessibility improvements completed -- Performance targets met - ---- - -## TanStack Query Research - -### What is TanStack Query? - -TanStack Query (formerly React Query) is a powerful data synchronization library for fetching, caching, and managing asynchronous state. It's widely used in the React ecosystem. - -### Key Features - -- **Automatic caching and refetching** -- **Loading and error state management** -- **Optimistic updates** -- **Pagination support** -- **Request deduplication** - -### TanStack Query for Svelte? - -TanStack Query has a Svelte implementation (`@tanstack/svelte-query`) that provides similar functionality to the React version. - -### Why TanStack Query is NOT Recommended for MVP - -| Factor | TanStack Query | SvelteKit Load | Recommendation | -|--------|----------------|----------------|----------------| -| **Data Source** | Client-side API calls | Server-side data loading | SvelteKit Load is sufficient | -| **Complexity** | Additional dependency | Built-in to framework | SvelteKit reduces complexity | -| **SSR Support** | Requires hydration | Native SSR | SvelteKit is cleaner | -| **Learning Curve** | Additional concepts to learn | Learn SvelteKit once | Focus on SvelteKit | -| **Bundle Size** | ~13KB gzipped | 0KB (built-in) | Smaller bundle | -| **Type Safety** | Good | Excellent | SvelteKit with TS is robust | -| **Caching Strategy** | In-memory cache | Static generation + CDN | SSG is more performant | - -### Use Cases Where TanStack Query Would Be Useful - -Consider adding TanStack Query in the future if: -- Implementing user authentication and profile data -- Building real-time features (WebSocket, polling) -- Complex pagination with infinite scroll -- Multiple API endpoints requiring complex cache invalidation -- Offline-first functionality - -### Recommended Data Fetching Approach for MVP - -#### Use SvelteKit's Data Loading - -```typescript -// ✅ Recommended: SvelteKit load function -// src/routes/fonts/+page.ts -import { fetchGoogleFonts, fetchFontshareFonts } from '$lib/services'; -import { setCache, getCache } from '$lib/utils/cache'; - -export const load = async ({ fetch, depends }) => { - // Check cache first - const cached = getCache('font-catalog'); - if (cached) { - return { fonts: cached, cached: true }; - } - - // Fetch from providers - const [googleFonts, fontshareFonts] = await Promise.all([ - fetchGoogleFonts(import.meta.env.VITE_GOOGLE_FONTS_API_KEY), - fetchFontshareFonts() - ]); - - const allFonts = [...googleFonts, ...fontshareFonts]; - - // Cache for 24 hours - setCache('font-catalog', allFonts); - - // Invalidate cache when needed - depends('app:fonts'); - - return { fonts: allFonts, cached: false }; -}; -``` - -#### Use Svelte Stores for Client-Side State - -```typescript -// ✅ Recommended: Svelte 5 stores with $state -// src/lib/stores/fontStore.ts -import { setCache, getCache } from '$lib/utils/cache'; - -class FontStore { - fonts = $state([]); - loading = $state(false); - error = $state(null); - - async loadFonts(apiKey: string) { - this.loading = true; - this.error = null; - - try { - const cached = getCache('font-catalog'); - if (cached) { - this.fonts = cached; - return; - } - - const [googleFonts, fontshareFonts] = await Promise.all([ - fetchGoogleFonts(apiKey), - fetchFontshareFonts() - ]); - - this.fonts = [...googleFonts, ...fontshareFonts]; - setCache('font-catalog', this.fonts); - } catch (e) { - this.error = e as Error; - } finally { - this.loading = false; - } - } -} - -export const fontStore = new FontStore(); -``` - -### Conclusion: Do NOT Use TanStack Query for MVP - -**Reasoning:** -1. SvelteKit's data loading capabilities are sufficient for this project's needs -2. Focus on learning Svelte 5 and SvelteKit rather than adding more dependencies -3. Static generation provides better performance than client-side caching -4. Simpler codebase is easier to maintain while learning - -**Future Consideration:** -Revisit this decision if the project grows to include: -- User authentication -- Real-time features -- Complex client-side data synchronization - ---- - -## Learning Guide: React → Svelte - -### React vs Svelte 5 Comparison - -| React Concept | Svelte 5 Equivalent | Notes | -|--------------|---------------------|-------| -| `useState` | `$state` | No setter function needed, direct reassignment triggers reactivity | -| `useEffect` | `$effect` | Auto-tracks dependencies, no dependency array needed | -| `useMemo` | `$derived` | Computed values that automatically update | -| `useCallback` | Direct function | Functions are auto-memoized in Svelte 5 | -| `children` prop | `` | Slot composition with named slots | -| `props` | `export let` | Props declared with `export let propName` | -| `context` | `setContext` / `getContext` | Similar but with cleaner API | -| `useRef` | `bind:this` | Direct element binding instead of ref objects | -| `useState(prev => prev + 1)` | `count += 1` | Direct mutation works with $state | -| `useReducer` | Custom store with methods | Svelte stores are more flexible | -| `forwardRef` | N/A | Ref forwarding handled automatically | -| `useImperativeHandle` | N/A | Call methods directly on component instances | - -### Code Examples - -#### Counter Component - -**React:** - -```tsx -import { useState, useEffect } from 'react'; - -function Counter({ initial = 0 }: { initial?: number }) { - const [count, setCount] = useState(initial); - const [doubled, setDoubled] = useState(0); - - useEffect(() => { - setDoubled(count * 2); - }, [count]); - - return ( -
-

Count: {count}

-

Doubled: {doubled}

- - -
- ); -} -``` - -**Svelte 5:** - -```svelte - - -
-

Count: {count}

-

Doubled: {doubled}

- - -
-``` - -**Key Differences:** -- No setter function needed - direct mutation works with `$state` -- `$derived` eliminates need for `useEffect` with dependencies -- Functions are defined separately (no need for `useCallback`) -- Event handlers use lowercase `onclick` instead of `onClick` - ---- - -#### Data Fetching - -**React with React Query:** - -```tsx -import { useQuery } from '@tanstack/react-query'; - -function FontList() { - const { - data: fonts, - isLoading, - error - } = useQuery({ - queryKey: ['fonts'], - queryFn: async () => { - const response = await fetch('/api/fonts'); - if (!response.ok) throw new Error('Failed to fetch'); - return response.json(); - } - }); - - if (isLoading) return
Loading...
; - if (error) return
Error: {error.message}
; - - return ( -
    - {fonts.map((font: any) => ( -
  • {font.displayName}
  • - ))} -
- ); -} -``` - -**SvelteKit with load function:** - -```typescript -// +page.ts -export const load = async ({ fetch }) => { - const response = await fetch('https://www.googleapis.com/webfonts/v1/webfonts?key=' + import.meta.env.VITE_GOOGLE_FONTS_API_KEY); - const fonts = await response.json(); - return { fonts }; -}; -``` - -```svelte - - - -{#if data.fonts} -
    - {#each data.fonts as font} -
  • {font.family}
  • - {/each} -
-{/if} -``` - -**Key Differences:** -- SvelteKit loads data on the server (SSR) by default -- No loading state needed for initial page load -- Data flows from `load` function to page component via props - ---- - -#### State Management - -**React with Zustand:** - -```ts -// store.ts -import { create } from 'zustand'; - -interface FontStore { - fonts: FontMetadata[]; - selectedFont: string | null; - setFonts: (fonts: FontMetadata[]) => void; - selectFont: (id: string) => void; -} - -export const useFontStore = create((set) => ({ - fonts: [], - selectedFont: null, - setFonts: (fonts) => set({ fonts }), - selectFont: (id) => set({ selectedFont: id }) -})); -``` - -```tsx -// Component.tsx -import { useFontStore } from './store'; - -function FontSelector() { - const { fonts, selectedFont, selectFont } = useFontStore(); - - return ( - - ); -} -``` - -**Svelte with Store:** - -```ts -// fontStore.ts -import type { FontMetadata } from '$lib/types'; - -class FontStore { - fonts = $state([]); - selectedFont = $state(null); - - setFonts(fonts: FontMetadata[]) { - this.fonts = fonts; - } - - selectFont(id: string) { - this.selectedFont = id; - } -} - -export const fontStore = new FontStore(); -``` - -```svelte - - - - -``` - -**Key Differences:** -- Svelte stores use `$state` directly, no need for immer -- Two-way binding with `bind:value` instead of `onChange` -- Store access is simpler - just import and use - ---- - -#### Conditional Rendering - -**React:** - -```tsx -function ConditionalRender({ fonts, loading }: { fonts: Font[]; loading: boolean }) { - if (loading) { - return ; - } - - if (fonts.length === 0) { - return ; - } - - return ( -
- {fonts.map(font => ( - - ))} -
- ); -} -``` - -**Svelte:** - -```svelte - - -{#if loading} - -{:else if fonts.length === 0} - -{:else} -
- {#each fonts as font} - - {/each} -
-{/if} -``` - -**Key Differences:** -- Svelte uses `{#if}`, `{:else if}`, `{:else}`, `{/if}` block syntax -- No ternary operator needed inside JSX -- `{#each}` block with `{/each}` for lists - ---- - -#### Component Props - -**React:** - -```tsx -interface FontCardProps { - font: FontMetadata; - onClick?: (font: FontMetadata) => void; - size?: number; - showLicense?: boolean; -} - -function FontCard({ font, onClick, size = 16, showLicense = false }: FontCardProps) { - return ( -
-

{font.displayName}

- {showLicense && } - {onClick && } -
- ); -} -``` - -**Svelte:** - -```svelte - - -
-

{font.displayName}

- {#if showLicense} - - {/if} - {#if onClick} - - {/if} -
-``` - -**Key Differences:** -- Props declared with `export let` instead of function parameters -- Default values assigned with `=` in declaration -- Event handlers use lowercase (`onclick`) -- Props passed as attributes without curly braces for simple values - ---- - -#### Slot/Children Composition - -**React:** - -```tsx -// Modal.tsx -function Modal({ children, title, footer }: { - children: React.ReactNode; - title: string; - footer?: React.ReactNode; -}) { - return ( -
-
{title}
-
{children}
- {footer &&
{footer}
} -
- ); -} - -// Usage -Save}> -

Choose a font from the list below.

- -
-``` - -**Svelte:** - -```svelte - - - - - - - - - - -

Choose a font from the list below.

- -
-``` - -**Key Differences:** -- Named slots using `slot="name"` and `` -- Multiple slots with different names -- Default slot uses `` without name -- `` for content without wrapper element - ---- - -### Common Patterns - -#### Form Handling - -**React:** - -```tsx -function SearchForm({ onSearch }: { onSearch: (query: string) => void }) { - const [query, setQuery] = useState(''); - - const handleSubmit = (e: FormEvent) => { - e.preventDefault(); - onSearch(query); - }; - - return ( -
- setQuery(e.target.value)} - placeholder="Search fonts..." - /> - -
- ); -} -``` - -**Svelte:** - -```svelte - - -
- - -
-``` - -**Key Difference:** Two-way binding with `bind:value` eliminates the need for `onChange`. - ---- - -#### Lists with Keys - -**React:** - -```tsx -{fonts.map(font => ( - handleSelect(font)} - /> -))} -``` - -**Svelte:** - -```svelte -{#each fonts as font (font.id)} - handleSelect(font)} - /> -{/each} -``` - -**Key Difference:** Key specified in parentheses after item name: `as item (key)`. - ---- - -#### Lifecycle Methods - -**React:** - -```tsx -useEffect(() => { - console.log('Mounted'); - return () => console.log('Cleanup'); -}, []); - -useEffect(() => { - console.log('count changed:', count); -}, [count]); -``` - -**Svelte:** - -```svelte - -``` - ---- - -## Scalability & Commercialization - -### When to Add Backend - -#### Indicators for Backend Implementation - -| Metric | Threshold | Action | -|--------|-----------|--------| -| Daily Active Users | > 1,000 | Consider backend | -| Google Fonts API Usage | > 800/day (80% of limit) | Implement server proxy | -| Feature Requests | > 10/month for user accounts | Plan authentication | -| Comparison Saves | Users frequently export/save | Add database | -| Page Load Time | > 3s on mobile | Optimize with backend caching | - -#### Backend Architecture (When Needed) - -``` -┌─────────────────────────────────────────────────────────────┐ -│ Vercel Edge │ -│ ┌───────────────────────────────────────────────────────┐ │ -│ │ SvelteKit Server Functions │ │ -│ │ ┌──────────────┐ ┌──────────────┐ │ │ -│ │ │ API Routes │ │ Auth Routes │ │ │ -│ │ └──────────────┘ └──────────────┘ │ │ -│ └───────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ - │ │ - ▼ ▼ -┌──────────────────┐ ┌──────────────────┐ -│ Vercel Postgres │ │ Vercel KV │ -│ (User accounts, │ │ (Caching, │ -│ saved comps) │ │ rate limiting) │ -└──────────────────┘ └──────────────────┘ -``` - -#### Recommended Backend Stack - -| Component | Technology | Why | -|-----------|------------|-----| -| Server Runtime | Vercel Edge Functions | Fast, global, free tier generous | -| Database | Vercel Postgres (Neon) | Managed, edge-ready, TypeScript native | -| Caching | Vercel KV (Redis) | Fast key-value store, built-in | -| Authentication | Lucia Auth | Lightweight, framework-agnostic | -| ORM | Drizzle ORM | TypeScript-first, no runtime overhead | -| File Storage | Vercel Blob | Simple, scalable storage | - ---- - -### Monetization Models - -#### Tier 1: Freemium (Recommended Launch Model) - -| Feature | Free | Pro ($5/mo) | -|---------|------|-------------| -| Font comparisons | 4 fonts max | Unlimited | -| Save comparisons | Browser storage only | Cloud sync | -| Export images | Watermarked | No watermark | -| Custom text | Limited chars | Unlimited | -| Advanced filters | Basic | Advanced | -| API access | ❌ | ✅ | - -#### Tier 2: Team/Agency ($29/mo) - -- All Pro features -- Team collaboration (shared workspaces) -- Brand management (save brand fonts) -- Custom domains for shared comparisons -- Priority support -- Monthly usage reports - -#### Tier 3: Enterprise (Custom) - -- Unlimited API access -- Self-hosted deployment option -- Custom integrations -- SSO authentication -- Dedicated support -- SLA guarantees - ---- - -### Additional Features for Monetization - -1. **Font Pairing Recommendations** - - ML-based font matching - - Designer-curated pairings - - Upload custom fonts for analysis - -2. **CSS Generator** - - Export optimized CSS - - Variable font support - - Fallback font stacks - -3. **Embeddable Widget** - - Embed comparison on external sites - - White-label options - - Custom branding - -4. **Font Inspector** - - Detailed glyph analysis - - Character set viewer - - Font metrics display - -5. **A/B Testing** - - Test font combinations - - User feedback collection - - Conversion tracking - ---- - -## Deployment - -### Vercel Configuration - -#### vercel.json - -```json -{ - "buildCommand": "npm run build", - "devCommand": "npm run dev", - "installCommand": "npm install", - "framework": "sveltekit", - "regions": ["iad1"], - "headers": [ - { - "source": "/(.*)", - "headers": [ - { - "key": "X-Content-Type-Options", - "value": "nosniff" - }, - { - "key": "X-Frame-Options", - "value": "DENY" - }, - { - "key": "X-XSS-Protection", - "value": "1; mode=block" - }, - { - "key": "Referrer-Policy", - "value": "strict-origin-when-cross-origin" - } - ] - }, - { - "source": "/static/(.*)", - "headers": [ - { - "key": "Cache-Control", - "value": "public, max-age=31536000, immutable" - } - ] - }, - { - "source": "/fonts/(.*)", - "headers": [ - { - "key": "Cache-Control", - "value": "public, max-age=604800" - } - ] - } - ], - "rewrites": [ - { - "source": "/api/:path*", - "destination": "/api/:path*" - } - ] -} -``` - ---- - -### Environment Configuration - -#### .env.example - -```bash -# Google Fonts API Key -# Get from: https://developers.google.com/fonts/docs/developer_api -GOOGLE_FONTS_API_KEY=your_api_key_here - -# Application Configuration -APP_URL=https://glyphdiff.com -APP_NAME=GlyphDiff - -# Analytics (Optional) -VITE_GA_ID=G-XXXXXXXXXX -VITE_PLAUSIBLE_DOMAIN= glyphdiff.com - -# Feature Flags -VITE_ENABLE_CACHE=true -VITE_ENABLE_ANALYTICS=false -``` - -#### vite.config.ts - -```typescript -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - plugins: [sveltekit()], - define: { - // Expose env variables to client code - 'import.meta.env.VITE_GOOGLE_FONTS_API_KEY': JSON.stringify(process.env.GOOGLE_FONTS_API_KEY), - } -}); -``` - ---- - -### DNS Configuration - -#### DNS Records for glyphdiff.com - -| Type | Name | Value | TTL | -|------|------|-------|-----| -| A | @ | 76.76.21.21 | 3600 | -| CNAME | www | cname.vercel-dns.com | 3600 | - -**Note:** 76.76.21.21 is Vercel's IPv4 address for custom domains. - ---- - -### Build and Deploy Commands - -```bash -# Install dependencies -npm install - -# Run development server -npm run dev - -# Build for production -npm run build - -# Preview production build -npm run preview - -# Run tests -npm test - -# Run linter -npm run lint - -# Format code -npm run format - -# Type check -npm run check -``` - -#### package.json Scripts - -```json -{ - "name": "glyphdiff", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "lint": "prettier --check . && eslint .", - "format": "prettier --write .", - "test": "playwright test", - "test:unit": "vitest" - }, - "devDependencies": { - "@sveltejs/adapter-vercel": "^5.0.0", - "@sveltejs/kit": "^2.0.0", - "svelte": "^5.0.0", - "typescript": "^5.3.0", - "vite": "^5.0.0" - } -} -``` - ---- - -### Deployment Steps - -1. **Push to GitHub** - ```bash - git init - git add . - git commit -m "Initial commit" - git branch -M main - git remote add origin git@github.com:your-username/glyphdiff.git - git push -u origin main - ``` - -2. **Connect to Vercel** - - Go to [vercel.com](https://vercel.com) - - Click "Add New Project" - - Import from GitHub - - Configure environment variables: - - `GOOGLE_FONTS_API_KEY` - -3. **Configure Custom Domain** - - Go to Project Settings → Domains - - Add `glyphdiff.com` - - Add `www.glyphdiff.com` - - Update DNS records as shown above - -4. **Deploy** - - Push changes to GitHub - - Vercel automatically deploys on push to main - - Preview deployments for pull requests - ---- - -## Risk Assessment - -### Technical Risks - -| Risk | Probability | Impact | Mitigation | -|------|-------------|--------|------------| -| Google Fonts API rate limiting | Medium | High | Implement caching, batch requests, consider backend proxy | -| Browser font loading issues | Low | Medium | Use `document.fonts.load()`, implement fallback fonts | -| Poor mobile performance | Medium | High | Lazy loading, code splitting, test on real devices | -| Accessibility violations | Low | Medium | Regular audits with axe DevTools, keyboard testing | -| Vercel free tier limits | Low | Low | Monitor usage, easy upgrade path | - -### Learning Curve Risks - -| Risk | Probability | Impact | Mitigation | -|------|-------------|--------|------------| -| Difficulty with Svelte 5 runes | Medium | High | Start with simple components, practice with $state basics | -| TypeScript integration issues | Low | Medium | Use strict mode from start, leverage SvelteKit templates | -| CSS/Tailwind complexity | Low | Low | Use utility classes, avoid custom CSS where possible | -| Bits UI learning curve | Low | Medium | Read documentation, check examples, copy patterns | - -### External Dependency Risks - -| Risk | Probability | Impact | Mitigation | -|------|-------------|--------|------------| -| Google Fonts API changes | Low | High | Use TypeScript interfaces, watch for deprecation notices | -| Fontshare availability changes | Low | Low | Include as optional source, can be removed if needed | -| Vercel downtime | Very Low | Medium | Static files served from CDN, minimal impact | -| Bits UI breaking changes | Medium | Low | Lock to specific version, update carefully | - ---- - -## Success Metrics - -### Learning Success Metrics - -| Metric | Target | Measurement | -|--------|--------|-------------| -| Svelte 5 proficiency | Comfortable with $state, $derived, $effect | Self-assessment quiz | -| SvelteKit understanding | Build 3+ routes with data loading | Route count | -| Store management | Implement 4 stores with persistence | Store count | -| Component library usage | Use 5+ Bits UI components | Component count | -| TypeScript adoption | 100% typed codebase | Lint check | - -### Project Success Metrics - -| Metric | Target | Measurement | -|--------|--------|-------------| -| Functional font catalog | 1000+ fonts loaded | Google Fonts API response | -| Comparison tool working | 4 fonts simultaneously | Manual testing | -| Performance score | 90+ Lighthouse score | Lighthouse audit | -| Mobile responsiveness | Pass on 375px - 1280px | Browser DevTools | -| Accessibility | WCAG 2.1 AA compliant | axe DevTools | - -### Milestones - -- [ ] **Week 1 Complete:** Environment setup, basic components, types defined -- [ ] **Week 2 Complete:** Font catalog with data fetching, routing working -- [ ] **Week 3 Complete:** All stores implemented, URL state working -- [ ] **Week 4 Complete:** Polished UI, dark mode, responsive design -- [ ] **MVP Launch:** Application deployed to glyphdiff.com -- [ ] **Post-Launch:** Collect feedback, iterate on features - ---- - -## Appendix - -### Commands Reference - -#### Project Initialization - -```bash -# Create new SvelteKit project -npm create svelte@latest glyphdiff - -# Select options: -# - Which Svelte app template? Skeleton project -# - Add type checking? Yes, using TypeScript -# - Select additional options: ESLint, Prettier, Playwright - -# Navigate to project -cd glyphdiff - -# Install dependencies -npm install - -# Add Tailwind CSS -npx svelte-add@latest tailwindcss - -# Add Bits UI -npm install bits-ui - -# Install additional dependencies -npm install clsx tailwind-merge -``` - -#### Development Commands - -```bash -# Start development server -npm run dev - -# Open browser to localhost:5173 - -# Type check -npm run check - -# Type check in watch mode -npm run check:watch - -# Lint code -npm run lint - -# Format code -npm run format - -# Run Playwright tests -npm run test - -# Run unit tests (Vitest) -npm run test:unit -``` - -#### Build Commands - -```bash -# Build for production -npm run build - -# Preview production build -npm run preview - -# Clean build artifacts -rm -rf .svelte-kit build -``` - -#### Git Commands - -```bash -# Initialize git repo -git init - -# Create .gitignore -echo ".svelte-kit -node_modules -.env -.DS_Store -dist -build -.vercel" > .gitignore - -# Stage all files -git add . - -# Commit -git commit -m "Initial commit" - -# Create main branch -git branch -M main - -# Add remote -git remote add origin git@github.com:username/glyphdiff.git - -# Push to remote -git push -u origin main -``` - ---- - -### Learning Resources - -#### Official Documentation - -- [Svelte 5 Documentation](https://svelte-5-preview.vercel.app/) -- [SvelteKit Documentation](https://kit.svelte.dev/docs) -- [Tailwind CSS Documentation](https://tailwindcss.com/docs) -- [Bits UI Documentation](https://www.bits-ui.com/) - -#### React to Svelte Migration - -- [Svelte for React Developers](https://svelte.dev/docs/svelte-vs-react) -- [Svelte 5 Runes Guide](https://svelte-5-preview.vercel.app/docs/runes-api) -- [SvelteKit Data Loading](https://kit.svelte.dev/docs/load) - -#### Google Fonts API - -- [Google Fonts API Developer Guide](https://developers.google.com/fonts/docs/developer_api) -- [Google Fonts API Reference](https://developers.google.com/fonts/docs/reference/rest) - -#### Video Resources - -- [Svelte 5 Tutorial](https://www.youtube.com/results?search_query=svelte+5+tutorial) -- [SvelteKit Crash Course](https://www.youtube.com/results?search_query=sveltekit+crash+course) -- [Tailwind CSS Full Course](https://www.youtube.com/results?search_query=tailwind+css+full+course) - -#### Community - -- [Svelte Discord](https://svelte.dev/chat) -- [Svelte subreddit](https://reddit.com/r/sveltejs) -- [SvelteKit subreddit](https://reddit.com/r/sveltekit) - ---- - -### File Templates - -#### Component Template - -```svelte - - -
-

{prop}

-

Count: {count}, Doubled: {doubled}

- -
- - -``` - -#### Store Template - -```typescript -import type { FontMetadata } from '$lib/types'; -import { setCache, getCache } from '$lib/utils/cache'; - -class ExampleStore { - // State - items = $state([]); - loading = $state(false); - error = $state(null); - - // Derived state (computed automatically) - get count() { - return this.items.length; - } - - // Methods - async loadItems() { - this.loading = true; - this.error = null; - - try { - // Load from cache first - const cached = getCache('items'); - if (cached) { - this.items = cached; - return; - } - - // Fetch items - // this.items = await fetchItems(); - // setCache('items', this.items); - } catch (e) { - this.error = e as Error; - } finally { - this.loading = false; - } - } - - addItem(item: FontMetadata) { - this.items = [...this.items, item]; - } - - removeItem(id: string) { - this.items = this.items.filter(item => item.id !== id); - } -} - -// Export singleton instance -export const exampleStore = new ExampleStore(); -``` - -#### Load Function Template - -```typescript -// +page.ts or +page.server.ts -import type { PageLoad } from './$types'; - -export const load: PageLoad = async ({ fetch, depends, url }) => { - // Declare dependencies for invalidation - depends('app:data'); - - // Get URL parameters - const search = url.searchParams.get('search') || ''; - - try { - // Fetch data - const response = await fetch('/api/data'); - const data = await response.json(); - - return { - data, - search - }; - } catch (error) { - return { - data: [], - search, - error: error.message - }; - } -}; -``` - ---- - -### Troubleshooting - -#### Common Issues - -**Issue: Google Fonts API returns 403** - -```bash -# Solution: Check API key and rate limits -# 1. Verify API key is correct -# 2. Check API key restrictions (set to None for testing) -# 3. Verify you haven't exceeded daily quota -``` - -**Issue: Tailwind classes not working** - -```bash -# Solution: Check configuration -# 1. Ensure app.css imports tailwind directives -# 2. Restart dev server after config changes -# 3. Check vite.config.ts includes postcss config -``` - -**Issue: Svelte 5 syntax errors** - -```bash -# Solution: Ensure you're using latest Svelte -npm install svelte@next - -# If using VS Code, install Svelte extension -# Ensure it's the latest version for Svelte 5 support -``` - -**Issue: TypeScript errors with $state** - -```bash -# Solution: Update Svelte configuration -# Ensure svelte.config.js includes: -export default { - compilerOptions: { - runes: true - } -}; -``` - -**Issue: Vercel deployment fails** - -```bash -# Solution: Check build output -npm run build - -# Review error logs in Vercel dashboard -# Common issues: -# - Missing environment variables -# - TypeScript errors -# - Build timeout -``` - ---- - -### Performance Checklist - -- [ ] Implement lazy loading for fonts -- [ ] Use SvelteKit static generation where possible -- [ ] Optimize images (use webp format) -- [ ] Minimize JavaScript bundle size -- [ ] Use code splitting for routes -- [ ] Implement caching strategy -- [ ] Enable compression (handled by Vercel) -- [ ] Test Lighthouse scores -- [ ] Minimize layout shifts -- [ ] Use appropriate font formats (WOFF2) - ---- - -## Conclusion - -This project plan provides a comprehensive roadmap for building glyphdiff.com as a learning-focused MVP. The pure client-side approach prioritizes mastering Svelte 5 and SvelteKit while building a functional font comparison tool. - -### Next Steps - -1. Set up the development environment (Week 1 tasks) -2. Obtain Google Fonts API key -3. Configure environment variables -4. Begin implementing Svelte fundamentals - -Good luck on your React → Svelte journey! 🚀 - ---- - -*Document Version: 1.0* -*Last Updated: December 2025* -*Project: glyphdiff.com* diff --git a/README.md b/README.md new file mode 100644 index 0000000..75842c4 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```sh +# create a new project in the current directory +npx sv create + +# create a new project in my-app +npx sv create my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```sh +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```sh +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/components.json b/components.json new file mode 100644 index 0000000..b723574 --- /dev/null +++ b/components.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://shadcn-svelte.com/schema.json", + "tailwind": { + "css": "src/app.css", + "baseColor": "zinc" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils", + "ui": "$lib/components/ui", + "hooks": "$lib/hooks", + "lib": "$lib" + }, + "typescript": true, + "registry": "https://shadcn-svelte.com/registry" +} diff --git a/dprint.json b/dprint.json new file mode 100644 index 0000000..8c057ab --- /dev/null +++ b/dprint.json @@ -0,0 +1,42 @@ +{ + "incremental": true, + "includes": ["**/*.{ts,tsx,js,jsx,svelte,json,md}"], + "excludes": [ + "**/node_modules", + "**/dist", + "**/build", + "**/.svelte-kit", + "**/.vercel" + ], + "plugins": [ + "https://plugins.dprint.dev/typescript-0.93.0.wasm", + "https://plugins.dprint.dev/json-0.19.3.wasm", + "https://plugins.dprint.dev/markdown-0.17.8.wasm", + "https://plugins.dprint.dev/g-plane/markup_fmt-v0.25.3.wasm" + ], + "typescript": { + "lineWidth": 100, + "indentWidth": 4, + "useTabs": false, + "semiColons": "prefer", + "quoteStyle": "preferSingle", + "trailingCommas": "onlyMultiLine", + "arrowFunction.useParentheses": "preferNone", + "importDeclaration.sortNamedImports": "caseInsensitive" + }, + "json": { + "indentWidth": 2, + "useTabs": false + }, + "markdown": { + "lineWidth": 100 + }, + "markup": { + "printWidth": 100, + "indentWidth": 4, + "useTabs": false, + "quotes": "double", + "scriptIndent": true, + "styleIndent": true + } +} diff --git a/e2e/demo.test.ts b/e2e/demo.test.ts new file mode 100644 index 0000000..cad77f2 --- /dev/null +++ b/e2e/demo.test.ts @@ -0,0 +1,6 @@ +import { expect, test } from '@playwright/test'; + +test('home page has expected h1', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('h1')).toBeVisible(); +}); diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..4f98201 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,26 @@ +pre-commit: + parallel: true + commands: + format: + glob: "*.{ts,js,svelte,json,md}" + run: yarn dprint fmt {staged_files} + stage_fixed: true + + lint: + glob: "*.{ts,js,svelte}" + run: yarn oxlint {staged_files} + +pre-push: + parallel: true + commands: + type-check: + run: yarn tsc --noEmit + + svelte-check: + run: yarn svelte-check --threshold warning + + format-check: + run: yarn dprint check + + lint-full: + run: yarn oxlint . diff --git a/oxlint.json b/oxlint.json new file mode 100644 index 0000000..5d0d57b --- /dev/null +++ b/oxlint.json @@ -0,0 +1,27 @@ +{ + "categories": { + "correctness": "error", + "suspicious": "warn", + "perf": "warn", + "style": "warn", + "restriction": "error" + }, + "env": { + "browser": true, + "es2021": true + }, + "ignore": [ + "node_modules", + "dist", + "build", + ".svelte-kit", + ".vercel", + "*.config.js", + "*.config.ts" + ], + "rules": { + "no-console": "off", + "no-debugger": "error", + "no-alert": "warn" + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..77078a6 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "glyphdiff", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "oxlint", + "format": "dprint fmt", + "format:check": "dprint check", + "test:e2e": "playwright test", + "test": "npm run test:e2e" + }, + "devDependencies": { + "@lucide/svelte": "^0.562.0", + "@playwright/test": "^1.57.0", + "@sveltejs/adapter-auto": "^7.0.0", + "@sveltejs/kit": "^2.49.1", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tailwindcss/vite": "^4.1.18", + "clsx": "^2.1.1", + "dprint": "^0.50.2", + "lefthook": "^2.0.13", + "oxlint": "^1.35.0", + "svelte": "^5.45.6", + "svelte-check": "^4.3.4", + "tailwind-merge": "^3.4.0", + "tailwind-variants": "^3.2.2", + "tailwindcss": "^4.1.18", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "vite": "^7.2.6" + } +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..999bd03 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + webServer: { command: 'npm run build && npm run preview', port: 4173 }, + testDir: 'e2e', +}); diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..ee2d8d5 --- /dev/null +++ b/src/app.css @@ -0,0 +1,121 @@ +@import "tailwindcss"; + +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.141 0.005 285.823); + --card: oklch(1 0 0); + --card-foreground: oklch(0.141 0.005 285.823); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.141 0.005 285.823); + --primary: oklch(0.21 0.006 285.885); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.967 0.001 286.375); + --secondary-foreground: oklch(0.21 0.006 285.885); + --muted: oklch(0.967 0.001 286.375); + --muted-foreground: oklch(0.552 0.016 285.938); + --accent: oklch(0.967 0.001 286.375); + --accent-foreground: oklch(0.21 0.006 285.885); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.92 0.004 286.32); + --input: oklch(0.92 0.004 286.32); + --ring: oklch(0.705 0.015 286.067); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.141 0.005 285.823); + --sidebar-primary: oklch(0.21 0.006 285.885); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.967 0.001 286.375); + --sidebar-accent-foreground: oklch(0.21 0.006 285.885); + --sidebar-border: oklch(0.92 0.004 286.32); + --sidebar-ring: oklch(0.705 0.015 286.067); +} + +.dark { + --background: oklch(0.141 0.005 285.823); + --foreground: oklch(0.985 0 0); + --card: oklch(0.21 0.006 285.885); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.21 0.006 285.885); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.92 0.004 286.32); + --primary-foreground: oklch(0.21 0.006 285.885); + --secondary: oklch(0.274 0.006 286.033); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.274 0.006 286.033); + --muted-foreground: oklch(0.705 0.015 286.067); + --accent: oklch(0.274 0.006 286.033); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.552 0.016 285.938); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.21 0.006 285.885); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.274 0.006 286.033); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.552 0.016 285.938); +} + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} \ No newline at end of file diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..d76242a --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..1966776 --- /dev/null +++ b/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/src/lib/assets/favicon.svg b/src/lib/assets/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/src/lib/components/ui/button/button.svelte b/src/lib/components/ui/button/button.svelte new file mode 100644 index 0000000..371540b --- /dev/null +++ b/src/lib/components/ui/button/button.svelte @@ -0,0 +1,85 @@ + + + + +{#if href} + + {@render children?.()} + +{:else} + +{/if} diff --git a/src/lib/components/ui/button/index.ts b/src/lib/components/ui/button/index.ts new file mode 100644 index 0000000..b9e1882 --- /dev/null +++ b/src/lib/components/ui/button/index.ts @@ -0,0 +1,17 @@ +import Root, { + type ButtonProps, + type ButtonSize, + type ButtonVariant, + buttonVariants, +} from './button.svelte'; + +export { + type ButtonProps, + type ButtonProps as Props, + type ButtonSize, + type ButtonVariant, + buttonVariants, + Root, + // + Root as Button, +}; diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..f5acaf9 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,13 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChild = T extends { child?: any } ? Omit : T; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { ref?: U | null }; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..a611c7d --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,11 @@ + + + + + + +{@render children()} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..0bb4a48 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,9 @@ + + +

Welcome to SvelteKit

+

+ Visit svelte.dev/docs/kit to read the documentation +

+ diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..37afe3a --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,22 @@ +import adapter from '@sveltejs/adapter-auto'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), + + compilerOptions: { + runes: true, + }, + + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter(), + }, +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..672ded3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + }, + "exclude": ["./src/lib/components/ui"] + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..f8b704b --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,7 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import tailwindcss from '@tailwindcss/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit(), tailwindcss()], +}); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..33074cd --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2364 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@dprint/darwin-arm64@npm:0.50.2": + version: 0.50.2 + resolution: "@dprint/darwin-arm64@npm:0.50.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@dprint/darwin-x64@npm:0.50.2": + version: 0.50.2 + resolution: "@dprint/darwin-x64@npm:0.50.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@dprint/linux-arm64-glibc@npm:0.50.2": + version: 0.50.2 + resolution: "@dprint/linux-arm64-glibc@npm:0.50.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@dprint/linux-arm64-musl@npm:0.50.2": + version: 0.50.2 + resolution: "@dprint/linux-arm64-musl@npm:0.50.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@dprint/linux-riscv64-glibc@npm:0.50.2": + version: 0.50.2 + resolution: "@dprint/linux-riscv64-glibc@npm:0.50.2" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@dprint/linux-x64-glibc@npm:0.50.2": + version: 0.50.2 + resolution: "@dprint/linux-x64-glibc@npm:0.50.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@dprint/linux-x64-musl@npm:0.50.2": + version: 0.50.2 + resolution: "@dprint/linux-x64-musl@npm:0.50.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@dprint/win32-arm64@npm:0.50.2": + version: 0.50.2 + resolution: "@dprint/win32-arm64@npm:0.50.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@dprint/win32-x64@npm:0.50.2": + version: 0.50.2 + resolution: "@dprint/win32-x64@npm:0.50.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@emnapi/core@npm:^1.7.1": + version: 1.7.1 + resolution: "@emnapi/core@npm:1.7.1" + dependencies: + "@emnapi/wasi-threads": "npm:1.1.0" + tslib: "npm:^2.4.0" + checksum: 10c0/f3740be23440b439333e3ae3832163f60c96c4e35337f3220ceba88f36ee89a57a871d27c94eb7a9ff98a09911ed9a2089e477ab549f4d30029f8b907f84a351 + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.7.1": + version: 1.7.1 + resolution: "@emnapi/runtime@npm:1.7.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/26b851cd3e93877d8732a985a2ebf5152325bbacc6204ef5336a47359dedcc23faeb08cdfcb8bb389b5401b3e894b882bc1a1e55b4b7c1ed1e67c991a760ddd5 + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.1.0, @emnapi/wasi-threads@npm:^1.1.0": + version: 1.1.0 + resolution: "@emnapi/wasi-threads@npm:1.1.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/e6d54bf2b1e64cdd83d2916411e44e579b6ae35d5def0dea61a3c452d9921373044dff32a8b8473ae60c80692bdc39323e98b96a3f3d87ba6886b24dd0ef7ca1 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/aix-ppc64@npm:0.27.2" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm64@npm:0.27.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-arm@npm:0.27.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/android-x64@npm:0.27.2" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-arm64@npm:0.27.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/darwin-x64@npm:0.27.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-arm64@npm:0.27.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/freebsd-x64@npm:0.27.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm64@npm:0.27.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-arm@npm:0.27.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ia32@npm:0.27.2" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-loong64@npm:0.27.2" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-mips64el@npm:0.27.2" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-ppc64@npm:0.27.2" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-riscv64@npm:0.27.2" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-s390x@npm:0.27.2" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/linux-x64@npm:0.27.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-arm64@npm:0.27.2" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/netbsd-x64@npm:0.27.2" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-arm64@npm:0.27.2" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openbsd-x64@npm:0.27.2" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/openharmony-arm64@npm:0.27.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/sunos-x64@npm:0.27.2" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-arm64@npm:0.27.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-ia32@npm:0.27.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.27.2": + version: 0.27.2 + resolution: "@esbuild/win32-x64@npm:0.27.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.0": + version: 5.0.0 + resolution: "@isaacs/brace-expansion@npm:5.0.0" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + +"@jridgewell/remapping@npm:^2.3.4": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + +"@lucide/svelte@npm:^0.562.0": + version: 0.562.0 + resolution: "@lucide/svelte@npm:0.562.0" + peerDependencies: + svelte: ^5 + checksum: 10c0/4886cbae7bf575daf5ee66b26be55a49f6e361672db6eb85f39779c202cd7d4e0d1431f6af710a7045ce9e36e720aef589d78da5807a2f2612c24e54198290bd + languageName: node + linkType: hard + +"@napi-rs/wasm-runtime@npm:^1.1.0": + version: 1.1.0 + resolution: "@napi-rs/wasm-runtime@npm:1.1.0" + dependencies: + "@emnapi/core": "npm:^1.7.1" + "@emnapi/runtime": "npm:^1.7.1" + "@tybys/wasm-util": "npm:^0.10.1" + checksum: 10c0/ee351052123bfc635c4cef03ac273a686522394ccd513b1e5b7b3823cecd6abb4a31f23a3a962933192b87eb7b7c3eb3def7748bd410edc66f932d90cf44e9ab + languageName: node + linkType: hard + +"@npmcli/agent@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/agent@npm:4.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^11.2.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^5.0.0": + version: 5.0.0 + resolution: "@npmcli/fs@npm:5.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/26e376d780f60ff16e874a0ac9bc3399186846baae0b6e1352286385ac134d900cc5dafaded77f38d77f86898fc923ae1cee9d7399f0275b1aa24878915d722b + languageName: node + linkType: hard + +"@oxlint/darwin-arm64@npm:1.36.0": + version: 1.36.0 + resolution: "@oxlint/darwin-arm64@npm:1.36.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@oxlint/darwin-x64@npm:1.36.0": + version: 1.36.0 + resolution: "@oxlint/darwin-x64@npm:1.36.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@oxlint/linux-arm64-gnu@npm:1.36.0": + version: 1.36.0 + resolution: "@oxlint/linux-arm64-gnu@npm:1.36.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@oxlint/linux-arm64-musl@npm:1.36.0": + version: 1.36.0 + resolution: "@oxlint/linux-arm64-musl@npm:1.36.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@oxlint/linux-x64-gnu@npm:1.36.0": + version: 1.36.0 + resolution: "@oxlint/linux-x64-gnu@npm:1.36.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@oxlint/linux-x64-musl@npm:1.36.0": + version: 1.36.0 + resolution: "@oxlint/linux-x64-musl@npm:1.36.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@oxlint/win32-arm64@npm:1.36.0": + version: 1.36.0 + resolution: "@oxlint/win32-arm64@npm:1.36.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@oxlint/win32-x64@npm:1.36.0": + version: 1.36.0 + resolution: "@oxlint/win32-x64@npm:1.36.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@playwright/test@npm:^1.57.0": + version: 1.57.0 + resolution: "@playwright/test@npm:1.57.0" + dependencies: + playwright: "npm:1.57.0" + bin: + playwright: cli.js + checksum: 10c0/35ba4b28be72bf0a53e33dbb11c6cff848fb9a37f49e893ce63a90675b5291ec29a1ba82c8a3b043abaead129400f0589623e9ace2e6a1c8eaa409721ecc3774 + languageName: node + linkType: hard + +"@polka/url@npm:^1.0.0-next.24": + version: 1.0.0-next.29 + resolution: "@polka/url@npm:1.0.0-next.29" + checksum: 10c0/0d58e081844095cb029d3c19a659bfefd09d5d51a2f791bc61eba7ea826f13d6ee204a8a448c2f5a855c17df07b37517373ff916dd05801063c0568ae9937684 + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.54.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-android-arm64@npm:4.54.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.54.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.54.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.54.0" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-freebsd-x64@npm:4.54.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.54.0" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.54.0" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.54.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.54.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.54.0" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.54.0" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.54.0" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.54.0" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.54.0" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.54.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.54.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.54.0" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.54.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.54.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.54.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.54.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@standard-schema/spec@npm:^1.0.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 + languageName: node + linkType: hard + +"@sveltejs/acorn-typescript@npm:^1.0.5": + version: 1.0.8 + resolution: "@sveltejs/acorn-typescript@npm:1.0.8" + peerDependencies: + acorn: ^8.9.0 + checksum: 10c0/3de68af48db0b9cbc82872b218cd9134f494ba7716872e8c11bfdbb156b11dba2205541a627eed943733e4a4e8bbb261fe898bf7659105e1a4c641033fe3d4fe + languageName: node + linkType: hard + +"@sveltejs/adapter-auto@npm:^7.0.0": + version: 7.0.0 + resolution: "@sveltejs/adapter-auto@npm:7.0.0" + peerDependencies: + "@sveltejs/kit": ^2.0.0 + checksum: 10c0/928393d4e366a0094bec5e09c70e44c3c7b5f17687f1735f71eb305e1ae640c64cd9f7d8f80d8b31dcaa235ae2b07e0f021a9204b5cec895d5bd8035064c9195 + languageName: node + linkType: hard + +"@sveltejs/kit@npm:^2.49.1": + version: 2.49.2 + resolution: "@sveltejs/kit@npm:2.49.2" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + "@sveltejs/acorn-typescript": "npm:^1.0.5" + "@types/cookie": "npm:^0.6.0" + acorn: "npm:^8.14.1" + cookie: "npm:^0.6.0" + devalue: "npm:^5.3.2" + esm-env: "npm:^1.2.2" + kleur: "npm:^4.1.5" + magic-string: "npm:^0.30.5" + mrmime: "npm:^2.0.0" + sade: "npm:^1.8.1" + set-cookie-parser: "npm:^2.6.0" + sirv: "npm:^3.0.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + "@sveltejs/vite-plugin-svelte": ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + "@opentelemetry/api": + optional: true + bin: + svelte-kit: svelte-kit.js + checksum: 10c0/05bf6d0d9fb87d894d1a5667f0473d8e83a4584f5c80850ed9eef1d1c8982236ea0d703bbd83befdff30c729d102fc67f2839014d900edb15f6b449ee059b9aa + languageName: node + linkType: hard + +"@sveltejs/vite-plugin-svelte-inspector@npm:^5.0.0": + version: 5.0.1 + resolution: "@sveltejs/vite-plugin-svelte-inspector@npm:5.0.1" + dependencies: + debug: "npm:^4.4.1" + peerDependencies: + "@sveltejs/vite-plugin-svelte": ^6.0.0-next.0 + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + checksum: 10c0/d8bdd22936c60098640c82d12f976e978c1de0a9a801a49f0a438aa4fc8a47847460a7412613151e1e4423b56ab0f436b96b6386f992da5a464e02d8634e72b7 + languageName: node + linkType: hard + +"@sveltejs/vite-plugin-svelte@npm:^6.2.1": + version: 6.2.1 + resolution: "@sveltejs/vite-plugin-svelte@npm:6.2.1" + dependencies: + "@sveltejs/vite-plugin-svelte-inspector": "npm:^5.0.0" + debug: "npm:^4.4.1" + deepmerge: "npm:^4.3.1" + magic-string: "npm:^0.30.17" + vitefu: "npm:^1.1.1" + peerDependencies: + svelte: ^5.0.0 + vite: ^6.3.0 || ^7.0.0 + checksum: 10c0/b521837fbcf33586e1013d3b8b1b2ab20158a3e35ccc9db553b94a8eeb136be1f113705a8d9c1bbb086729fa621721eaa17e354b7f1b5f29818b6244028af26e + languageName: node + linkType: hard + +"@tailwindcss/node@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/node@npm:4.1.18" + dependencies: + "@jridgewell/remapping": "npm:^2.3.4" + enhanced-resolve: "npm:^5.18.3" + jiti: "npm:^2.6.1" + lightningcss: "npm:1.30.2" + magic-string: "npm:^0.30.21" + source-map-js: "npm:^1.2.1" + tailwindcss: "npm:4.1.18" + checksum: 10c0/0527f4cb602a80413a7f135edc9a9c785edd543cceedd046ed2401d4c35c1ec433d5162c325d31ee7248f3560a709dafe30a50c1406662f28a2b3aaeb21f69fe + languageName: node + linkType: hard + +"@tailwindcss/oxide-android-arm64@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/oxide-android-arm64@npm:4.1.18" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@tailwindcss/oxide-darwin-arm64@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/oxide-darwin-arm64@npm:4.1.18" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@tailwindcss/oxide-darwin-x64@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/oxide-darwin-x64@npm:4.1.18" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@tailwindcss/oxide-freebsd-x64@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/oxide-freebsd-x64@npm:4.1.18" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@tailwindcss/oxide-linux-arm-gnueabihf@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/oxide-linux-arm-gnueabihf@npm:4.1.18" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@tailwindcss/oxide-linux-arm64-gnu@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/oxide-linux-arm64-gnu@npm:4.1.18" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@tailwindcss/oxide-linux-arm64-musl@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/oxide-linux-arm64-musl@npm:4.1.18" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@tailwindcss/oxide-linux-x64-gnu@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/oxide-linux-x64-gnu@npm:4.1.18" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@tailwindcss/oxide-linux-x64-musl@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/oxide-linux-x64-musl@npm:4.1.18" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@tailwindcss/oxide-wasm32-wasi@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/oxide-wasm32-wasi@npm:4.1.18" + dependencies: + "@emnapi/core": "npm:^1.7.1" + "@emnapi/runtime": "npm:^1.7.1" + "@emnapi/wasi-threads": "npm:^1.1.0" + "@napi-rs/wasm-runtime": "npm:^1.1.0" + "@tybys/wasm-util": "npm:^0.10.1" + tslib: "npm:^2.4.0" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@tailwindcss/oxide-win32-arm64-msvc@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/oxide-win32-arm64-msvc@npm:4.1.18" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@tailwindcss/oxide-win32-x64-msvc@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/oxide-win32-x64-msvc@npm:4.1.18" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@tailwindcss/oxide@npm:4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/oxide@npm:4.1.18" + dependencies: + "@tailwindcss/oxide-android-arm64": "npm:4.1.18" + "@tailwindcss/oxide-darwin-arm64": "npm:4.1.18" + "@tailwindcss/oxide-darwin-x64": "npm:4.1.18" + "@tailwindcss/oxide-freebsd-x64": "npm:4.1.18" + "@tailwindcss/oxide-linux-arm-gnueabihf": "npm:4.1.18" + "@tailwindcss/oxide-linux-arm64-gnu": "npm:4.1.18" + "@tailwindcss/oxide-linux-arm64-musl": "npm:4.1.18" + "@tailwindcss/oxide-linux-x64-gnu": "npm:4.1.18" + "@tailwindcss/oxide-linux-x64-musl": "npm:4.1.18" + "@tailwindcss/oxide-wasm32-wasi": "npm:4.1.18" + "@tailwindcss/oxide-win32-arm64-msvc": "npm:4.1.18" + "@tailwindcss/oxide-win32-x64-msvc": "npm:4.1.18" + dependenciesMeta: + "@tailwindcss/oxide-android-arm64": + optional: true + "@tailwindcss/oxide-darwin-arm64": + optional: true + "@tailwindcss/oxide-darwin-x64": + optional: true + "@tailwindcss/oxide-freebsd-x64": + optional: true + "@tailwindcss/oxide-linux-arm-gnueabihf": + optional: true + "@tailwindcss/oxide-linux-arm64-gnu": + optional: true + "@tailwindcss/oxide-linux-arm64-musl": + optional: true + "@tailwindcss/oxide-linux-x64-gnu": + optional: true + "@tailwindcss/oxide-linux-x64-musl": + optional: true + "@tailwindcss/oxide-wasm32-wasi": + optional: true + "@tailwindcss/oxide-win32-arm64-msvc": + optional: true + "@tailwindcss/oxide-win32-x64-msvc": + optional: true + checksum: 10c0/1ff978ef24ffae6369e0468bd8c71d1995a00f1697ac1b8f24e92d2d5505ae23534e6257194e78360c16abbe34fc70de508c86d589917336067a60d755b86fcb + languageName: node + linkType: hard + +"@tailwindcss/vite@npm:^4.1.18": + version: 4.1.18 + resolution: "@tailwindcss/vite@npm:4.1.18" + dependencies: + "@tailwindcss/node": "npm:4.1.18" + "@tailwindcss/oxide": "npm:4.1.18" + tailwindcss: "npm:4.1.18" + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + checksum: 10c0/7605364d29cd5683948f7b74f22c5f3b39b89b54d25b3b1094f8300ec6fe9f053f73246170debad86e01080f858d0bf6d0ef8e398a9dc0ce1f5a02c34447726b + languageName: node + linkType: hard + +"@tybys/wasm-util@npm:^0.10.1": + version: 0.10.1 + resolution: "@tybys/wasm-util@npm:0.10.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/b255094f293794c6d2289300c5fbcafbb5532a3aed3a5ffd2f8dc1828e639b88d75f6a376dd8f94347a44813fd7a7149d8463477a9a49525c8b2dcaa38c2d1e8 + languageName: node + linkType: hard + +"@types/cookie@npm:^0.6.0": + version: 0.6.0 + resolution: "@types/cookie@npm:0.6.0" + checksum: 10c0/5b326bd0188120fb32c0be086b141b1481fec9941b76ad537f9110e10d61ee2636beac145463319c71e4be67a17e85b81ca9e13ceb6e3bb63b93d16824d6c149 + languageName: node + linkType: hard + +"@types/estree@npm:1.0.8, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.6": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 + languageName: node + linkType: hard + +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 + languageName: node + linkType: hard + +"acorn@npm:^8.12.1, acorn@npm:^8.14.1": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" + bin: + acorn: bin/acorn + checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe + languageName: node + linkType: hard + +"aria-query@npm:^5.3.1": + version: 5.3.2 + resolution: "aria-query@npm:5.3.2" + checksum: 10c0/003c7e3e2cff5540bf7a7893775fc614de82b0c5dde8ae823d47b7a28a9d4da1f7ed85f340bdb93d5649caa927755f0e31ecc7ab63edfdfc00c8ef07e505e03e + languageName: node + linkType: hard + +"axobject-query@npm:^4.1.0": + version: 4.1.0 + resolution: "axobject-query@npm:4.1.0" + checksum: 10c0/c470e4f95008f232eadd755b018cb55f16c03ccf39c027b941cd8820ac6b68707ce5d7368a46756db4256fbc91bb4ead368f84f7fb034b2b7932f082f6dc0775 + languageName: node + linkType: hard + +"cacache@npm:^20.0.1": + version: 20.0.3 + resolution: "cacache@npm:20.0.3" + dependencies: + "@npmcli/fs": "npm:^5.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^13.0.0" + lru-cache: "npm:^11.1.0" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^13.0.0" + unique-filename: "npm:^5.0.0" + checksum: 10c0/c7da1ca694d20e8f8aedabd21dc11518f809a7d2b59aa76a1fc655db5a9e62379e465c157ddd2afe34b19230808882288effa6911b2de26a088a6d5645123462 + languageName: node + linkType: hard + +"chokidar@npm:^4.0.1": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10c0/a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + +"cookie@npm:^0.6.0": + version: 0.6.0 + resolution: "cookie@npm:0.6.0" + checksum: 10c0/f2318b31af7a31b4ddb4a678d024514df5e705f9be5909a192d7f116cfb6d45cbacf96a473fa733faa95050e7cff26e7832bb3ef94751592f1387b71c8956686 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.3.4, debug@npm:^4.4.1": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"deepmerge@npm:^4.3.1": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.3": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 + languageName: node + linkType: hard + +"devalue@npm:^5.3.2, devalue@npm:^5.5.0": + version: 5.6.1 + resolution: "devalue@npm:5.6.1" + checksum: 10c0/4dca0e800336003fd1e268c142adfe78f3539cda7384b4f69762a93e0dfc33e223b580251da0a6da4be44962958fcba5eadf122f9720e09f437b28904af9c43e + languageName: node + linkType: hard + +"dprint@npm:^0.50.2": + version: 0.50.2 + resolution: "dprint@npm:0.50.2" + dependencies: + "@dprint/darwin-arm64": "npm:0.50.2" + "@dprint/darwin-x64": "npm:0.50.2" + "@dprint/linux-arm64-glibc": "npm:0.50.2" + "@dprint/linux-arm64-musl": "npm:0.50.2" + "@dprint/linux-riscv64-glibc": "npm:0.50.2" + "@dprint/linux-x64-glibc": "npm:0.50.2" + "@dprint/linux-x64-musl": "npm:0.50.2" + "@dprint/win32-arm64": "npm:0.50.2" + "@dprint/win32-x64": "npm:0.50.2" + dependenciesMeta: + "@dprint/darwin-arm64": + optional: true + "@dprint/darwin-x64": + optional: true + "@dprint/linux-arm64-glibc": + optional: true + "@dprint/linux-arm64-musl": + optional: true + "@dprint/linux-riscv64-glibc": + optional: true + "@dprint/linux-x64-glibc": + optional: true + "@dprint/linux-x64-musl": + optional: true + "@dprint/win32-arm64": + optional: true + "@dprint/win32-x64": + optional: true + bin: + dprint: bin.js + checksum: 10c0/07fa9a79dcd72fb5a03cec04981d6152bb4495f3803725a0f920d752fede0102509395f936166015832afd677331b852f08bbb6642b215d0598b98da8d4a1356 + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"enhanced-resolve@npm:^5.18.3": + version: 5.18.4 + resolution: "enhanced-resolve@npm:5.18.4" + dependencies: + graceful-fs: "npm:^4.2.4" + tapable: "npm:^2.2.0" + checksum: 10c0/8f6d42c8a0787a746c493e724c9de5d091cfe8e3f871f2464e2f78a6c55fa1a3aaba495334f923c8ea3ac23e1472491f79feef6fc0fb46a75169cb447ffbe2dc + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"esbuild@npm:^0.27.0": + version: 0.27.2 + resolution: "esbuild@npm:0.27.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.2" + "@esbuild/android-arm": "npm:0.27.2" + "@esbuild/android-arm64": "npm:0.27.2" + "@esbuild/android-x64": "npm:0.27.2" + "@esbuild/darwin-arm64": "npm:0.27.2" + "@esbuild/darwin-x64": "npm:0.27.2" + "@esbuild/freebsd-arm64": "npm:0.27.2" + "@esbuild/freebsd-x64": "npm:0.27.2" + "@esbuild/linux-arm": "npm:0.27.2" + "@esbuild/linux-arm64": "npm:0.27.2" + "@esbuild/linux-ia32": "npm:0.27.2" + "@esbuild/linux-loong64": "npm:0.27.2" + "@esbuild/linux-mips64el": "npm:0.27.2" + "@esbuild/linux-ppc64": "npm:0.27.2" + "@esbuild/linux-riscv64": "npm:0.27.2" + "@esbuild/linux-s390x": "npm:0.27.2" + "@esbuild/linux-x64": "npm:0.27.2" + "@esbuild/netbsd-arm64": "npm:0.27.2" + "@esbuild/netbsd-x64": "npm:0.27.2" + "@esbuild/openbsd-arm64": "npm:0.27.2" + "@esbuild/openbsd-x64": "npm:0.27.2" + "@esbuild/openharmony-arm64": "npm:0.27.2" + "@esbuild/sunos-x64": "npm:0.27.2" + "@esbuild/win32-arm64": "npm:0.27.2" + "@esbuild/win32-ia32": "npm:0.27.2" + "@esbuild/win32-x64": "npm:0.27.2" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/cf83f626f55500f521d5fe7f4bc5871bec240d3deb2a01fbd379edc43b3664d1167428738a5aad8794b35d1cca985c44c375b1cd38a2ca613c77ced2c83aafcd + languageName: node + linkType: hard + +"esm-env@npm:^1.2.1, esm-env@npm:^1.2.2": + version: 1.2.2 + resolution: "esm-env@npm:1.2.2" + checksum: 10c0/3d25c973f2fd69c25ffff29c964399cea573fe10795ecc1d26f6f957ce0483d3254e1cceddb34bf3296a0d7b0f1d53a28992f064ba509dfe6366751e752c4166 + languageName: node + linkType: hard + +"esrap@npm:^2.2.1": + version: 2.2.1 + resolution: "esrap@npm:2.2.1" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.4.15" + checksum: 10c0/bd351c19b6827b69c73f86d9e5bb20fa890911c8e9aaa0581e61a38211346498e8bf4a1ac58811e9306ddbb13cd395db89e0b5302702aba121e8aaf880006f68 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"fdir@npm:^6.2.0, fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fsevents@npm:2.3.2": + version: 2.3.2 + resolution: "fsevents@npm:2.3.2" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/be78a3efa3e181cda3cf7a4637cb527bcebb0bd0ea0440105a3bb45b86f9245b307dc10a2507e8f4498a7d4ec349d1910f4d73e4d4495b16103106e07eee735b + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin": + version: 2.3.2 + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"glob@npm:^13.0.0": + version: 13.0.0 + resolution: "glob@npm:13.0.0" + dependencies: + minimatch: "npm:^10.1.1" + minipass: "npm:^7.1.2" + path-scurry: "npm:^2.0.0" + checksum: 10c0/8e2f5821f3f7c312dd102e23a15b80c79e0837a9872784293ba2e15ec73b3f3749a49a42a31bfcb4e52c84820a474e92331c2eebf18819d20308f5c33876630a + languageName: node + linkType: hard + +"glyphdiff@workspace:.": + version: 0.0.0-use.local + resolution: "glyphdiff@workspace:." + dependencies: + "@lucide/svelte": "npm:^0.562.0" + "@playwright/test": "npm:^1.57.0" + "@sveltejs/adapter-auto": "npm:^7.0.0" + "@sveltejs/kit": "npm:^2.49.1" + "@sveltejs/vite-plugin-svelte": "npm:^6.2.1" + "@tailwindcss/vite": "npm:^4.1.18" + clsx: "npm:^2.1.1" + dprint: "npm:^0.50.2" + lefthook: "npm:^2.0.13" + oxlint: "npm:^1.35.0" + svelte: "npm:^5.45.6" + svelte-check: "npm:^4.3.4" + tailwind-merge: "npm:^3.4.0" + tailwind-variants: "npm:^3.2.2" + tailwindcss: "npm:^4.1.18" + tw-animate-css: "npm:^1.4.0" + typescript: "npm:^5.9.3" + vite: "npm:^7.2.6" + languageName: unknown + linkType: soft + +"graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"ip-address@npm:^10.0.1": + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 + languageName: node + linkType: hard + +"is-reference@npm:^3.0.3": + version: 3.0.3 + resolution: "is-reference@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.6" + checksum: 10c0/35edd284cfb4cd9e9f08973f20e276ec517eaca31f5f049598e97dbb2d05544973dde212dac30fddee5b420930bff365e2e67dcd1293d0866c6720377382e3e5 + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 + languageName: node + linkType: hard + +"jiti@npm:^2.6.1": + version: 2.6.1 + resolution: "jiti@npm:2.6.1" + bin: + jiti: lib/jiti-cli.mjs + checksum: 10c0/79b2e96a8e623f66c1b703b98ec1b8be4500e1d217e09b09e343471bbb9c105381b83edbb979d01cef18318cc45ce6e153571b6c83122170eefa531c64b6789b + languageName: node + linkType: hard + +"kleur@npm:^4.1.5": + version: 4.1.5 + resolution: "kleur@npm:4.1.5" + checksum: 10c0/e9de6cb49657b6fa70ba2d1448fd3d691a5c4370d8f7bbf1c2f64c24d461270f2117e1b0afe8cb3114f13bbd8e51de158c2a224953960331904e636a5e4c0f2a + languageName: node + linkType: hard + +"lefthook-darwin-arm64@npm:2.0.13": + version: 2.0.13 + resolution: "lefthook-darwin-arm64@npm:2.0.13" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lefthook-darwin-x64@npm:2.0.13": + version: 2.0.13 + resolution: "lefthook-darwin-x64@npm:2.0.13" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lefthook-freebsd-arm64@npm:2.0.13": + version: 2.0.13 + resolution: "lefthook-freebsd-arm64@npm:2.0.13" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"lefthook-freebsd-x64@npm:2.0.13": + version: 2.0.13 + resolution: "lefthook-freebsd-x64@npm:2.0.13" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lefthook-linux-arm64@npm:2.0.13": + version: 2.0.13 + resolution: "lefthook-linux-arm64@npm:2.0.13" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"lefthook-linux-x64@npm:2.0.13": + version: 2.0.13 + resolution: "lefthook-linux-x64@npm:2.0.13" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"lefthook-openbsd-arm64@npm:2.0.13": + version: 2.0.13 + resolution: "lefthook-openbsd-arm64@npm:2.0.13" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"lefthook-openbsd-x64@npm:2.0.13": + version: 2.0.13 + resolution: "lefthook-openbsd-x64@npm:2.0.13" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"lefthook-windows-arm64@npm:2.0.13": + version: 2.0.13 + resolution: "lefthook-windows-arm64@npm:2.0.13" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"lefthook-windows-x64@npm:2.0.13": + version: 2.0.13 + resolution: "lefthook-windows-x64@npm:2.0.13" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lefthook@npm:^2.0.13": + version: 2.0.13 + resolution: "lefthook@npm:2.0.13" + dependencies: + lefthook-darwin-arm64: "npm:2.0.13" + lefthook-darwin-x64: "npm:2.0.13" + lefthook-freebsd-arm64: "npm:2.0.13" + lefthook-freebsd-x64: "npm:2.0.13" + lefthook-linux-arm64: "npm:2.0.13" + lefthook-linux-x64: "npm:2.0.13" + lefthook-openbsd-arm64: "npm:2.0.13" + lefthook-openbsd-x64: "npm:2.0.13" + lefthook-windows-arm64: "npm:2.0.13" + lefthook-windows-x64: "npm:2.0.13" + dependenciesMeta: + lefthook-darwin-arm64: + optional: true + lefthook-darwin-x64: + optional: true + lefthook-freebsd-arm64: + optional: true + lefthook-freebsd-x64: + optional: true + lefthook-linux-arm64: + optional: true + lefthook-linux-x64: + optional: true + lefthook-openbsd-arm64: + optional: true + lefthook-openbsd-x64: + optional: true + lefthook-windows-arm64: + optional: true + lefthook-windows-x64: + optional: true + bin: + lefthook: bin/index.js + checksum: 10c0/11dc5a952c24325b19803c261331f1e12ea8eaf31c0ab131815955a6e7210323828995c2b9cc73b79c23de960beda27ab71de88afdf00e40f367c6b13cee1ce3 + languageName: node + linkType: hard + +"lightningcss-android-arm64@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-android-arm64@npm:1.30.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-darwin-arm64@npm:1.30.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-darwin-x64@npm:1.30.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-freebsd-x64@npm:1.30.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.30.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-linux-arm64-gnu@npm:1.30.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-linux-arm64-musl@npm:1.30.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-linux-x64-gnu@npm:1.30.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-linux-x64-musl@npm:1.30.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-win32-arm64-msvc@npm:1.30.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss-win32-x64-msvc@npm:1.30.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:1.30.2": + version: 1.30.2 + resolution: "lightningcss@npm:1.30.2" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.30.2" + lightningcss-darwin-arm64: "npm:1.30.2" + lightningcss-darwin-x64: "npm:1.30.2" + lightningcss-freebsd-x64: "npm:1.30.2" + lightningcss-linux-arm-gnueabihf: "npm:1.30.2" + lightningcss-linux-arm64-gnu: "npm:1.30.2" + lightningcss-linux-arm64-musl: "npm:1.30.2" + lightningcss-linux-x64-gnu: "npm:1.30.2" + lightningcss-linux-x64-musl: "npm:1.30.2" + lightningcss-win32-arm64-msvc: "npm:1.30.2" + lightningcss-win32-x64-msvc: "npm:1.30.2" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/5c0c73a33946dab65908d5cd1325df4efa290efb77f940b60f40448b5ab9a87d3ea665ef9bcf00df4209705050ecf2f7ecc649f44d6dfa5905bb50f15717e78d + languageName: node + linkType: hard + +"locate-character@npm:^3.0.0": + version: 3.0.0 + resolution: "locate-character@npm:3.0.0" + checksum: 10c0/9da917622395002eb1336fca8cbef1c19904e3dc0b3b8078abe8ff390106d947a86feccecd0346f0e0e19fa017623fb4ccb65263d72a76dfa36e20cc18766b6c + languageName: node + linkType: hard + +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": + version: 11.2.4 + resolution: "lru-cache@npm:11.2.4" + checksum: 10c0/4a24f9b17537619f9144d7b8e42cd5a225efdfd7076ebe7b5e7dc02b860a818455201e67fbf000765233fe7e339d3c8229fc815e9b58ee6ede511e07608c19b2 + languageName: node + linkType: hard + +"magic-string@npm:^0.30.11, magic-string@npm:^0.30.17, magic-string@npm:^0.30.21, magic-string@npm:^0.30.5": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + languageName: node + linkType: hard + +"make-fetch-happen@npm:^15.0.0": + version: 15.0.3 + resolution: "make-fetch-happen@npm:15.0.3" + dependencies: + "@npmcli/agent": "npm:^4.0.0" + cacache: "npm:^20.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^5.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^6.0.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^13.0.0" + checksum: 10c0/525f74915660be60b616bcbd267c4a5b59481b073ba125e45c9c3a041bb1a47a2bd0ae79d028eb6f5f95bf9851a4158423f5068539c3093621abb64027e8e461 + languageName: node + linkType: hard + +"minimatch@npm:^10.1.1": + version: 10.1.1 + resolution: "minimatch@npm:10.1.1" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10c0/c85d44821c71973d636091fddbfbffe62370f5ee3caf0241c5b60c18cd289e916200acb2361b7e987558cd06896d153e25d505db9fc1e43e6b4b6752e2702902 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass-fetch@npm:5.0.0" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^3.0.1" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/9443aab5feab190972f84b64116e54e58dd87a58e62399cae0a4a7461b80568281039b7c3a38ba96453431ebc799d1e26999e548540156216729a4967cd5ef06 + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"mri@npm:^1.1.0": + version: 1.2.0 + resolution: "mri@npm:1.2.0" + checksum: 10c0/a3d32379c2554cf7351db6237ddc18dc9e54e4214953f3da105b97dc3babe0deb3ffe99cf409b38ea47cc29f9430561ba6b53b24ab8f9ce97a4b50409e4a50e7 + languageName: node + linkType: hard + +"mrmime@npm:^2.0.0": + version: 2.0.1 + resolution: "mrmime@npm:2.0.1" + checksum: 10c0/af05afd95af202fdd620422f976ad67dc18e6ee29beb03dd1ce950ea6ef664de378e44197246df4c7cdd73d47f2e7143a6e26e473084b9e4aa2095c0ad1e1761 + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 12.1.0 + resolution: "node-gyp@npm:12.1.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^15.0.0" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.2" + tinyglobby: "npm:^0.2.12" + which: "npm:^6.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/f43efea8aaf0beb6b2f6184e533edad779b2ae38062953e21951f46221dd104006cc574154f2ad4a135467a5aae92c49e84ef289311a82e08481c5df0e8dc495 + languageName: node + linkType: hard + +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" + dependencies: + abbrev: "npm:^4.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd + languageName: node + linkType: hard + +"oxlint@npm:^1.35.0": + version: 1.36.0 + resolution: "oxlint@npm:1.36.0" + dependencies: + "@oxlint/darwin-arm64": "npm:1.36.0" + "@oxlint/darwin-x64": "npm:1.36.0" + "@oxlint/linux-arm64-gnu": "npm:1.36.0" + "@oxlint/linux-arm64-musl": "npm:1.36.0" + "@oxlint/linux-x64-gnu": "npm:1.36.0" + "@oxlint/linux-x64-musl": "npm:1.36.0" + "@oxlint/win32-arm64": "npm:1.36.0" + "@oxlint/win32-x64": "npm:1.36.0" + peerDependencies: + oxlint-tsgolint: ">=0.10.0" + dependenciesMeta: + "@oxlint/darwin-arm64": + optional: true + "@oxlint/darwin-x64": + optional: true + "@oxlint/linux-arm64-gnu": + optional: true + "@oxlint/linux-arm64-musl": + optional: true + "@oxlint/linux-x64-gnu": + optional: true + "@oxlint/linux-x64-musl": + optional: true + "@oxlint/win32-arm64": + optional: true + "@oxlint/win32-x64": + optional: true + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + bin: + oxc_language_server: bin/oxc_language_server + oxlint: bin/oxlint + checksum: 10c0/e480ede331a2be74f879e378bea9eae9e5561064ea81938cf68c525ef3295633100c92f7f045730eae8561da27f02c0fdfd1a59c567df5a32f5a92a60253b811 + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.4 + resolution: "p-map@npm:7.0.4" + checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd + languageName: node + linkType: hard + +"path-scurry@npm:^2.0.0": + version: 2.0.1 + resolution: "path-scurry@npm:2.0.1" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/2a16ed0e81fbc43513e245aa5763354e25e787dab0d539581a6c3f0f967461a159ed6236b2559de23aa5b88e7dc32b469b6c47568833dd142a4b24b4f5cd2620 + languageName: node + linkType: hard + +"picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 + languageName: node + linkType: hard + +"playwright-core@npm:1.57.0": + version: 1.57.0 + resolution: "playwright-core@npm:1.57.0" + bin: + playwright-core: cli.js + checksum: 10c0/798e35d83bf48419a8c73de20bb94d68be5dde68de23f95d80a0ebe401e3b83e29e3e84aea7894d67fa6c79d2d3d40cc5bcde3e166f657ce50987aaa2421b6a9 + languageName: node + linkType: hard + +"playwright@npm:1.57.0": + version: 1.57.0 + resolution: "playwright@npm:1.57.0" + dependencies: + fsevents: "npm:2.3.2" + playwright-core: "npm:1.57.0" + dependenciesMeta: + fsevents: + optional: true + bin: + playwright: cli.js + checksum: 10c0/ab03c99a67b835bdea9059f516ad3b6e42c21025f9adaa161a4ef6bc7ca716dcba476d287140bb240d06126eb23f889a8933b8f5f1f1a56b80659d92d1358899 + languageName: node + linkType: hard + +"postcss@npm:^8.5.6": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024 + languageName: node + linkType: hard + +"proc-log@npm:^6.0.0": + version: 6.1.0 + resolution: "proc-log@npm:6.1.0" + checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10c0/60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"rollup@npm:^4.43.0": + version: 4.54.0 + resolution: "rollup@npm:4.54.0" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.54.0" + "@rollup/rollup-android-arm64": "npm:4.54.0" + "@rollup/rollup-darwin-arm64": "npm:4.54.0" + "@rollup/rollup-darwin-x64": "npm:4.54.0" + "@rollup/rollup-freebsd-arm64": "npm:4.54.0" + "@rollup/rollup-freebsd-x64": "npm:4.54.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.54.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.54.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.54.0" + "@rollup/rollup-linux-loong64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-riscv64-musl": "npm:4.54.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.54.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-x64-musl": "npm:4.54.0" + "@rollup/rollup-openharmony-arm64": "npm:4.54.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.54.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.54.0" + "@rollup/rollup-win32-x64-gnu": "npm:4.54.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.54.0" + "@types/estree": "npm:1.0.8" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/62e5fd5d43e72751ac631f13fd7e70bec0fc3809231d5e087c3c0811945e7b8f0956620c5bed4e0cd67085325324266989e5ea4d22985c2677119ac7809b6455 + languageName: node + linkType: hard + +"sade@npm:^1.7.4, sade@npm:^1.8.1": + version: 1.8.1 + resolution: "sade@npm:1.8.1" + dependencies: + mri: "npm:^1.1.0" + checksum: 10c0/da8a3a5d667ad5ce3bf6d4f054bbb9f711103e5df21003c5a5c1a8a77ce12b640ed4017dd423b13c2307ea7e645adee7c2ae3afe8051b9db16a6f6d3da3f90b1 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"semver@npm:^7.3.5": + version: 7.7.3 + resolution: "semver@npm:7.7.3" + bin: + semver: bin/semver.js + checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e + languageName: node + linkType: hard + +"set-cookie-parser@npm:^2.6.0": + version: 2.7.2 + resolution: "set-cookie-parser@npm:2.7.2" + checksum: 10c0/4381a9eb7ee951dfe393fe7aacf76b9a3b4e93a684d2162ab35594fa4053cc82a4d7d7582bf397718012c9adcf839b8cd8f57c6c42901ea9effe33c752da4a45 + languageName: node + linkType: hard + +"sirv@npm:^3.0.0": + version: 3.0.2 + resolution: "sirv@npm:3.0.2" + dependencies: + "@polka/url": "npm:^1.0.0-next.24" + mrmime: "npm:^2.0.0" + totalist: "npm:^3.0.0" + checksum: 10c0/5930e4397afdb14fbae13751c3be983af4bda5c9aadec832607dc2af15a7162f7d518c71b30e83ae3644b9a24cea041543cc969e5fe2b80af6ce8ea3174b2d04 + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.7 + resolution: "socks@npm:2.8.7" + dependencies: + ip-address: "npm:^10.0.1" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"ssri@npm:^13.0.0": + version: 13.0.0 + resolution: "ssri@npm:13.0.0" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/405f3a531cd98b013cecb355d63555dca42fd12c7bc6671738aaa9a82882ff41cdf0ef9a2b734ca4f9a760338f114c29d01d9238a65db3ccac27929bd6e6d4b2 + languageName: node + linkType: hard + +"svelte-check@npm:^4.3.4": + version: 4.3.5 + resolution: "svelte-check@npm:4.3.5" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.25" + chokidar: "npm:^4.0.1" + fdir: "npm:^6.2.0" + picocolors: "npm:^1.0.0" + sade: "npm:^1.7.4" + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: ">=5.0.0" + bin: + svelte-check: bin/svelte-check + checksum: 10c0/a9196c61b9aea4dd597f268362452189df81db08332477727037d5270fad4de733addf6954b57aeeb6825df34f9b20a282ec16f964186c82f39f8456bc4a533c + languageName: node + linkType: hard + +"svelte@npm:^5.45.6": + version: 5.46.1 + resolution: "svelte@npm:5.46.1" + dependencies: + "@jridgewell/remapping": "npm:^2.3.4" + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@sveltejs/acorn-typescript": "npm:^1.0.5" + "@types/estree": "npm:^1.0.5" + acorn: "npm:^8.12.1" + aria-query: "npm:^5.3.1" + axobject-query: "npm:^4.1.0" + clsx: "npm:^2.1.1" + devalue: "npm:^5.5.0" + esm-env: "npm:^1.2.1" + esrap: "npm:^2.2.1" + is-reference: "npm:^3.0.3" + locate-character: "npm:^3.0.0" + magic-string: "npm:^0.30.11" + zimmerframe: "npm:^1.1.2" + checksum: 10c0/63ba18676fc8e1806795ac49b81b26ff69778ad5f89ae0bc6c3bb7c6ab4eab75a60793d8b9fda2380a789d8baaa01af9a5b2eb27cb27a35b42f356e2e4a6eaa3 + languageName: node + linkType: hard + +"tailwind-merge@npm:^3.4.0": + version: 3.4.0 + resolution: "tailwind-merge@npm:3.4.0" + checksum: 10c0/eaf17bb695c51c7bb7a90366a9c62be295473ee97fcfd1da54287714d4a5788a88ff4ad1ab9e0128638257fda777d6c9ea88682e36195e31a7fa2cf43f45e310 + languageName: node + linkType: hard + +"tailwind-variants@npm:^3.2.2": + version: 3.2.2 + resolution: "tailwind-variants@npm:3.2.2" + peerDependencies: + tailwind-merge: ">=3.0.0" + tailwindcss: "*" + peerDependenciesMeta: + tailwind-merge: + optional: true + checksum: 10c0/715a35b66c374f3bb234cd1e6737588cb7c0213c80a69bd62239d752d20c44377bec1d028c3ea7c28882d81384507fbae407813d348c142cb991c5a0ad063d48 + languageName: node + linkType: hard + +"tailwindcss@npm:4.1.18, tailwindcss@npm:^4.1.18": + version: 4.1.18 + resolution: "tailwindcss@npm:4.1.18" + checksum: 10c0/c79263cea0b2c577859b02f28284caa4eb3844e4d0f563686726ca97817c045c5c395a55bc776daaa351ba9e4aefa9a75bfbb43c22d86f3c573eecc2b87d6bf1 + languageName: node + linkType: hard + +"tapable@npm:^2.2.0": + version: 2.3.0 + resolution: "tapable@npm:2.3.0" + checksum: 10c0/cb9d67cc2c6a74dedc812ef3085d9d681edd2c1fa18e4aef57a3c0605fdbe44e6b8ea00bd9ef21bc74dd45314e39d31227aa031ebf2f5e38164df514136f2681 + languageName: node + linkType: hard + +"tar@npm:^7.5.2": + version: 7.5.2 + resolution: "tar@npm:7.5.2" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/a7d8b801139b52f93a7e34830db0de54c5aa45487c7cb551f6f3d44a112c67f1cb8ffdae856b05fd4f17b1749911f1c26f1e3a23bbe0279e17fd96077f13f467 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + languageName: node + linkType: hard + +"totalist@npm:^3.0.0": + version: 3.0.1 + resolution: "totalist@npm:3.0.1" + checksum: 10c0/4bb1fadb69c3edbef91c73ebef9d25b33bbf69afe1e37ce544d5f7d13854cda15e47132f3e0dc4cafe300ddb8578c77c50a65004d8b6e97e77934a69aa924863 + languageName: node + linkType: hard + +"tslib@npm:^2.4.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + +"tw-animate-css@npm:^1.4.0": + version: 1.4.0 + resolution: "tw-animate-css@npm:1.4.0" + checksum: 10c0/6cfbc19ccc73883ec80ef1f9147f43e736cb01ee99c8172968b37eb81b720523d30e38b1a96aef92db3c586d864204db5510b51744ddacbbf0ad8e3c7fb56ec7 + languageName: node + linkType: hard + +"typescript@npm:^5.9.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + +"unique-filename@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-filename@npm:5.0.0" + dependencies: + unique-slug: "npm:^6.0.0" + checksum: 10c0/afb897e9cf4c2fb622ea716f7c2bb462001928fc5f437972213afdf1cc32101a230c0f1e9d96fc91ee5185eca0f2feb34127145874975f347be52eb91d6ccc2c + languageName: node + linkType: hard + +"unique-slug@npm:^6.0.0": + version: 6.0.0 + resolution: "unique-slug@npm:6.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/da7ade4cb04eb33ad0499861f82fe95ce9c7c878b7139dc54d140ecfb6a6541c18a5c8dac16188b8b379fe62c0c1f1b710814baac910cde5f4fec06212126c6a + languageName: node + linkType: hard + +"vite@npm:^7.2.6": + version: 7.3.0 + resolution: "vite@npm:7.3.0" + dependencies: + esbuild: "npm:^0.27.0" + fdir: "npm:^6.5.0" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rollup: "npm:^4.43.0" + tinyglobby: "npm:^0.2.15" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.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 + dependenciesMeta: + fsevents: + optional: true + 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 + bin: + vite: bin/vite.js + checksum: 10c0/0457c196cdd5761ec351c0f353945430fbad330e615b9eeab729c8ae163334f18acdc1d9cd7d9d673dbf111f07f6e4f0b25d4ac32360e65b4a6df9991046f3ff + languageName: node + linkType: hard + +"vitefu@npm:^1.1.1": + version: 1.1.1 + resolution: "vitefu@npm:1.1.1" + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + checksum: 10c0/7e0d0dd6fb73bd80cb56a3f47ccc44159e0330c3e94b2951648079b35711226f9088dbe616d910b931740b92259230b874fbe351108b49f5c11b629b641292a5 + languageName: node + linkType: hard + +"which@npm:^6.0.0": + version: 6.0.0 + resolution: "which@npm:6.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/fe9d6463fe44a76232bb6e3b3181922c87510a5b250a98f1e43a69c99c079b3f42ddeca7e03d3e5f2241bf2d334f5a7657cfa868b97c109f3870625842f4cc15 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"zimmerframe@npm:^1.1.2": + version: 1.1.4 + resolution: "zimmerframe@npm:1.1.4" + checksum: 10c0/9470cbf22cefae975ab413c7158a119d082b354ddcf0da48a842f2f42246fa15943cd9b92c047de39db38015e3b866e32f383bc217e8e4f4192945c7d425536b + languageName: node + linkType: hard