From e5109da732ec062acd805af5370accfd54ca999f Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Tue, 17 Feb 2026 12:57:43 +0100 Subject: [PATCH] feat(wallpaper-generator): add device wallpaper generation package Add new @manacore/wallpaper-generator package for creating device wallpapers from QR codes and images. Features: - 30 device presets (phones, tablets, desktops) - 3 layout types (center, corner, pattern) - 5 gradient presets + solid color backgrounds - Browser (Canvas) and Node.js (Sharp) renderers - Svelte WallpaperModal UI component Integrations: - @manacore/qr-export: toWallpaper() function - @manacore/spiral-db: toWallpaper() function - QRExportModal: "Als Wallpaper" button Also includes the full @manacore/qr-export package which was previously untracked. Co-Authored-By: Claude Opus 4.5 --- apps/manacore/apps/web/package.json | 1 + .../components/my-data/QRExportModal.svelte | 40 + packages/qr-export/package.json | 48 + packages/qr-export/src/builder.ts | 247 +++ packages/qr-export/src/encoder.test.ts | 153 ++ packages/qr-export/src/encoder.ts | 231 +++ packages/qr-export/src/generate.ts | 140 ++ packages/qr-export/src/index.ts | 66 + packages/qr-export/src/selectors.ts | 232 +++ .../qr-export/src/svelte/ManaQRCode.svelte | 144 ++ .../qr-export/src/svelte/ManaQRScanner.svelte | 338 ++++ packages/qr-export/src/svelte/index.ts | 36 + packages/qr-export/src/types.ts | 142 ++ packages/qr-export/src/wallpaper.ts | 124 ++ packages/qr-export/tsconfig.json | 18 + packages/qr-export/vitest.config.ts | 9 + packages/spiral-db/package.json | 14 +- packages/spiral-db/src/wallpaper.ts | 157 ++ packages/wallpaper-generator/package.json | 38 + .../src/backgrounds/gradient.ts | 160 ++ .../src/backgrounds/index.ts | 6 + .../src/backgrounds/solid.ts | 57 + packages/wallpaper-generator/src/index.ts | 140 ++ .../wallpaper-generator/src/layouts/center.ts | 59 + .../wallpaper-generator/src/layouts/corner.ts | 79 + .../wallpaper-generator/src/layouts/index.ts | 11 + .../src/layouts/pattern.ts | 88 + .../src/presets/devices.ts | 336 ++++ .../wallpaper-generator/src/presets/index.ts | 14 + .../src/renderers/browser.ts | 316 ++++ .../src/renderers/index.ts | 6 + .../wallpaper-generator/src/renderers/node.ts | 397 +++++ .../src/svelte/WallpaperModal.svelte | 453 +++++ .../wallpaper-generator/src/svelte/index.ts | 5 + packages/wallpaper-generator/src/types.ts | 282 ++++ packages/wallpaper-generator/tsconfig.json | 19 + pnpm-lock.yaml | 1463 +++++++++-------- 37 files changed, 5393 insertions(+), 676 deletions(-) create mode 100644 packages/qr-export/package.json create mode 100644 packages/qr-export/src/builder.ts create mode 100644 packages/qr-export/src/encoder.test.ts create mode 100644 packages/qr-export/src/encoder.ts create mode 100644 packages/qr-export/src/generate.ts create mode 100644 packages/qr-export/src/index.ts create mode 100644 packages/qr-export/src/selectors.ts create mode 100644 packages/qr-export/src/svelte/ManaQRCode.svelte create mode 100644 packages/qr-export/src/svelte/ManaQRScanner.svelte create mode 100644 packages/qr-export/src/svelte/index.ts create mode 100644 packages/qr-export/src/types.ts create mode 100644 packages/qr-export/src/wallpaper.ts create mode 100644 packages/qr-export/tsconfig.json create mode 100644 packages/qr-export/vitest.config.ts create mode 100644 packages/spiral-db/src/wallpaper.ts create mode 100644 packages/wallpaper-generator/package.json create mode 100644 packages/wallpaper-generator/src/backgrounds/gradient.ts create mode 100644 packages/wallpaper-generator/src/backgrounds/index.ts create mode 100644 packages/wallpaper-generator/src/backgrounds/solid.ts create mode 100644 packages/wallpaper-generator/src/index.ts create mode 100644 packages/wallpaper-generator/src/layouts/center.ts create mode 100644 packages/wallpaper-generator/src/layouts/corner.ts create mode 100644 packages/wallpaper-generator/src/layouts/index.ts create mode 100644 packages/wallpaper-generator/src/layouts/pattern.ts create mode 100644 packages/wallpaper-generator/src/presets/devices.ts create mode 100644 packages/wallpaper-generator/src/presets/index.ts create mode 100644 packages/wallpaper-generator/src/renderers/browser.ts create mode 100644 packages/wallpaper-generator/src/renderers/index.ts create mode 100644 packages/wallpaper-generator/src/renderers/node.ts create mode 100644 packages/wallpaper-generator/src/svelte/WallpaperModal.svelte create mode 100644 packages/wallpaper-generator/src/svelte/index.ts create mode 100644 packages/wallpaper-generator/src/types.ts create mode 100644 packages/wallpaper-generator/tsconfig.json diff --git a/apps/manacore/apps/web/package.json b/apps/manacore/apps/web/package.json index 7dcb3f04e..001ba32ac 100644 --- a/apps/manacore/apps/web/package.json +++ b/apps/manacore/apps/web/package.json @@ -43,6 +43,7 @@ }, "dependencies": { "@manacore/qr-export": "workspace:*", + "@manacore/wallpaper-generator": "workspace:*", "@manacore/shared-auth": "workspace:*", "@manacore/shared-auth-ui": "workspace:*", "@manacore/shared-branding": "workspace:*", diff --git a/apps/manacore/apps/web/src/lib/components/my-data/QRExportModal.svelte b/apps/manacore/apps/web/src/lib/components/my-data/QRExportModal.svelte index ae15a4b19..5ee634822 100644 --- a/apps/manacore/apps/web/src/lib/components/my-data/QRExportModal.svelte +++ b/apps/manacore/apps/web/src/lib/components/my-data/QRExportModal.svelte @@ -3,6 +3,7 @@ import { toDataURL, toSVG } from '@manacore/qr-export/generate'; import { qrExportService, type QRExportResult } from '$lib/api/services/qr-export'; import type { UserDataSummary } from '$lib/api/services/my-data'; + import { WallpaperModal } from '@manacore/wallpaper-generator/svelte'; interface Props { show: boolean; @@ -15,6 +16,8 @@ let loading = $state(true); let error = $state(null); let exportResult = $state(null); + let showWallpaperModal = $state(false); + let qrDataUrl = $state(null); // Load export data when modal opens $effect(() => { @@ -87,6 +90,18 @@ if (bytes < 1024) return `${bytes} Bytes`; return `${(bytes / 1024).toFixed(1)} KB`; } + + async function openWallpaperModal() { + if (!exportResult) return; + + try { + // Generate QR code as data URL for wallpaper generation + qrDataUrl = await toDataURL(exportResult.encodeResult, { size: 600 }); + showWallpaperModal = true; + } catch (e) { + console.error('Failed to generate QR data URL:', e); + } + } {#if show} @@ -236,6 +251,22 @@ SVG + + + {/if} @@ -250,3 +281,12 @@ {/if} + + +{#if qrDataUrl} + (showWallpaperModal = false)} + /> +{/if} diff --git a/packages/qr-export/package.json b/packages/qr-export/package.json new file mode 100644 index 000000000..141810a5d --- /dev/null +++ b/packages/qr-export/package.json @@ -0,0 +1,48 @@ +{ + "name": "@manacore/qr-export", + "version": "0.1.0", + "private": true, + "description": "QR code export/import for personal data (contacts, events, todos, user context)", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts", + "./svelte": "./src/svelte/index.ts", + "./generate": "./src/generate.ts", + "./wallpaper": "./src/wallpaper.ts" + }, + "scripts": { + "type-check": "tsc --noEmit", + "clean": "rm -rf dist", + "lint": "eslint .", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "pako": "^2.1.0", + "qrcode": "^1.5.4" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "@types/pako": "^2.0.3", + "@types/qrcode": "^1.5.5", + "typescript": "^5.9.3", + "vitest": "^3.0.5" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "jsqr": "^1.4.0", + "@manacore/wallpaper-generator": "workspace:*" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + }, + "jsqr": { + "optional": true + }, + "@manacore/wallpaper-generator": { + "optional": true + } + } +} diff --git a/packages/qr-export/src/builder.ts b/packages/qr-export/src/builder.ts new file mode 100644 index 000000000..5e8edcfc1 --- /dev/null +++ b/packages/qr-export/src/builder.ts @@ -0,0 +1,247 @@ +/** + * ManaQR Export Builder + * + * Fluent API for building QR exports easily. + */ + +import type { + ManaQRExport, + ManaQRUserContext, + ManaQRContact, + ManaQREvent, + ManaQRTodo, + EncodeResult, + ContactRelation, + TodoPriority, +} from './types'; +import { MANA_QR_LIMITS } from './types'; +import { encode, estimateSize } from './encoder'; +import { + selectTopContacts, + selectUpcomingEvents, + selectPriorityTodos, + type ContactInput, + type EventInput, + type TodoInput, +} from './selectors'; + +/** + * Builder for creating ManaQR exports + * + * @example + * ```ts + * const result = createManaQRExport() + * .user({ n: 'Till', z: 'Europe/Berlin', l: 'de' }) + * .addContact({ n: 'Mama', p: '+491701234567', r: 1 }) + * .addContact({ n: 'Papa', p: '+491707654321', r: 1 }) + * .addEvent({ t: 'Zahnarzt', s: Date.now() + 86400000, d: 60 }) + * .addTodo({ t: 'Steuererklärung', p: 1, d: 14 }) + * .encode(); + * ``` + */ +export class ManaQRExportBuilder { + private _user: ManaQRUserContext = { n: '' }; + private _contacts: ManaQRContact[] = []; + private _events: ManaQREvent[] = []; + private _todos: ManaQRTodo[] = []; + private _timestamp: number = Math.floor(Date.now() / 1000); + + /** + * Set user context + */ + user(context: ManaQRUserContext): this { + this._user = context; + return this; + } + + /** + * Set user name (shorthand) + */ + userName(name: string): this { + this._user.n = name; + return this; + } + + /** + * Set user timezone + */ + timezone(tz: string): this { + this._user.z = tz; + return this; + } + + /** + * Set user language + */ + language(lang: string): this { + this._user.l = lang; + return this; + } + + /** + * Set user location + */ + location(loc: string): this { + this._user.w = loc; + return this; + } + + /** + * Add a single contact + */ + addContact(contact: ManaQRContact): this { + this._contacts.push(contact); + return this; + } + + /** + * Add multiple contacts + */ + contacts(contacts: ManaQRContact[]): this { + this._contacts.push(...contacts); + return this; + } + + /** + * Add contacts from input format (auto-selects top contacts) + */ + contactsFrom(inputs: ContactInput[], limit: number = MANA_QR_LIMITS.MAX_CONTACTS): this { + this._contacts = selectTopContacts(inputs, limit); + return this; + } + + /** + * Add a single event + */ + addEvent(event: ManaQREvent): this { + this._events.push(event); + return this; + } + + /** + * Add multiple events + */ + events(events: ManaQREvent[]): this { + this._events.push(...events); + return this; + } + + /** + * Add events from input format (auto-selects upcoming) + */ + eventsFrom(inputs: EventInput[], limit: number = MANA_QR_LIMITS.MAX_EVENTS): this { + this._events = selectUpcomingEvents(inputs, limit); + return this; + } + + /** + * Add a single todo + */ + addTodo(todo: ManaQRTodo): this { + this._todos.push(todo); + return this; + } + + /** + * Add multiple todos + */ + todos(todos: ManaQRTodo[]): this { + this._todos.push(...todos); + return this; + } + + /** + * Add todos from input format (auto-selects by priority) + */ + todosFrom(inputs: TodoInput[], limit: number = MANA_QR_LIMITS.MAX_TODOS): this { + this._todos = selectPriorityTodos(inputs, limit); + return this; + } + + /** + * Set custom timestamp (Unix seconds) + */ + timestamp(ts: number): this { + this._timestamp = ts; + return this; + } + + /** + * Build the export data (without encoding) + */ + build(): ManaQRExport { + return { + v: 1, + ts: this._timestamp, + u: this._user, + c: this._contacts, + e: this._events, + t: this._todos, + }; + } + + /** + * Estimate encoded size in bytes + */ + estimateSize(): number { + return estimateSize(this.build()); + } + + /** + * Check if current data will fit in a QR code + */ + willFit(): boolean { + return this.estimateSize() <= MANA_QR_LIMITS.MAX_QR_BYTES; + } + + /** + * Encode to QR-ready string + */ + encode(): EncodeResult { + return encode(this.build()); + } +} + +/** + * Create a new ManaQR export builder + */ +export function createManaQRExport(): ManaQRExportBuilder { + return new ManaQRExportBuilder(); +} + +// --- Quick helpers for common formats --- + +/** + * Create a contact in compact format + */ +export function contact( + name: string, + phone?: string, + relation: ContactRelation = 3 +): ManaQRContact { + return { n: name, p: phone, r: relation }; +} + +/** + * Create an event in compact format + */ +export function event( + title: string, + startDate: Date, + durationMinutes = 60, + location?: string +): ManaQREvent { + return { + t: title, + s: Math.floor(startDate.getTime() / 1000), + d: durationMinutes, + l: location, + }; +} + +/** + * Create a todo in compact format + */ +export function todo(title: string, priority: TodoPriority = 2, dueDays?: number): ManaQRTodo { + return { t: title, p: priority, d: dueDays }; +} diff --git a/packages/qr-export/src/encoder.test.ts b/packages/qr-export/src/encoder.test.ts new file mode 100644 index 000000000..462563196 --- /dev/null +++ b/packages/qr-export/src/encoder.test.ts @@ -0,0 +1,153 @@ +import { describe, it, expect } from 'vitest'; +import { encode, decode, estimateSize, willFitInQR, MANA_QR_PREFIX } from './encoder'; +import { createManaQRExport, contact, event, todo } from './builder'; +import type { ManaQRExport } from './types'; + +describe('encoder', () => { + const sampleExport: ManaQRExport = { + v: 1, + ts: 1708185600, + u: { + n: 'Till', + z: 'Europe/Berlin', + l: 'de', + w: 'Berlin', + }, + c: [ + { n: 'Mama', p: '+491701234567', r: 1 }, + { n: 'Papa', p: '+491707654321', r: 1 }, + ], + e: [ + { t: 'Zahnarzt', s: 1708272000, d: 60, l: 'Praxis Dr. Weber' }, + { t: 'Team Meeting', s: 1708358400, d: 30 }, + ], + t: [ + { t: 'Steuererklärung abgeben', p: 1, d: 14 }, + { t: 'Backup machen', p: 3 }, + ], + }; + + describe('encode', () => { + it('should encode data with correct prefix', () => { + const result = encode(sampleExport); + + expect(result.data.startsWith(MANA_QR_PREFIX)).toBe(true); + expect(result.size).toBeGreaterThan(0); + }); + + it('should report fitsInQR correctly for small data', () => { + const result = encode(sampleExport); + + expect(result.fitsInQR).toBe(true); + expect(result.size).toBeLessThan(2500); + }); + + it('should compress data significantly', () => { + const jsonSize = JSON.stringify(sampleExport).length; + const result = encode(sampleExport); + + // Encoded should be smaller than raw JSON + expect(result.size).toBeLessThan(jsonSize); + }); + }); + + describe('decode', () => { + it('should decode encoded data correctly', () => { + const encoded = encode(sampleExport); + const decoded = decode(encoded.data); + + expect(decoded.success).toBe(true); + if (decoded.success) { + expect(decoded.data.u.n).toBe('Till'); + expect(decoded.data.c).toHaveLength(2); + expect(decoded.data.e).toHaveLength(2); + expect(decoded.data.t).toHaveLength(2); + } + }); + + it('should fail for invalid prefix', () => { + const result = decode('INVALID:xyz'); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBe('INVALID_PREFIX'); + } + }); + + it('should fail for invalid base64', () => { + const result = decode(MANA_QR_PREFIX + '!!!invalid!!!'); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error).toBe('INVALID_BASE64'); + } + }); + }); + + describe('roundtrip', () => { + it('should preserve all data through encode/decode', () => { + const encoded = encode(sampleExport); + const decoded = decode(encoded.data); + + expect(decoded.success).toBe(true); + if (decoded.success) { + expect(decoded.data).toEqual(sampleExport); + } + }); + }); + + describe('estimateSize', () => { + it('should estimate size within reasonable range', () => { + const estimated = estimateSize(sampleExport); + const actual = encode(sampleExport).size; + + // Estimate should be within 50% of actual + expect(estimated).toBeGreaterThan(actual * 0.5); + expect(estimated).toBeLessThan(actual * 1.5); + }); + }); + + describe('willFitInQR', () => { + it('should return true for small data', () => { + expect(willFitInQR(sampleExport)).toBe(true); + }); + }); +}); + +describe('builder', () => { + it('should build export with fluent API', () => { + const result = createManaQRExport() + .user({ n: 'Test', z: 'UTC', l: 'en' }) + .addContact(contact('Alice', '+1234567890', 1)) + .addEvent(event('Meeting', new Date('2024-02-20T10:00:00Z'), 60)) + .addTodo(todo('Task 1', 1, 7)) + .build(); + + expect(result.v).toBe(1); + expect(result.u.n).toBe('Test'); + expect(result.c).toHaveLength(1); + expect(result.e).toHaveLength(1); + expect(result.t).toHaveLength(1); + }); + + it('should encode to valid QR string', () => { + const result = createManaQRExport() + .userName('Test') + .timezone('UTC') + .addContact({ n: 'Bob', r: 3 }) + .encode(); + + expect(result.data.startsWith(MANA_QR_PREFIX)).toBe(true); + expect(result.fitsInQR).toBe(true); + }); + + it('should estimate size correctly', () => { + const builder = createManaQRExport().userName('Test').addContact({ n: 'Alice', r: 1 }); + + const estimated = builder.estimateSize(); + const _actual = builder.encode().size; + + expect(estimated).toBeGreaterThan(0); + expect(builder.willFit()).toBe(true); + }); +}); diff --git a/packages/qr-export/src/encoder.ts b/packages/qr-export/src/encoder.ts new file mode 100644 index 000000000..06e202f71 --- /dev/null +++ b/packages/qr-export/src/encoder.ts @@ -0,0 +1,231 @@ +/** + * ManaQR Encoder/Decoder + * + * Encodes/decodes ManaQRExport data for QR codes using gzip compression. + */ + +import pako from 'pako'; +import type { ManaQRExport, EncodeResult, DecodeResult, DecodeError } from './types'; +import { MANA_QR_LIMITS } from './types'; + +/** Prefix for ManaQR data (helps identify valid codes) */ +export const MANA_QR_PREFIX = 'MANA1:'; + +/** Current format version */ +export const MANA_QR_VERSION = 1; + +/** + * Encode ManaQRExport data to a compressed string for QR codes + */ +export function encode(data: ManaQRExport): EncodeResult { + // Ensure version is set + const exportData: ManaQRExport = { + ...data, + v: MANA_QR_VERSION, + ts: data.ts || Math.floor(Date.now() / 1000), + }; + + // Convert to JSON + const json = JSON.stringify(exportData); + + // Compress with gzip + const compressed = pako.deflate(json); + + // Convert to base64 + const base64 = uint8ArrayToBase64(compressed); + + // Add prefix + const result = MANA_QR_PREFIX + base64; + + return { + data: result, + size: result.length, + fitsInQR: result.length <= MANA_QR_LIMITS.MAX_QR_BYTES, + }; +} + +/** + * Decode a ManaQR string back to ManaQRExport data + */ +export function decode(qrString: string): DecodeResult { + // Check prefix + if (!qrString.startsWith(MANA_QR_PREFIX)) { + return { + success: false, + error: 'INVALID_PREFIX', + message: `String must start with "${MANA_QR_PREFIX}"`, + }; + } + + // Extract base64 part + const base64 = qrString.slice(MANA_QR_PREFIX.length); + + // Decode base64 + let compressed: Uint8Array; + try { + compressed = base64ToUint8Array(base64); + } catch { + return { + success: false, + error: 'INVALID_BASE64', + message: 'Invalid base64 encoding', + }; + } + + // Decompress + let json: string; + try { + json = pako.inflate(compressed, { to: 'string' }); + } catch { + return { + success: false, + error: 'DECOMPRESSION_FAILED', + message: 'Failed to decompress data', + }; + } + + // Parse JSON + let data: unknown; + try { + data = JSON.parse(json); + } catch { + return { + success: false, + error: 'INVALID_JSON', + message: 'Invalid JSON data', + }; + } + + // Validate structure + const validation = validateExport(data); + if (!validation.valid) { + return { + success: false, + error: validation.error, + message: validation.message, + }; + } + + return { + success: true, + data: data as ManaQRExport, + }; +} + +/** + * Estimate the encoded size without actually encoding + * Useful for checking if data will fit before encoding + */ +export function estimateSize(data: ManaQRExport): number { + const json = JSON.stringify(data); + // Rough estimate: gzip typically achieves 60-70% compression on JSON + // Base64 adds ~33% overhead + // Add prefix length + const estimatedCompressed = json.length * 0.35; + const estimatedBase64 = estimatedCompressed * 1.33; + return Math.ceil(estimatedBase64 + MANA_QR_PREFIX.length); +} + +/** + * Check if data will likely fit in a single QR code + */ +export function willFitInQR(data: ManaQRExport): boolean { + return estimateSize(data) <= MANA_QR_LIMITS.MAX_QR_BYTES; +} + +// --- Validation --- + +interface ValidationResult { + valid: boolean; + error: DecodeError; + message: string; +} + +function validateExport(data: unknown): ValidationResult { + if (typeof data !== 'object' || data === null) { + return { + valid: false, + error: 'INVALID_STRUCTURE', + message: 'Data must be an object', + }; + } + + const obj = data as Record; + + // Check version + if (obj.v !== MANA_QR_VERSION) { + return { + valid: false, + error: 'INVALID_VERSION', + message: `Unsupported version: ${obj.v}`, + }; + } + + // Check required fields + if (typeof obj.ts !== 'number') { + return { + valid: false, + error: 'INVALID_STRUCTURE', + message: 'Missing or invalid timestamp', + }; + } + + if (typeof obj.u !== 'object' || obj.u === null) { + return { + valid: false, + error: 'INVALID_STRUCTURE', + message: 'Missing user context', + }; + } + + if (!Array.isArray(obj.c)) { + return { + valid: false, + error: 'INVALID_STRUCTURE', + message: 'Contacts must be an array', + }; + } + + if (!Array.isArray(obj.e)) { + return { + valid: false, + error: 'INVALID_STRUCTURE', + message: 'Events must be an array', + }; + } + + if (!Array.isArray(obj.t)) { + return { + valid: false, + error: 'INVALID_STRUCTURE', + message: 'Todos must be an array', + }; + } + + return { valid: true, error: 'INVALID_STRUCTURE', message: '' }; +} + +// --- Base64 utilities (browser & Node compatible) --- + +function uint8ArrayToBase64(bytes: Uint8Array): string { + if (typeof btoa === 'function') { + // Browser + return btoa(String.fromCharCode(...bytes)); + } + // Node.js + return Buffer.from(bytes).toString('base64'); +} + +function base64ToUint8Array(base64: string): Uint8Array { + if (typeof atob === 'function') { + // Browser + const binaryString = atob(base64); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; + } + // Node.js + return new Uint8Array(Buffer.from(base64, 'base64')); +} diff --git a/packages/qr-export/src/generate.ts b/packages/qr-export/src/generate.ts new file mode 100644 index 000000000..f00ef0675 --- /dev/null +++ b/packages/qr-export/src/generate.ts @@ -0,0 +1,140 @@ +/** + * QR Code Generation utilities + * + * Framework-agnostic functions for generating QR codes. + */ + +import QRCode from 'qrcode'; +import type { ManaQRExport, EncodeResult } from './types'; +import { encode } from './encoder'; + +/** QR Code generation options */ +export interface QRGenerateOptions { + /** Width/height in pixels (default: 300) */ + size?: number; + /** Margin in modules (default: 2) */ + margin?: number; + /** Error correction level (default: 'M') */ + errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H'; + /** Dark color (default: '#000000') */ + darkColor?: string; + /** Light color (default: '#ffffff') */ + lightColor?: string; +} + +const DEFAULT_OPTIONS: Required = { + size: 300, + margin: 2, + errorCorrectionLevel: 'M', + darkColor: '#000000', + lightColor: '#ffffff', +}; + +/** + * Generate QR code as Data URL (for ) + */ +export async function toDataURL( + data: string | ManaQRExport | EncodeResult, + options?: QRGenerateOptions +): Promise { + const qrData = resolveData(data); + const opts = { ...DEFAULT_OPTIONS, ...options }; + + return QRCode.toDataURL(qrData, { + errorCorrectionLevel: opts.errorCorrectionLevel, + margin: opts.margin, + width: opts.size, + color: { + dark: opts.darkColor, + light: opts.lightColor, + }, + }); +} + +/** + * Generate QR code as SVG string + */ +export async function toSVG( + data: string | ManaQRExport | EncodeResult, + options?: QRGenerateOptions +): Promise { + const qrData = resolveData(data); + const opts = { ...DEFAULT_OPTIONS, ...options }; + + return QRCode.toString(qrData, { + type: 'svg', + errorCorrectionLevel: opts.errorCorrectionLevel, + margin: opts.margin, + width: opts.size, + color: { + dark: opts.darkColor, + light: opts.lightColor, + }, + }); +} + +/** + * Generate QR code for terminal output + */ +export async function toTerminal(data: string | ManaQRExport | EncodeResult): Promise { + const qrData = resolveData(data); + return QRCode.toString(qrData, { type: 'terminal', small: true }); +} + +/** + * Draw QR code to canvas element + */ +export async function toCanvas( + canvas: HTMLCanvasElement, + data: string | ManaQRExport | EncodeResult, + options?: QRGenerateOptions +): Promise { + const qrData = resolveData(data); + const opts = { ...DEFAULT_OPTIONS, ...options }; + + await QRCode.toCanvas(canvas, qrData, { + errorCorrectionLevel: opts.errorCorrectionLevel, + margin: opts.margin, + width: opts.size, + color: { + dark: opts.darkColor, + light: opts.lightColor, + }, + }); +} + +/** + * Save QR code to file (Node.js only) + */ +export async function toFile( + path: string, + data: string | ManaQRExport | EncodeResult, + options?: QRGenerateOptions +): Promise { + const qrData = resolveData(data); + const opts = { ...DEFAULT_OPTIONS, ...options }; + + await QRCode.toFile(path, qrData, { + errorCorrectionLevel: opts.errorCorrectionLevel, + margin: opts.margin, + width: opts.size, + color: { + dark: opts.darkColor, + light: opts.lightColor, + }, + }); +} + +// --- Helpers --- + +function resolveData(data: string | ManaQRExport | EncodeResult): string { + if (typeof data === 'string') { + return data; + } + if ('data' in data && typeof data.data === 'string') { + // EncodeResult + return data.data; + } + // ManaQRExport - encode it + return encode(data as ManaQRExport).data; +} diff --git a/packages/qr-export/src/index.ts b/packages/qr-export/src/index.ts new file mode 100644 index 000000000..bcb1dac71 --- /dev/null +++ b/packages/qr-export/src/index.ts @@ -0,0 +1,66 @@ +/** + * @manacore/qr-export + * + * QR code export/import for personal data (contacts, events, todos, user context). + * Compresses data to fit within a single QR code (~2,500 bytes). + * + * @example + * ```ts + * import { createManaQRExport, decode } from '@manacore/qr-export'; + * + * // Create export + * const result = createManaQRExport() + * .user({ n: 'Till', z: 'Europe/Berlin', l: 'de' }) + * .addContact({ n: 'Mama', p: '+491701234567', r: 1 }) + * .addEvent({ t: 'Meeting', s: Date.now() / 1000, d: 60 }) + * .addTodo({ t: 'Backup machen', p: 1, d: 7 }) + * .encode(); + * + * console.log(result.data); // "MANA1:eJy..." (use for QR code) + * console.log(result.fitsInQR); // true + * + * // Decode + * const decoded = decode(result.data); + * if (decoded.success) { + * console.log(decoded.data.u.n); // "Till" + * } + * ``` + */ + +// Types +export type { + ManaQRExport, + ManaQRUserContext, + ManaQRContact, + ManaQREvent, + ManaQRTodo, + ContactRelation, + TodoPriority, + EncodeResult, + DecodeResult, + DecodeError, +} from './types'; + +export { MANA_QR_LIMITS, RELATION_LABELS, PRIORITY_LABELS } from './types'; + +// Encoder/Decoder +export { + encode, + decode, + estimateSize, + willFitInQR, + MANA_QR_PREFIX, + MANA_QR_VERSION, +} from './encoder'; + +// Selectors +export type { ContactInput, EventInput, TodoInput } from './selectors'; + +export { selectTopContacts, selectUpcomingEvents, selectPriorityTodos } from './selectors'; + +// Builder +export { ManaQRExportBuilder, createManaQRExport, contact, event, todo } from './builder'; + +// QR Code Generation +export type { QRGenerateOptions } from './generate'; +export { toDataURL, toSVG, toTerminal, toCanvas, toFile } from './generate'; diff --git a/packages/qr-export/src/selectors.ts b/packages/qr-export/src/selectors.ts new file mode 100644 index 000000000..825306c88 --- /dev/null +++ b/packages/qr-export/src/selectors.ts @@ -0,0 +1,232 @@ +/** + * Selectors for choosing the most important data for QR export + * + * These helpers select and prioritize data to fit within QR code limits. + */ + +import type { + ManaQRContact, + ManaQREvent, + ManaQRTodo, + ContactRelation, + TodoPriority, +} from './types'; +import { MANA_QR_LIMITS } from './types'; + +// --- Contact Selectors --- + +/** Input format for contact selection */ +export interface ContactInput { + name: string; + phone?: string; + email?: string; + relation?: ContactRelation; + /** Higher = more important */ + importance?: number; + /** Is emergency contact */ + isEmergency?: boolean; + /** Is family member */ + isFamily?: boolean; +} + +/** + * Select the most important contacts for QR export + * + * Priority order: + * 1. Emergency contacts (relation = 5) + * 2. Family (relation = 1) + * 3. Partner (relation = 2) + * 4. By importance score + * 5. By provided order + */ +export function selectTopContacts( + contacts: ContactInput[], + limit: number = MANA_QR_LIMITS.MAX_CONTACTS +): ManaQRContact[] { + const scored = contacts.map((c, index) => { + let score = 0; + + // Emergency contacts highest priority + if (c.isEmergency || c.relation === 5) score += 1000; + // Family second + if (c.isFamily || c.relation === 1) score += 500; + // Partner third + if (c.relation === 2) score += 400; + // Work contacts + if (c.relation === 4) score += 100; + // Custom importance + if (c.importance) score += c.importance; + // Prefer contacts with phone numbers + if (c.phone) score += 50; + // Original order as tiebreaker + score -= index * 0.01; + + return { contact: c, score }; + }); + + // Sort by score descending + scored.sort((a, b) => b.score - a.score); + + // Take top N and convert to compact format + return scored.slice(0, limit).map(({ contact }) => ({ + n: contact.name, + p: contact.phone, + e: contact.email, + r: contact.relation || 3, // Default to "Freund" + })); +} + +// --- Event Selectors --- + +/** Input format for event selection */ +export interface EventInput { + title: string; + /** Start time as Date or Unix timestamp (ms) */ + start: Date | number; + /** End time as Date or Unix timestamp (ms) */ + end?: Date | number; + /** Duration in minutes (alternative to end) */ + durationMinutes?: number; + location?: string; + /** Is all-day event */ + allDay?: boolean; + /** Higher = more important */ + importance?: number; +} + +/** + * Select upcoming events for QR export + * + * Only includes future events, sorted by start time. + * Truncates titles to fit size limits. + */ +export function selectUpcomingEvents( + events: EventInput[], + limit: number = MANA_QR_LIMITS.MAX_EVENTS, + fromDate: Date = new Date() +): ManaQREvent[] { + const fromTimestamp = fromDate.getTime(); + + // Filter and sort future events + const futureEvents = events + .map((e) => ({ + event: e, + startMs: e.start instanceof Date ? e.start.getTime() : e.start, + })) + .filter(({ startMs }) => startMs >= fromTimestamp) + .sort((a, b) => a.startMs - b.startMs); + + // Take top N and convert to compact format + return futureEvents.slice(0, limit).map(({ event, startMs }) => { + // Calculate duration + let durationMinutes = event.durationMinutes || 60; + if (event.end) { + const endMs = event.end instanceof Date ? event.end.getTime() : event.end; + durationMinutes = Math.round((endMs - startMs) / 60000); + } + if (event.allDay) { + durationMinutes = 1440; // 24 hours + } + + return { + t: truncate(event.title, MANA_QR_LIMITS.MAX_EVENT_TITLE), + s: Math.floor(startMs / 1000), // Unix seconds + d: durationMinutes, + l: event.location ? truncate(event.location, 20) : undefined, + }; + }); +} + +// --- Todo Selectors --- + +/** Input format for todo selection */ +export interface TodoInput { + title: string; + priority?: TodoPriority; + /** Due date as Date or Unix timestamp (ms) */ + dueDate?: Date | number; + /** Is completed */ + completed?: boolean; + /** Higher = more important */ + importance?: number; +} + +/** + * Select the most important todos for QR export + * + * Priority order: + * 1. Priority 1 (high) + * 2. Priority 2 (medium) + * 3. Priority 3 (low) + * 4. By due date (sooner = higher) + * 5. By importance score + * + * Excludes completed todos. + */ +export function selectPriorityTodos( + todos: TodoInput[], + limit: number = MANA_QR_LIMITS.MAX_TODOS, + fromDate: Date = new Date() +): ManaQRTodo[] { + const fromTimestamp = fromDate.getTime(); + const fromDayStart = new Date(fromDate); + fromDayStart.setHours(0, 0, 0, 0); + + // Filter out completed todos and score the rest + const scored = todos + .filter((t) => !t.completed) + .map((t, index) => { + let score = 0; + + // Priority is most important + const priority = t.priority || 3; + score += (4 - priority) * 1000; // P1=3000, P2=2000, P3=1000 + + // Due date matters + if (t.dueDate) { + const dueMs = t.dueDate instanceof Date ? t.dueDate.getTime() : t.dueDate; + const daysUntilDue = Math.floor((dueMs - fromTimestamp) / 86400000); + // Overdue items get highest boost + if (daysUntilDue < 0) { + score += 500; + } else if (daysUntilDue <= 7) { + score += 300 - daysUntilDue * 10; + } + } + + // Custom importance + if (t.importance) score += t.importance; + + // Original order as tiebreaker + score -= index * 0.01; + + return { todo: t, score }; + }); + + // Sort by score descending + scored.sort((a, b) => b.score - a.score); + + // Take top N and convert to compact format + return scored.slice(0, limit).map(({ todo }) => { + let dueDays: number | undefined; + if (todo.dueDate) { + const dueMs = todo.dueDate instanceof Date ? todo.dueDate.getTime() : todo.dueDate; + const daysFromNow = Math.floor((dueMs - fromDayStart.getTime()) / 86400000); + // Clamp to 0-255 range + dueDays = Math.max(0, Math.min(255, daysFromNow)); + } + + return { + t: truncate(todo.title, MANA_QR_LIMITS.MAX_TODO_TITLE), + p: todo.priority || 3, + d: dueDays, + }; + }); +} + +// --- Utility --- + +function truncate(str: string, maxLength: number): string { + if (str.length <= maxLength) return str; + return str.slice(0, maxLength - 1) + '…'; +} diff --git a/packages/qr-export/src/svelte/ManaQRCode.svelte b/packages/qr-export/src/svelte/ManaQRCode.svelte new file mode 100644 index 000000000..87a018631 --- /dev/null +++ b/packages/qr-export/src/svelte/ManaQRCode.svelte @@ -0,0 +1,144 @@ + + +{#if error} + +{:else if qrOutput} + {#if svg} + + {:else} + + {/if} +{:else} +
+ +
+{/if} + + diff --git a/packages/qr-export/src/svelte/ManaQRScanner.svelte b/packages/qr-export/src/svelte/ManaQRScanner.svelte new file mode 100644 index 000000000..5f3d5188a --- /dev/null +++ b/packages/qr-export/src/svelte/ManaQRScanner.svelte @@ -0,0 +1,338 @@ + + +
+ {#if error} + + {/if} + +
+ + + + {#if showOverlay && isScanning} +
+
+
+ {/if} + + {#if !isScanning} +
+ + + + + + + + Ready to scan +
+ {/if} +
+ +
+ {#if isScanning} + + {:else} + + + {/if} +
+
+ + diff --git a/packages/qr-export/src/svelte/index.ts b/packages/qr-export/src/svelte/index.ts new file mode 100644 index 000000000..2c26e7261 --- /dev/null +++ b/packages/qr-export/src/svelte/index.ts @@ -0,0 +1,36 @@ +/** + * Svelte components for ManaQR + * + * @example + * ```svelte + * + * + * + * + * ``` + */ + +export { default as ManaQRCode } from './ManaQRCode.svelte'; +export { default as ManaQRScanner } from './ManaQRScanner.svelte'; + +// Re-export types for convenience +export type { + ManaQRExport, + ManaQRUserContext, + ManaQRContact, + ManaQREvent, + ManaQRTodo, + EncodeResult, + DecodeResult, +} from '../types'; diff --git a/packages/qr-export/src/types.ts b/packages/qr-export/src/types.ts new file mode 100644 index 000000000..d641c9245 --- /dev/null +++ b/packages/qr-export/src/types.ts @@ -0,0 +1,142 @@ +/** + * ManaQR Export Format Types + * + * Compact format for exporting personal data to QR codes. + * Target size: ~2,500 bytes (fits in single QR code) + */ + +/** Relation type for contacts */ +export type ContactRelation = + | 1 // Familie + | 2 // Partner + | 3 // Freund + | 4 // Arbeit + | 5; // Notfall + +/** Priority level for todos */ +export type TodoPriority = + | 1 // Hoch + | 2 // Mittel + | 3; // Niedrig + +/** Minimal contact data */ +export interface ManaQRContact { + /** Name (required) */ + n: string; + /** Phone number */ + p?: string; + /** Email */ + e?: string; + /** Relation type */ + r: ContactRelation; +} + +/** Minimal calendar event data */ +export interface ManaQREvent { + /** Title (max 30 chars recommended) */ + t: string; + /** Start time as Unix timestamp (seconds) */ + s: number; + /** Duration in minutes */ + d: number; + /** Location (short) */ + l?: string; +} + +/** Minimal todo data */ +export interface ManaQRTodo { + /** Title (max 40 chars recommended) */ + t: string; + /** Priority 1-3 */ + p: TodoPriority; + /** Due date as days from export date (0-255) */ + d?: number; +} + +/** User context data */ +export interface ManaQRUserContext { + /** Name */ + n: string; + /** Timezone (e.g., "Europe/Berlin") */ + z?: string; + /** Language code (e.g., "de") */ + l?: string; + /** Location/City */ + w?: string; + /** Profession/Job */ + b?: string; + /** Status/Motto */ + m?: string; +} + +/** Main export format */ +export interface ManaQRExport { + /** Format version */ + v: 1; + /** Export timestamp (Unix seconds) */ + ts: number; + /** User context */ + u: ManaQRUserContext; + /** Top contacts (recommended: 5) */ + c: ManaQRContact[]; + /** Upcoming events (recommended: 10) */ + e: ManaQREvent[]; + /** Priority todos (recommended: 15) */ + t: ManaQRTodo[]; +} + +/** Result of encoding */ +export interface EncodeResult { + /** Encoded string for QR code */ + data: string; + /** Size in bytes */ + size: number; + /** Whether it fits in a single QR code */ + fitsInQR: boolean; +} + +/** Decode error types */ +export type DecodeError = + | 'INVALID_PREFIX' + | 'INVALID_BASE64' + | 'DECOMPRESSION_FAILED' + | 'INVALID_JSON' + | 'INVALID_VERSION' + | 'INVALID_STRUCTURE'; + +/** Result of decoding */ +export type DecodeResult = + | { success: true; data: ManaQRExport } + | { success: false; error: DecodeError; message: string }; + +/** Limits for QR export */ +export const MANA_QR_LIMITS = { + /** Max bytes for QR code (with some buffer) */ + MAX_QR_BYTES: 2500, + /** Recommended max contacts */ + MAX_CONTACTS: 5, + /** Recommended max events */ + MAX_EVENTS: 10, + /** Recommended max todos */ + MAX_TODOS: 15, + /** Max title length for events */ + MAX_EVENT_TITLE: 30, + /** Max title length for todos */ + MAX_TODO_TITLE: 40, +} as const; + +/** Relation labels (for UI) */ +export const RELATION_LABELS: Record = { + 1: 'Familie', + 2: 'Partner', + 3: 'Freund', + 4: 'Arbeit', + 5: 'Notfall', +}; + +/** Priority labels (for UI) */ +export const PRIORITY_LABELS: Record = { + 1: 'Hoch', + 2: 'Mittel', + 3: 'Niedrig', +}; diff --git a/packages/qr-export/src/wallpaper.ts b/packages/qr-export/src/wallpaper.ts new file mode 100644 index 000000000..648d8313e --- /dev/null +++ b/packages/qr-export/src/wallpaper.ts @@ -0,0 +1,124 @@ +/** + * QR Code Wallpaper Generation + * + * Creates device wallpapers from QR codes using @manacore/wallpaper-generator. + */ + +import type { ManaQRExport, EncodeResult } from './types'; +import type { + WallpaperOptions, + WallpaperResult, + DeviceOption, + Layout, + Background, +} from '@manacore/wallpaper-generator'; +import { + createWallpaperGenerator, + DEFAULT_CENTER_LAYOUT, + DEFAULT_BACKGROUND, +} from '@manacore/wallpaper-generator'; +import { toDataURL } from './generate'; + +/** Options for QR wallpaper generation */ +export interface QRWallpaperOptions { + /** Target device (preset ID like 'iphone-15-pro-max' or custom dimensions) */ + device: DeviceOption; + /** Layout configuration (default: center) */ + layout?: Layout; + /** Background configuration (default: dark gradient) */ + background?: Background; + /** QR code size before placing on wallpaper (default: 600) */ + qrSize?: number; + /** Output format (default: 'png') */ + format?: 'png' | 'jpeg'; + /** JPEG quality 0-100 (default: 90) */ + quality?: number; +} + +const DEFAULT_QR_WALLPAPER_OPTIONS: Partial = { + layout: DEFAULT_CENTER_LAYOUT, + background: DEFAULT_BACKGROUND, + qrSize: 600, + format: 'png', + quality: 90, +}; + +/** + * Generate a device wallpaper from QR code data. + * + * @param data - QR code data (string, ManaQRExport, or EncodeResult) + * @param options - Wallpaper generation options + * @returns Promise with wallpaper result + * + * @example + * ```ts + * import { toWallpaper } from '@manacore/qr-export/wallpaper'; + * + * const result = await toWallpaper(encodeResult, { + * device: 'iphone-15-pro-max', + * layout: { type: 'center', scale: 0.7 }, + * background: { type: 'gradient', colors: ['#1a1a2e', '#16213e'] }, + * }); + * + * // result.dataUrl contains the wallpaper as data URL + * ``` + */ +export async function toWallpaper( + data: string | ManaQRExport | EncodeResult, + options: QRWallpaperOptions +): Promise { + const opts = { ...DEFAULT_QR_WALLPAPER_OPTIONS, ...options }; + + // Generate QR code as data URL + const qrDataUrl = await toDataURL(data, { + size: opts.qrSize, + errorCorrectionLevel: 'M', + darkColor: '#000000', + lightColor: '#ffffff', + }); + + // Create wallpaper generator + const generator = createWallpaperGenerator(); + + // Generate wallpaper + const wallpaperOptions: WallpaperOptions = { + device: opts.device, + layout: opts.layout ?? DEFAULT_CENTER_LAYOUT, + background: opts.background ?? DEFAULT_BACKGROUND, + format: opts.format, + quality: opts.quality, + }; + + return generator.generate({ type: 'dataUrl', data: qrDataUrl }, wallpaperOptions); +} + +/** + * Generate a preview of the QR wallpaper (smaller, faster). + * + * @param data - QR code data + * @param options - Wallpaper generation options + * @returns Promise with preview data URL + */ +export async function toWallpaperPreview( + data: string | ManaQRExport | EncodeResult, + options: QRWallpaperOptions +): Promise { + const opts = { ...DEFAULT_QR_WALLPAPER_OPTIONS, ...options }; + + // Generate QR code at smaller size for preview + const qrSize = opts.qrSize ?? 600; + const qrDataUrl = await toDataURL(data, { + size: Math.round(qrSize / 2), + errorCorrectionLevel: 'M', + }); + + const generator = createWallpaperGenerator(); + + const wallpaperOptions: WallpaperOptions = { + device: opts.device, + layout: opts.layout ?? DEFAULT_CENTER_LAYOUT, + background: opts.background ?? DEFAULT_BACKGROUND, + }; + + return generator.preview({ type: 'dataUrl', data: qrDataUrl }, wallpaperOptions); +} diff --git a/packages/qr-export/tsconfig.json b/packages/qr-export/tsconfig.json new file mode 100644 index 000000000..c5aba2214 --- /dev/null +++ b/packages/qr-export/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM"], + "types": ["node"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "noEmit": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "vitest.config.ts"], + "exclude": ["node_modules", "src/**/*.svelte"] +} diff --git a/packages/qr-export/vitest.config.ts b/packages/qr-export/vitest.config.ts new file mode 100644 index 000000000..e28945bf4 --- /dev/null +++ b/packages/qr-export/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['src/**/*.test.ts'], + }, +}); diff --git a/packages/spiral-db/package.json b/packages/spiral-db/package.json index 592a6dac2..2b9a250ea 100644 --- a/packages/spiral-db/package.json +++ b/packages/spiral-db/package.json @@ -10,11 +10,15 @@ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" + }, + "./wallpaper": { + "types": "./dist/wallpaper.d.ts", + "import": "./dist/wallpaper.js" } }, "scripts": { - "build": "tsup src/index.ts --format esm --dts --clean", - "dev": "tsup src/index.ts --format esm --dts --watch", + "build": "tsup src/index.ts src/wallpaper.ts --format esm --dts --clean", + "dev": "tsup src/index.ts src/wallpaper.ts --format esm --dts --watch", "test": "vitest", "test:run": "vitest run", "type-check": "tsc --noEmit" @@ -31,11 +35,15 @@ "vitest": "^1.6.1" }, "peerDependencies": { - "sharp": "^0.33.0" + "sharp": "^0.33.0", + "@manacore/wallpaper-generator": "workspace:*" }, "peerDependenciesMeta": { "sharp": { "optional": true + }, + "@manacore/wallpaper-generator": { + "optional": true } }, "files": [ diff --git a/packages/spiral-db/src/wallpaper.ts b/packages/spiral-db/src/wallpaper.ts new file mode 100644 index 000000000..fccabc75a --- /dev/null +++ b/packages/spiral-db/src/wallpaper.ts @@ -0,0 +1,157 @@ +/** + * Spiral-DB Wallpaper Generation + * + * Creates device wallpapers from SpiralDB images using @manacore/wallpaper-generator. + */ + +import type { SpiralImage } from './types.js'; +import type { + WallpaperOptions, + WallpaperResult, + DeviceOption, + Layout, + Background, +} from '@manacore/wallpaper-generator'; +import { + createWallpaperGenerator, + DEFAULT_CENTER_LAYOUT, + DEFAULT_BACKGROUND, +} from '@manacore/wallpaper-generator'; +import { exportToDataUrl } from './png.js'; + +/** Options for Spiral wallpaper generation */ +export interface SpiralWallpaperOptions { + /** Target device (preset ID like 'iphone-15-pro-max' or custom dimensions) */ + device: DeviceOption; + /** Layout configuration (default: center) */ + layout?: Layout; + /** Background configuration (default: dark gradient) */ + background?: Background; + /** Scale factor for the spiral image (default: 10 for crisp pixel art) */ + scale?: number; + /** Output format (default: 'png') */ + format?: 'png' | 'jpeg'; + /** JPEG quality 0-100 (default: 90) */ + quality?: number; +} + +const DEFAULT_SPIRAL_WALLPAPER_OPTIONS: Partial = { + layout: DEFAULT_CENTER_LAYOUT, + background: DEFAULT_BACKGROUND, + scale: 10, + format: 'png', + quality: 90, +}; + +/** + * Generate a device wallpaper from a SpiralDB image. + * + * @param image - SpiralDB image + * @param options - Wallpaper generation options + * @returns Promise with wallpaper result + * + * @example + * ```ts + * import { SpiralDB, createTodoSchema } from '@manacore/spiral-db'; + * import { toWallpaper } from '@manacore/spiral-db/wallpaper'; + * + * const db = new SpiralDB({ schema: createTodoSchema() }); + * db.insert({ title: 'My Todo', ... }); + * + * const image = db.getImage(); + * const result = await toWallpaper(image, { + * device: 'iphone-15-pro-max', + * layout: { type: 'corner', position: 'bottom-right', scale: 0.3 }, + * background: { type: 'gradient', colors: ['#0f0f23', '#1a1a2e'] }, + * }); + * + * // result.dataUrl contains the wallpaper as data URL + * ``` + */ +export async function toWallpaper( + image: SpiralImage, + options: SpiralWallpaperOptions +): Promise { + const opts = { ...DEFAULT_SPIRAL_WALLPAPER_OPTIONS, ...options }; + + // Convert spiral image to data URL + const dataUrl = exportToDataUrl(image); + + // Create wallpaper generator + const generator = createWallpaperGenerator(); + + // Apply scale to layout + const layoutConfig = opts.layout ?? DEFAULT_CENTER_LAYOUT; + const scaleValue = opts.scale ?? 10; + const layout = applyScaleToLayout(layoutConfig, scaleValue); + + // Generate wallpaper + const wallpaperOptions: WallpaperOptions = { + device: opts.device, + layout, + background: opts.background ?? DEFAULT_BACKGROUND, + format: opts.format, + quality: opts.quality, + }; + + return generator.generate({ type: 'dataUrl', data: dataUrl }, wallpaperOptions); +} + +/** + * Generate a preview of the Spiral wallpaper (smaller, faster). + * + * @param image - SpiralDB image + * @param options - Wallpaper generation options + * @returns Promise with preview data URL + */ +export async function toWallpaperPreview( + image: SpiralImage, + options: SpiralWallpaperOptions +): Promise { + const opts = { ...DEFAULT_SPIRAL_WALLPAPER_OPTIONS, ...options }; + + // Convert spiral image to data URL + const dataUrl = exportToDataUrl(image); + + const generator = createWallpaperGenerator(); + + // Apply scale to layout (reduced for preview) + const layoutConfig = opts.layout ?? DEFAULT_CENTER_LAYOUT; + const scaleValue = opts.scale ?? 10; + const layout = applyScaleToLayout(layoutConfig, scaleValue * 0.5); + + const wallpaperOptions: WallpaperOptions = { + device: opts.device, + layout, + background: opts.background ?? DEFAULT_BACKGROUND, + }; + + return generator.preview({ type: 'dataUrl', data: dataUrl }, wallpaperOptions); +} + +/** + * Apply scale factor to layout + * For spiral images which are typically small (e.g., 11x11 pixels), + * we multiply the scale to make them visible on wallpapers. + */ +function applyScaleToLayout(layout: Layout, scale: number): Layout { + switch (layout.type) { + case 'center': + return { + ...layout, + scale: (layout.scale ?? 1.0) * scale, + }; + case 'corner': + return { + ...layout, + scale: (layout.scale ?? 0.3) * scale, + }; + case 'pattern': + return { + ...layout, + scale: (layout.scale ?? 0.5) * scale, + }; + default: + return layout; + } +} diff --git a/packages/wallpaper-generator/package.json b/packages/wallpaper-generator/package.json new file mode 100644 index 000000000..181db8601 --- /dev/null +++ b/packages/wallpaper-generator/package.json @@ -0,0 +1,38 @@ +{ + "name": "@manacore/wallpaper-generator", + "version": "0.1.0", + "private": true, + "description": "Device wallpaper generator from QR codes and images", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts", + "./svelte": "./src/svelte/index.ts", + "./presets": "./src/presets/index.ts" + }, + "scripts": { + "type-check": "tsc --noEmit", + "clean": "rm -rf dist", + "lint": "eslint .", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": {}, + "devDependencies": { + "@types/node": "^24.10.1", + "typescript": "^5.9.3", + "vitest": "^3.0.5" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "sharp": "^0.33.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + }, + "sharp": { + "optional": true + } + } +} diff --git a/packages/wallpaper-generator/src/backgrounds/gradient.ts b/packages/wallpaper-generator/src/backgrounds/gradient.ts new file mode 100644 index 000000000..5f4b5349c --- /dev/null +++ b/packages/wallpaper-generator/src/backgrounds/gradient.ts @@ -0,0 +1,160 @@ +/** + * Gradient Background Renderer + * + * Creates linear gradients on canvas. + */ + +import { parseHexColor } from './solid.js'; + +/** + * Calculate gradient end points from angle + * Angle: 0 = bottom to top, 90 = left to right, 180 = top to bottom + */ +function getGradientCoordinates( + width: number, + height: number, + angleDegrees: number +): { x0: number; y0: number; x1: number; y1: number } { + // Convert angle to radians (CSS gradient angles: 0deg = to top, 180deg = to bottom) + const angleRad = ((angleDegrees - 90) * Math.PI) / 180; + + // Calculate the diagonal length for proper coverage + const diagonal = Math.sqrt(width * width + height * height); + + const centerX = width / 2; + const centerY = height / 2; + + const dx = Math.cos(angleRad) * diagonal; + const dy = Math.sin(angleRad) * diagonal; + + return { + x0: centerX - dx / 2, + y0: centerY - dy / 2, + x1: centerX + dx / 2, + y1: centerY + dy / 2, + }; +} + +/** + * Fill canvas with linear gradient (browser) + */ +export function fillGradient( + ctx: CanvasRenderingContext2D, + width: number, + height: number, + colors: string[], + angle = 180 +): void { + if (colors.length === 0) { + ctx.fillStyle = '#000000'; + ctx.fillRect(0, 0, width, height); + return; + } + + if (colors.length === 1) { + ctx.fillStyle = colors[0]; + ctx.fillRect(0, 0, width, height); + return; + } + + const { x0, y0, x1, y1 } = getGradientCoordinates(width, height, angle); + const gradient = ctx.createLinearGradient(x0, y0, x1, y1); + + // Distribute color stops evenly + colors.forEach((color, index) => { + const stop = index / (colors.length - 1); + gradient.addColorStop(stop, color); + }); + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, width, height); +} + +/** + * Interpolate between two colors + */ +function interpolateColor( + color1: { r: number; g: number; b: number }, + color2: { r: number; g: number; b: number }, + t: number +): { r: number; g: number; b: number } { + return { + r: Math.round(color1.r + (color2.r - color1.r) * t), + g: Math.round(color1.g + (color2.g - color1.g) * t), + b: Math.round(color1.b + (color2.b - color1.b) * t), + }; +} + +/** + * Get color at position in gradient + */ +function getGradientColorAt( + colors: { r: number; g: number; b: number }[], + position: number +): { r: number; g: number; b: number } { + if (colors.length === 0) return { r: 0, g: 0, b: 0 }; + if (colors.length === 1) return colors[0]; + if (position <= 0) return colors[0]; + if (position >= 1) return colors[colors.length - 1]; + + const scaledPosition = position * (colors.length - 1); + const index = Math.floor(scaledPosition); + const t = scaledPosition - index; + + return interpolateColor(colors[index], colors[Math.min(index + 1, colors.length - 1)], t); +} + +/** + * Create gradient buffer for Node.js/Sharp + */ +export function createGradientBuffer( + width: number, + height: number, + colors: string[], + angle = 180 +): Uint8Array { + const buffer = new Uint8Array(width * height * 3); + + if (colors.length === 0) { + return buffer; // All zeros (black) + } + + const parsedColors = colors.map(parseHexColor); + + if (colors.length === 1) { + const color = parsedColors[0]; + for (let i = 0; i < width * height; i++) { + buffer[i * 3] = color.r; + buffer[i * 3 + 1] = color.g; + buffer[i * 3 + 2] = color.b; + } + return buffer; + } + + // Convert angle to radians for calculation + const angleRad = ((angle - 90) * Math.PI) / 180; + const cos = Math.cos(angleRad); + const sin = Math.sin(angleRad); + + // Calculate the projection for each pixel + const diagonal = Math.sqrt(width * width + height * height); + const centerX = width / 2; + const centerY = height / 2; + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + // Calculate position along gradient axis + const dx = x - centerX; + const dy = y - centerY; + const projection = (dx * cos + dy * sin) / diagonal + 0.5; + + const color = getGradientColorAt(parsedColors, Math.max(0, Math.min(1, projection))); + const i = (y * width + x) * 3; + buffer[i] = color.r; + buffer[i + 1] = color.g; + buffer[i + 2] = color.b; + } + } + + return buffer; +} diff --git a/packages/wallpaper-generator/src/backgrounds/index.ts b/packages/wallpaper-generator/src/backgrounds/index.ts new file mode 100644 index 000000000..76e64d392 --- /dev/null +++ b/packages/wallpaper-generator/src/backgrounds/index.ts @@ -0,0 +1,6 @@ +/** + * Background Exports + */ + +export { fillSolid, createSolidBuffer, parseHexColor } from './solid.js'; +export { fillGradient, createGradientBuffer } from './gradient.js'; diff --git a/packages/wallpaper-generator/src/backgrounds/solid.ts b/packages/wallpaper-generator/src/backgrounds/solid.ts new file mode 100644 index 000000000..521b3d4e8 --- /dev/null +++ b/packages/wallpaper-generator/src/backgrounds/solid.ts @@ -0,0 +1,57 @@ +/** + * Solid Background Renderer + * + * Fills canvas with a solid color. + */ + +/** + * Parse hex color to RGB values + */ +export function parseHexColor(hex: string): { r: number; g: number; b: number } { + // Remove # if present + const cleanHex = hex.replace('#', ''); + + // Handle shorthand hex (e.g., #fff) + const fullHex = + cleanHex.length === 3 + ? cleanHex + .split('') + .map((c) => c + c) + .join('') + : cleanHex; + + const r = parseInt(fullHex.substring(0, 2), 16); + const g = parseInt(fullHex.substring(2, 4), 16); + const b = parseInt(fullHex.substring(4, 6), 16); + + return { r, g, b }; +} + +/** + * Fill canvas with solid color (browser) + */ +export function fillSolid( + ctx: CanvasRenderingContext2D, + width: number, + height: number, + color: string +): void { + ctx.fillStyle = color; + ctx.fillRect(0, 0, width, height); +} + +/** + * Create solid color buffer for Node.js/Sharp + */ +export function createSolidBuffer(width: number, height: number, color: string): Uint8Array { + const { r, g, b } = parseHexColor(color); + const buffer = new Uint8Array(width * height * 3); + + for (let i = 0; i < width * height; i++) { + buffer[i * 3] = r; + buffer[i * 3 + 1] = g; + buffer[i * 3 + 2] = b; + } + + return buffer; +} diff --git a/packages/wallpaper-generator/src/index.ts b/packages/wallpaper-generator/src/index.ts new file mode 100644 index 000000000..71f0fed9b --- /dev/null +++ b/packages/wallpaper-generator/src/index.ts @@ -0,0 +1,140 @@ +/** + * @manacore/wallpaper-generator + * + * Device wallpaper generator from QR codes, Spiral-DB images, and other sources. + * Supports both browser (Canvas) and Node.js (Sharp) environments. + * + * @example Browser usage: + * ```ts + * import { createWallpaperGenerator } from '@manacore/wallpaper-generator'; + * + * const generator = createWallpaperGenerator(); + * + * const result = await generator.generate( + * { type: 'dataUrl', data: qrCodeDataUrl }, + * { + * device: 'iphone-15-pro-max', + * layout: { type: 'center', scale: 0.8 }, + * background: { type: 'gradient', colors: ['#1a1a2e', '#16213e'] }, + * } + * ); + * + * // Download the wallpaper + * downloadWallpaper(result, 'my-wallpaper.png'); + * ``` + * + * @example Node.js usage: + * ```ts + * import { createWallpaperGenerator, saveWallpaperToFile } from '@manacore/wallpaper-generator'; + * + * const generator = createWallpaperGenerator(); + * + * const result = await generator.generate( + * { type: 'dataUrl', data: imageDataUrl }, + * { + * device: 'desktop-4k', + * layout: { type: 'corner', position: 'bottom-right', scale: 0.2 }, + * background: { type: 'solid', color: '#0f0f23' }, + * } + * ); + * + * await saveWallpaperToFile(result, './wallpaper.png'); + * ``` + */ + +// Types +export type { + // Image Sources + ImageSource, + DataUrlSource, + CanvasSource, + BufferSource, + // Device Presets + DevicePreset, + DeviceCategory, + CustomDevice, + DeviceOption, + // Layouts + Layout, + CenterLayout, + CornerLayout, + PatternLayout, + CornerPosition, + // Backgrounds + Background, + SolidBackground, + GradientBackground, + // Options & Results + WallpaperOptions, + WallpaperResult, + OutputFormat, + // Generator Interface + WallpaperGenerator, + // Svelte Props + WallpaperModalProps, +} from './types.js'; + +// Constants +export { + DEFAULT_CENTER_LAYOUT, + DEFAULT_CORNER_LAYOUT, + DEFAULT_PATTERN_LAYOUT, + DEFAULT_BACKGROUND, + GRADIENT_PRESETS, +} from './types.js'; + +// Device Presets +export { + PHONE_PRESETS, + TABLET_PRESETS, + DESKTOP_PRESETS, + ALL_DEVICE_PRESETS, + getDevicePreset, + getPresetsByCategory, + getRecommendedPresets, +} from './presets/index.js'; + +// Renderers +export { + createBrowserGenerator, + downloadWallpaper, + copyWallpaperToClipboard, +} from './renderers/browser.js'; +export { createNodeGenerator, saveWallpaperToFile } from './renderers/node.js'; + +// ============================================================================= +// FACTORY FUNCTION +// ============================================================================= + +import type { WallpaperGenerator } from './types.js'; +import { createBrowserGenerator } from './renderers/browser.js'; +import { createNodeGenerator } from './renderers/node.js'; + +/** + * Detect if running in browser environment + */ +function isBrowser(): boolean { + return typeof window !== 'undefined' && typeof document !== 'undefined'; +} + +/** + * Create a wallpaper generator appropriate for the current environment. + * + * - In browser: Uses Canvas API + * - In Node.js: Uses Sharp + * + * @param options - Optional configuration + * @param options.preferNode - Force Node.js renderer even in browser (requires Sharp) + * @returns WallpaperGenerator instance + */ +export function createWallpaperGenerator(options?: { preferNode?: boolean }): WallpaperGenerator { + if (options?.preferNode) { + return createNodeGenerator(); + } + + if (isBrowser()) { + return createBrowserGenerator(); + } + + return createNodeGenerator(); +} diff --git a/packages/wallpaper-generator/src/layouts/center.ts b/packages/wallpaper-generator/src/layouts/center.ts new file mode 100644 index 000000000..624403d3a --- /dev/null +++ b/packages/wallpaper-generator/src/layouts/center.ts @@ -0,0 +1,59 @@ +/** + * Center Layout + * + * Places image centered on the wallpaper. + */ + +import type { CenterLayout } from '../types.js'; + +export interface CenterPosition { + x: number; + y: number; + width: number; + height: number; +} + +/** + * Calculate center position for image + */ +export function calculateCenterPosition( + canvasWidth: number, + canvasHeight: number, + imageWidth: number, + imageHeight: number, + layout: CenterLayout +): CenterPosition { + const scale = layout.scale ?? 1.0; + const offset = layout.offset ?? [0, 0]; + + const scaledWidth = imageWidth * scale; + const scaledHeight = imageHeight * scale; + + // Center the image + const x = (canvasWidth - scaledWidth) / 2 + offset[0]; + const y = (canvasHeight - scaledHeight) / 2 + offset[1]; + + return { + x: Math.round(x), + y: Math.round(y), + width: Math.round(scaledWidth), + height: Math.round(scaledHeight), + }; +} + +/** + * Draw image centered on canvas (browser) + */ +export function drawCentered( + ctx: CanvasRenderingContext2D, + image: HTMLImageElement | HTMLCanvasElement, + canvasWidth: number, + canvasHeight: number, + layout: CenterLayout +): void { + const pos = calculateCenterPosition(canvasWidth, canvasHeight, image.width, image.height, layout); + + // Use crisp rendering for pixel art / QR codes + ctx.imageSmoothingEnabled = false; + ctx.drawImage(image, pos.x, pos.y, pos.width, pos.height); +} diff --git a/packages/wallpaper-generator/src/layouts/corner.ts b/packages/wallpaper-generator/src/layouts/corner.ts new file mode 100644 index 000000000..9e79f7760 --- /dev/null +++ b/packages/wallpaper-generator/src/layouts/corner.ts @@ -0,0 +1,79 @@ +/** + * Corner Layout + * + * Places image in one of the four corners. + */ + +import type { CornerLayout } from '../types.js'; + +export interface CornerPositionResult { + x: number; + y: number; + width: number; + height: number; +} + +/** + * Calculate corner position for image + */ +export function calculateCornerPosition( + canvasWidth: number, + canvasHeight: number, + imageWidth: number, + imageHeight: number, + layout: CornerLayout +): CornerPositionResult { + const scale = layout.scale ?? 0.3; + const padding = layout.padding ?? 40; + const position = layout.position ?? 'bottom-right'; + + const scaledWidth = imageWidth * scale; + const scaledHeight = imageHeight * scale; + + let x: number; + let y: number; + + switch (position) { + case 'top-left': + x = padding; + y = padding; + break; + case 'top-right': + x = canvasWidth - scaledWidth - padding; + y = padding; + break; + case 'bottom-left': + x = padding; + y = canvasHeight - scaledHeight - padding; + break; + case 'bottom-right': + default: + x = canvasWidth - scaledWidth - padding; + y = canvasHeight - scaledHeight - padding; + break; + } + + return { + x: Math.round(x), + y: Math.round(y), + width: Math.round(scaledWidth), + height: Math.round(scaledHeight), + }; +} + +/** + * Draw image in corner on canvas (browser) + */ +export function drawCorner( + ctx: CanvasRenderingContext2D, + image: HTMLImageElement | HTMLCanvasElement, + canvasWidth: number, + canvasHeight: number, + layout: CornerLayout +): void { + const pos = calculateCornerPosition(canvasWidth, canvasHeight, image.width, image.height, layout); + + // Use crisp rendering for pixel art / QR codes + ctx.imageSmoothingEnabled = false; + ctx.drawImage(image, pos.x, pos.y, pos.width, pos.height); +} diff --git a/packages/wallpaper-generator/src/layouts/index.ts b/packages/wallpaper-generator/src/layouts/index.ts new file mode 100644 index 000000000..21b1c43dc --- /dev/null +++ b/packages/wallpaper-generator/src/layouts/index.ts @@ -0,0 +1,11 @@ +/** + * Layout Exports + */ + +export { calculateCenterPosition, drawCentered } from './center.js'; +export { calculateCornerPosition, drawCorner } from './corner.js'; +export { calculatePatternTiles, drawPattern } from './pattern.js'; + +export type { CenterPosition } from './center.js'; +export type { CornerPositionResult } from './corner.js'; +export type { PatternTile } from './pattern.js'; diff --git a/packages/wallpaper-generator/src/layouts/pattern.ts b/packages/wallpaper-generator/src/layouts/pattern.ts new file mode 100644 index 000000000..d63b29737 --- /dev/null +++ b/packages/wallpaper-generator/src/layouts/pattern.ts @@ -0,0 +1,88 @@ +/** + * Pattern Layout + * + * Tiles image across the wallpaper as a repeating pattern. + */ + +import type { PatternLayout } from '../types.js'; + +export interface PatternTile { + x: number; + y: number; + width: number; + height: number; +} + +/** + * Calculate tile positions for pattern + */ +export function calculatePatternTiles( + canvasWidth: number, + canvasHeight: number, + imageWidth: number, + imageHeight: number, + layout: PatternLayout +): PatternTile[] { + const scale = layout.scale ?? 0.5; + const gap = layout.gap ?? 20; + + const tileWidth = imageWidth * scale; + const tileHeight = imageHeight * scale; + + const tiles: PatternTile[] = []; + + // Calculate how many tiles fit (with overlap to cover edges) + const cols = Math.ceil(canvasWidth / (tileWidth + gap)) + 1; + const rows = Math.ceil(canvasHeight / (tileHeight + gap)) + 1; + + // Center the pattern grid + const totalPatternWidth = cols * tileWidth + (cols - 1) * gap; + const totalPatternHeight = rows * tileHeight + (rows - 1) * gap; + + const startX = (canvasWidth - totalPatternWidth) / 2; + const startY = (canvasHeight - totalPatternHeight) / 2; + + for (let row = 0; row < rows; row++) { + for (let col = 0; col < cols; col++) { + tiles.push({ + x: Math.round(startX + col * (tileWidth + gap)), + y: Math.round(startY + row * (tileHeight + gap)), + width: Math.round(tileWidth), + height: Math.round(tileHeight), + }); + } + } + + return tiles; +} + +/** + * Draw pattern on canvas (browser) + */ +export function drawPattern( + ctx: CanvasRenderingContext2D, + image: HTMLImageElement | HTMLCanvasElement, + canvasWidth: number, + canvasHeight: number, + layout: PatternLayout +): void { + const opacity = layout.opacity ?? 0.15; + const tiles = calculatePatternTiles(canvasWidth, canvasHeight, image.width, image.height, layout); + + // Save current state + ctx.save(); + + // Set opacity for pattern + ctx.globalAlpha = opacity; + + // Use crisp rendering for pixel art / QR codes + ctx.imageSmoothingEnabled = false; + + // Draw all tiles + for (const tile of tiles) { + ctx.drawImage(image, tile.x, tile.y, tile.width, tile.height); + } + + // Restore state + ctx.restore(); +} diff --git a/packages/wallpaper-generator/src/presets/devices.ts b/packages/wallpaper-generator/src/presets/devices.ts new file mode 100644 index 000000000..c48b4226c --- /dev/null +++ b/packages/wallpaper-generator/src/presets/devices.ts @@ -0,0 +1,336 @@ +/** + * Device Presets + * + * Standard device screen dimensions for wallpaper generation. + * Dimensions are in portrait orientation (height > width). + */ + +import type { DevicePreset, DeviceCategory } from '../types.js'; + +// ============================================================================= +// PHONE PRESETS +// ============================================================================= + +export const PHONE_PRESETS: DevicePreset[] = [ + // Apple iPhones + { + id: 'iphone-15-pro-max', + name: 'iPhone 15 Pro Max', + category: 'phone', + width: 1290, + height: 2796, + pixelRatio: 3, + }, + { + id: 'iphone-15-pro', + name: 'iPhone 15 Pro', + category: 'phone', + width: 1179, + height: 2556, + pixelRatio: 3, + }, + { + id: 'iphone-15', + name: 'iPhone 15', + category: 'phone', + width: 1179, + height: 2556, + pixelRatio: 3, + }, + { + id: 'iphone-14', + name: 'iPhone 14', + category: 'phone', + width: 1170, + height: 2532, + pixelRatio: 3, + }, + { + id: 'iphone-se', + name: 'iPhone SE', + category: 'phone', + width: 750, + height: 1334, + pixelRatio: 2, + }, + + // Google Pixels + { + id: 'pixel-8-pro', + name: 'Pixel 8 Pro', + category: 'phone', + width: 1344, + height: 2992, + pixelRatio: 3, + }, + { + id: 'pixel-8', + name: 'Pixel 8', + category: 'phone', + width: 1080, + height: 2400, + pixelRatio: 2.625, + }, + { + id: 'pixel-7a', + name: 'Pixel 7a', + category: 'phone', + width: 1080, + height: 2400, + pixelRatio: 2.5, + }, + + // Samsung Galaxy + { + id: 'samsung-s24-ultra', + name: 'Samsung S24 Ultra', + category: 'phone', + width: 1440, + height: 3120, + pixelRatio: 3.75, + }, + { + id: 'samsung-s24', + name: 'Samsung S24', + category: 'phone', + width: 1080, + height: 2340, + pixelRatio: 2.625, + }, + { + id: 'samsung-a54', + name: 'Samsung A54', + category: 'phone', + width: 1080, + height: 2340, + pixelRatio: 2.625, + }, + + // Generic Android + { + id: 'android-fhd', + name: 'Android FHD+', + category: 'phone', + width: 1080, + height: 2400, + pixelRatio: 2.5, + }, + { + id: 'android-hd', + name: 'Android HD+', + category: 'phone', + width: 720, + height: 1600, + pixelRatio: 2, + }, +]; + +// ============================================================================= +// TABLET PRESETS +// ============================================================================= + +export const TABLET_PRESETS: DevicePreset[] = [ + // Apple iPads + { + id: 'ipad-pro-12.9', + name: 'iPad Pro 12.9"', + category: 'tablet', + width: 2048, + height: 2732, + pixelRatio: 2, + }, + { + id: 'ipad-pro-11', + name: 'iPad Pro 11"', + category: 'tablet', + width: 1668, + height: 2388, + pixelRatio: 2, + }, + { + id: 'ipad-air', + name: 'iPad Air', + category: 'tablet', + width: 1640, + height: 2360, + pixelRatio: 2, + }, + { + id: 'ipad-mini', + name: 'iPad mini', + category: 'tablet', + width: 1488, + height: 2266, + pixelRatio: 2, + }, + { + id: 'ipad-10th', + name: 'iPad 10th Gen', + category: 'tablet', + width: 1640, + height: 2360, + pixelRatio: 2, + }, + + // Android Tablets + { + id: 'samsung-tab-s9-ultra', + name: 'Samsung Tab S9 Ultra', + category: 'tablet', + width: 1848, + height: 2960, + pixelRatio: 2, + }, + { + id: 'samsung-tab-s9', + name: 'Samsung Tab S9', + category: 'tablet', + width: 1600, + height: 2560, + pixelRatio: 2, + }, + { + id: 'android-tablet-generic', + name: 'Android Tablet', + category: 'tablet', + width: 1600, + height: 2560, + pixelRatio: 2, + }, +]; + +// ============================================================================= +// DESKTOP PRESETS +// ============================================================================= + +export const DESKTOP_PRESETS: DevicePreset[] = [ + // Standard Resolutions + { + id: 'desktop-4k', + name: '4K UHD', + category: 'desktop', + width: 3840, + height: 2160, + pixelRatio: 1, + }, + { + id: 'desktop-2k', + name: '2K QHD', + category: 'desktop', + width: 2560, + height: 1440, + pixelRatio: 1, + }, + { + id: 'desktop-fhd', + name: 'Full HD', + category: 'desktop', + width: 1920, + height: 1080, + pixelRatio: 1, + }, + + // Apple MacBooks + { + id: 'macbook-pro-16', + name: 'MacBook Pro 16"', + category: 'desktop', + width: 3456, + height: 2234, + pixelRatio: 2, + }, + { + id: 'macbook-pro-14', + name: 'MacBook Pro 14"', + category: 'desktop', + width: 3024, + height: 1964, + pixelRatio: 2, + }, + { + id: 'macbook-air-15', + name: 'MacBook Air 15"', + category: 'desktop', + width: 2880, + height: 1864, + pixelRatio: 2, + }, + { + id: 'macbook-air-13', + name: 'MacBook Air 13"', + category: 'desktop', + width: 2560, + height: 1664, + pixelRatio: 2, + }, + + // Ultrawide + { + id: 'ultrawide-34', + name: 'Ultrawide 34"', + category: 'desktop', + width: 3440, + height: 1440, + pixelRatio: 1, + }, + { + id: 'ultrawide-49', + name: 'Super Ultrawide 49"', + category: 'desktop', + width: 5120, + height: 1440, + pixelRatio: 1, + }, +]; + +// ============================================================================= +// ALL PRESETS +// ============================================================================= + +/** All device presets combined */ +export const ALL_DEVICE_PRESETS: DevicePreset[] = [ + ...PHONE_PRESETS, + ...TABLET_PRESETS, + ...DESKTOP_PRESETS, +]; + +/** Map of preset ID to preset for quick lookup */ +export const DEVICE_PRESET_MAP: Map = new Map( + ALL_DEVICE_PRESETS.map((preset) => [preset.id, preset]) +); + +// ============================================================================= +// HELPER FUNCTIONS +// ============================================================================= + +/** + * Get device preset by ID + */ +export function getDevicePreset(id: string): DevicePreset | undefined { + return DEVICE_PRESET_MAP.get(id); +} + +/** + * Get all presets for a category + */ +export function getPresetsByCategory(category: DeviceCategory): DevicePreset[] { + return ALL_DEVICE_PRESETS.filter((preset) => preset.category === category); +} + +/** + * Get recommended presets (most common devices) + */ +export function getRecommendedPresets(): DevicePreset[] { + const recommended = [ + 'iphone-15-pro-max', + 'iphone-15', + 'samsung-s24-ultra', + 'pixel-8-pro', + 'ipad-pro-12.9', + 'macbook-pro-16', + 'desktop-4k', + ]; + return recommended + .map((id) => DEVICE_PRESET_MAP.get(id)) + .filter((preset): preset is DevicePreset => preset !== undefined); +} diff --git a/packages/wallpaper-generator/src/presets/index.ts b/packages/wallpaper-generator/src/presets/index.ts new file mode 100644 index 000000000..8d23cd3be --- /dev/null +++ b/packages/wallpaper-generator/src/presets/index.ts @@ -0,0 +1,14 @@ +/** + * Device Presets Exports + */ + +export { + PHONE_PRESETS, + TABLET_PRESETS, + DESKTOP_PRESETS, + ALL_DEVICE_PRESETS, + DEVICE_PRESET_MAP, + getDevicePreset, + getPresetsByCategory, + getRecommendedPresets, +} from './devices.js'; diff --git a/packages/wallpaper-generator/src/renderers/browser.ts b/packages/wallpaper-generator/src/renderers/browser.ts new file mode 100644 index 000000000..aeaa4d365 --- /dev/null +++ b/packages/wallpaper-generator/src/renderers/browser.ts @@ -0,0 +1,316 @@ +/** + * Browser Renderer + * + * Canvas-based wallpaper generation for browser environments. + */ + +import type { + ImageSource, + WallpaperOptions, + WallpaperResult, + WallpaperGenerator, + DevicePreset, + DeviceCategory, + Layout, + Background, +} from '../types.js'; +import { ALL_DEVICE_PRESETS, getDevicePreset, getPresetsByCategory } from '../presets/index.js'; +import { fillSolid } from '../backgrounds/solid.js'; +import { fillGradient } from '../backgrounds/gradient.js'; +import { drawCentered } from '../layouts/center.js'; +import { drawCorner } from '../layouts/corner.js'; +import { drawPattern } from '../layouts/pattern.js'; + +// ============================================================================= +// IMAGE SOURCE LOADING +// ============================================================================= + +/** + * Load image from data URL + */ +async function loadImageFromDataUrl(dataUrl: string): Promise { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => resolve(img); + img.onerror = (e) => reject(new Error(`Failed to load image: ${e}`)); + img.src = dataUrl; + }); +} + +/** + * Load image from canvas + */ +function loadImageFromCanvas(canvas: HTMLCanvasElement): HTMLCanvasElement { + return canvas; +} + +/** + * Load image from buffer (convert to canvas) + */ +function loadImageFromBuffer( + buffer: Uint8Array, + width: number, + height: number, + channels: 3 | 4 = 4 +): HTMLCanvasElement { + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + + const ctx = canvas.getContext('2d'); + if (!ctx) throw new Error('Failed to get canvas context'); + + const imageData = ctx.createImageData(width, height); + + if (channels === 4) { + // RGBA - direct copy + imageData.data.set(buffer); + } else { + // RGB - need to add alpha channel + for (let i = 0; i < width * height; i++) { + imageData.data[i * 4] = buffer[i * 3]; + imageData.data[i * 4 + 1] = buffer[i * 3 + 1]; + imageData.data[i * 4 + 2] = buffer[i * 3 + 2]; + imageData.data[i * 4 + 3] = 255; + } + } + + ctx.putImageData(imageData, 0, 0); + return canvas; +} + +/** + * Convert ImageSource to drawable element + */ +async function loadSourceImage(source: ImageSource): Promise { + switch (source.type) { + case 'dataUrl': + return loadImageFromDataUrl(source.data); + case 'canvas': + return loadImageFromCanvas(source.canvas); + case 'buffer': + return loadImageFromBuffer(source.buffer, source.width, source.height, source.channels ?? 4); + default: + throw new Error('Unknown image source type'); + } +} + +// ============================================================================= +// BACKGROUND RENDERING +// ============================================================================= + +/** + * Draw background on canvas + */ +function drawBackground( + ctx: CanvasRenderingContext2D, + width: number, + height: number, + background: Background +): void { + if (background.type === 'solid') { + fillSolid(ctx, width, height, background.color); + } else if (background.type === 'gradient') { + fillGradient(ctx, width, height, background.colors, background.angle ?? 180); + } +} + +// ============================================================================= +// LAYOUT RENDERING +// ============================================================================= + +/** + * Draw image with layout + */ +function drawWithLayout( + ctx: CanvasRenderingContext2D, + image: HTMLImageElement | HTMLCanvasElement, + canvasWidth: number, + canvasHeight: number, + layout: Layout +): void { + switch (layout.type) { + case 'center': + drawCentered(ctx, image, canvasWidth, canvasHeight, layout); + break; + case 'corner': + drawCorner(ctx, image, canvasWidth, canvasHeight, layout); + break; + case 'pattern': + drawPattern(ctx, image, canvasWidth, canvasHeight, layout); + break; + default: + throw new Error('Unknown layout type'); + } +} + +// ============================================================================= +// DIMENSION RESOLUTION +// ============================================================================= + +/** + * Resolve device option to dimensions + */ +function resolveDimensions(device: string | { width: number; height: number }): { + width: number; + height: number; +} { + if (typeof device === 'string') { + const preset = getDevicePreset(device); + if (!preset) { + throw new Error(`Unknown device preset: ${device}`); + } + return { width: preset.width, height: preset.height }; + } + return device; +} + +// ============================================================================= +// BROWSER GENERATOR +// ============================================================================= + +/** + * Create browser-based wallpaper generator + */ +export function createBrowserGenerator(): WallpaperGenerator { + return { + async generate(source: ImageSource, options: WallpaperOptions): Promise { + const { width, height } = resolveDimensions(options.device); + const format = options.format ?? 'png'; + const quality = options.quality ?? 90; + + // Create canvas + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + + const ctx = canvas.getContext('2d'); + if (!ctx) throw new Error('Failed to get canvas context'); + + // Draw background + drawBackground(ctx, width, height, options.background); + + // Load and draw source image + const sourceImage = await loadSourceImage(source); + drawWithLayout(ctx, sourceImage, width, height, options.layout); + + // Export to data URL + const mimeType = format === 'jpeg' ? 'image/jpeg' : 'image/png'; + const dataUrl = canvas.toDataURL(mimeType, quality / 100); + + // Estimate size from base64 + const base64Length = dataUrl.split(',')[1]?.length ?? 0; + const size = Math.ceil((base64Length * 3) / 4); + + return { + dataUrl, + width, + height, + format, + size, + }; + }, + + async preview(source: ImageSource, options: WallpaperOptions): Promise { + // Generate at 1/4 resolution for preview + const { width, height } = resolveDimensions(options.device); + const previewWidth = Math.round(width / 4); + const previewHeight = Math.round(height / 4); + + const canvas = document.createElement('canvas'); + canvas.width = previewWidth; + canvas.height = previewHeight; + + const ctx = canvas.getContext('2d'); + if (!ctx) throw new Error('Failed to get canvas context'); + + // Draw background + drawBackground(ctx, previewWidth, previewHeight, options.background); + + // Load and draw source image (with scaled layout) + const sourceImage = await loadSourceImage(source); + + // Scale down the layout parameters + const scaledLayout = scaleLayout(options.layout, 0.25); + drawWithLayout(ctx, sourceImage, previewWidth, previewHeight, scaledLayout); + + return canvas.toDataURL('image/jpeg', 0.7); + }, + + getSupportedDevices(): DevicePreset[] { + return ALL_DEVICE_PRESETS; + }, + + getDevicesByCategory(category: DeviceCategory): DevicePreset[] { + return getPresetsByCategory(category); + }, + }; +} + +/** + * Scale layout parameters for preview + */ +function scaleLayout(layout: Layout, scaleFactor: number): Layout { + switch (layout.type) { + case 'center': + return { + ...layout, + offset: layout.offset + ? [layout.offset[0] * scaleFactor, layout.offset[1] * scaleFactor] + : undefined, + }; + case 'corner': + return { + ...layout, + padding: (layout.padding ?? 40) * scaleFactor, + }; + case 'pattern': + return { + ...layout, + gap: (layout.gap ?? 20) * scaleFactor, + }; + default: + return layout; + } +} + +// ============================================================================= +// BROWSER UTILITIES +// ============================================================================= + +/** + * Download wallpaper to user's device + */ +export function downloadWallpaper(result: WallpaperResult, filename?: string): void { + const defaultFilename = `wallpaper-${result.width}x${result.height}.${result.format}`; + const link = document.createElement('a'); + link.href = result.dataUrl; + link.download = filename ?? defaultFilename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +} + +/** + * Copy wallpaper to clipboard (if supported) + */ +export async function copyWallpaperToClipboard(result: WallpaperResult): Promise { + if (!navigator.clipboard?.write) { + return false; + } + + try { + // Convert data URL to blob + const response = await fetch(result.dataUrl); + const blob = await response.blob(); + + await navigator.clipboard.write([ + new ClipboardItem({ + [blob.type]: blob, + }), + ]); + return true; + } catch { + return false; + } +} diff --git a/packages/wallpaper-generator/src/renderers/index.ts b/packages/wallpaper-generator/src/renderers/index.ts new file mode 100644 index 000000000..7886f972d --- /dev/null +++ b/packages/wallpaper-generator/src/renderers/index.ts @@ -0,0 +1,6 @@ +/** + * Renderer Exports + */ + +export { createBrowserGenerator, downloadWallpaper, copyWallpaperToClipboard } from './browser.js'; +export { createNodeGenerator, saveWallpaperToFile } from './node.js'; diff --git a/packages/wallpaper-generator/src/renderers/node.ts b/packages/wallpaper-generator/src/renderers/node.ts new file mode 100644 index 000000000..5b92105d0 --- /dev/null +++ b/packages/wallpaper-generator/src/renderers/node.ts @@ -0,0 +1,397 @@ +/** + * Node.js Renderer + * + * Sharp-based wallpaper generation for Node.js environments. + */ + +import type { + ImageSource, + WallpaperOptions, + WallpaperResult, + WallpaperGenerator, + DevicePreset, + DeviceCategory, + Layout, + Background, + CenterLayout, + CornerLayout, + PatternLayout, +} from '../types.js'; +import { ALL_DEVICE_PRESETS, getDevicePreset, getPresetsByCategory } from '../presets/index.js'; +import { createSolidBuffer } from '../backgrounds/solid.js'; +import { createGradientBuffer } from '../backgrounds/gradient.js'; +import { calculateCenterPosition } from '../layouts/center.js'; +import { calculateCornerPosition } from '../layouts/corner.js'; +import { calculatePatternTiles } from '../layouts/pattern.js'; + +// ============================================================================= +// TYPES +// ============================================================================= + +interface SharpInstance { + metadata(): Promise<{ width?: number; height?: number }>; + resize(width: number, height: number, options?: object): SharpInstance; + composite(inputs: CompositeInput[]): SharpInstance; + png(options?: { compressionLevel?: number }): SharpInstance; + jpeg(options?: { quality?: number }): SharpInstance; + toBuffer(): Promise; +} + +interface CompositeInput { + input: + | Buffer + | { + create: { + width: number; + height: number; + channels: number; + background: { r: number; g: number; b: number; alpha: number }; + }; + }; + top?: number; + left?: number; + blend?: string; +} + +type SharpConstructor = { + ( + input?: + | Buffer + | string + | { + create: { + width: number; + height: number; + channels: 3 | 4; + background: { r: number; g: number; b: number; alpha?: number }; + }; + } + ): SharpInstance; + ( + buffer: Buffer, + options: { raw: { width: number; height: number; channels: 3 | 4 } } + ): SharpInstance; +}; + +// ============================================================================= +// SHARP LOADING +// ============================================================================= + +let sharpModule: SharpConstructor | null = null; + +async function getSharp(): Promise { + if (sharpModule) return sharpModule; + + try { + const module = await import('sharp'); + sharpModule = module.default as SharpConstructor; + return sharpModule; + } catch { + throw new Error( + 'Sharp is required for Node.js wallpaper generation. Install it with: npm install sharp' + ); + } +} + +// ============================================================================= +// IMAGE SOURCE LOADING +// ============================================================================= + +/** + * Convert data URL to Buffer + */ +function dataUrlToBuffer(dataUrl: string): Buffer { + const base64 = dataUrl.split(',')[1]; + if (!base64) throw new Error('Invalid data URL'); + return Buffer.from(base64, 'base64'); +} + +/** + * Load source image and get dimensions + */ +async function loadSourceBuffer( + source: ImageSource +): Promise<{ buffer: Buffer; width: number; height: number; isRaw?: boolean; channels?: 3 | 4 }> { + const sharp = await getSharp(); + + switch (source.type) { + case 'dataUrl': { + const buffer = dataUrlToBuffer(source.data); + const img = sharp(buffer); + const metadata = await img.metadata(); + return { + buffer, + width: metadata.width ?? 0, + height: metadata.height ?? 0, + isRaw: false, + }; + } + case 'canvas': { + throw new Error('Canvas source is not supported in Node.js environment'); + } + case 'buffer': { + return { + buffer: Buffer.from(source.buffer), + width: source.width, + height: source.height, + isRaw: true, + channels: source.channels ?? 3, + }; + } + default: + throw new Error('Unknown image source type'); + } +} + +// ============================================================================= +// BACKGROUND CREATION +// ============================================================================= + +/** + * Create background buffer + */ +function createBackgroundBuffer(width: number, height: number, background: Background): Buffer { + let buffer: Uint8Array; + + if (background.type === 'solid') { + buffer = createSolidBuffer(width, height, background.color); + } else if (background.type === 'gradient') { + buffer = createGradientBuffer(width, height, background.colors, background.angle ?? 180); + } else { + buffer = createSolidBuffer(width, height, '#000000'); + } + + return Buffer.from(buffer); +} + +// ============================================================================= +// DIMENSION RESOLUTION +// ============================================================================= + +/** + * Resolve device option to dimensions + */ +function resolveDimensions(device: string | { width: number; height: number }): { + width: number; + height: number; +} { + if (typeof device === 'string') { + const preset = getDevicePreset(device); + if (!preset) { + throw new Error(`Unknown device preset: ${device}`); + } + return { width: preset.width, height: preset.height }; + } + return device; +} + +// ============================================================================= +// NODE.JS GENERATOR +// ============================================================================= + +/** + * Create Node.js-based wallpaper generator using Sharp + */ +export function createNodeGenerator(): WallpaperGenerator { + return { + async generate(source: ImageSource, options: WallpaperOptions): Promise { + const sharp = await getSharp(); + const { width, height } = resolveDimensions(options.device); + const format = options.format ?? 'png'; + const quality = options.quality ?? 90; + + // Create background + const bgBuffer = createBackgroundBuffer(width, height, options.background); + let canvas = sharp(bgBuffer, { raw: { width, height, channels: 3 } }); + + // Load source image + const sourceData = await loadSourceBuffer(source); + + // Calculate composite operations based on layout + const composites: CompositeInput[] = []; + const { layout } = options; + + // Create sharp instance for source image + const createSourceSharp = () => { + if (sourceData.isRaw) { + return sharp(sourceData.buffer, { + raw: { + width: sourceData.width, + height: sourceData.height, + channels: sourceData.channels ?? 3, + }, + }); + } + return sharp(sourceData.buffer); + }; + + if (layout.type === 'center') { + const pos = calculateCenterPosition( + width, + height, + sourceData.width, + sourceData.height, + layout as CenterLayout + ); + + // Resize source image + const resizedSource = await createSourceSharp() + .resize(pos.width, pos.height, { fit: 'fill' }) + .png() + .toBuffer(); + + composites.push({ + input: resizedSource, + top: Math.max(0, pos.y), + left: Math.max(0, pos.x), + }); + } else if (layout.type === 'corner') { + const pos = calculateCornerPosition( + width, + height, + sourceData.width, + sourceData.height, + layout as CornerLayout + ); + + const resizedSource = await createSourceSharp() + .resize(pos.width, pos.height, { fit: 'fill' }) + .png() + .toBuffer(); + + composites.push({ + input: resizedSource, + top: Math.max(0, pos.y), + left: Math.max(0, pos.x), + }); + } else if (layout.type === 'pattern') { + const tiles = calculatePatternTiles( + width, + height, + sourceData.width, + sourceData.height, + layout as PatternLayout + ); + + // Resize source once for all tiles + const tileSize = tiles[0]; + if (tileSize) { + const resizedTile = await createSourceSharp() + .resize(tileSize.width, tileSize.height, { fit: 'fill' }) + .png() + .toBuffer(); + + // Add each tile (limited opacity in Sharp requires different approach) + for (const tile of tiles) { + if (tile.x >= 0 && tile.y >= 0 && tile.x < width && tile.y < height) { + composites.push({ + input: resizedTile, + top: tile.y, + left: tile.x, + blend: 'over', + }); + } + } + } + } + + // Apply composites + if (composites.length > 0) { + canvas = canvas.composite(composites); + } + + // Export + let outputBuffer: Buffer; + if (format === 'jpeg') { + outputBuffer = await canvas.jpeg({ quality }).toBuffer(); + } else { + outputBuffer = await canvas.png({ compressionLevel: 9 }).toBuffer(); + } + + // Convert to data URL + const mimeType = format === 'jpeg' ? 'image/jpeg' : 'image/png'; + const dataUrl = `data:${mimeType};base64,${outputBuffer.toString('base64')}`; + + return { + dataUrl, + width, + height, + format, + size: outputBuffer.length, + }; + }, + + async preview(source: ImageSource, options: WallpaperOptions): Promise { + // Generate at 1/4 resolution for preview + const { width, height } = resolveDimensions(options.device); + const previewWidth = Math.round(width / 4); + const previewHeight = Math.round(height / 4); + + const previewOptions: WallpaperOptions = { + ...options, + device: { width: previewWidth, height: previewHeight }, + format: 'jpeg', + quality: 70, + }; + + // Scale layout parameters + previewOptions.layout = scaleLayout(options.layout, 0.25); + + const result = await this.generate(source, previewOptions); + return result.dataUrl; + }, + + getSupportedDevices(): DevicePreset[] { + return ALL_DEVICE_PRESETS; + }, + + getDevicesByCategory(category: DeviceCategory): DevicePreset[] { + return getPresetsByCategory(category); + }, + }; +} + +/** + * Scale layout parameters for preview + */ +function scaleLayout(layout: Layout, scaleFactor: number): Layout { + switch (layout.type) { + case 'center': + return { + ...layout, + offset: layout.offset + ? [layout.offset[0] * scaleFactor, layout.offset[1] * scaleFactor] + : undefined, + } as CenterLayout; + case 'corner': + return { + ...layout, + padding: ((layout as CornerLayout).padding ?? 40) * scaleFactor, + } as CornerLayout; + case 'pattern': + return { + ...layout, + gap: ((layout as PatternLayout).gap ?? 20) * scaleFactor, + } as PatternLayout; + default: + return layout; + } +} + +// ============================================================================= +// NODE.JS UTILITIES +// ============================================================================= + +/** + * Save wallpaper to file + */ +export async function saveWallpaperToFile( + result: WallpaperResult, + filePath: string +): Promise { + const fs = await import('fs/promises'); + const base64 = result.dataUrl.split(',')[1]; + if (!base64) throw new Error('Invalid data URL'); + const buffer = Buffer.from(base64, 'base64'); + await fs.writeFile(filePath, buffer); +} diff --git a/packages/wallpaper-generator/src/svelte/WallpaperModal.svelte b/packages/wallpaper-generator/src/svelte/WallpaperModal.svelte new file mode 100644 index 000000000..73c8cbe3e --- /dev/null +++ b/packages/wallpaper-generator/src/svelte/WallpaperModal.svelte @@ -0,0 +1,453 @@ + + +{#if show} + +
+ +
+{/if} diff --git a/packages/wallpaper-generator/src/svelte/index.ts b/packages/wallpaper-generator/src/svelte/index.ts new file mode 100644 index 000000000..31912b625 --- /dev/null +++ b/packages/wallpaper-generator/src/svelte/index.ts @@ -0,0 +1,5 @@ +/** + * Svelte Exports + */ + +export { default as WallpaperModal } from './WallpaperModal.svelte'; diff --git a/packages/wallpaper-generator/src/types.ts b/packages/wallpaper-generator/src/types.ts new file mode 100644 index 000000000..12eb00d2a --- /dev/null +++ b/packages/wallpaper-generator/src/types.ts @@ -0,0 +1,282 @@ +/** + * Wallpaper Generator Types + * + * Type definitions for the wallpaper generator package. + */ + +// ============================================================================= +// IMAGE SOURCE TYPES +// ============================================================================= + +/** Data URL image source (e.g., from QR code or canvas export) */ +export interface DataUrlSource { + type: 'dataUrl'; + data: string; +} + +/** HTML Canvas element source */ +export interface CanvasSource { + type: 'canvas'; + canvas: HTMLCanvasElement; +} + +/** Raw pixel buffer source */ +export interface BufferSource { + type: 'buffer'; + buffer: Uint8Array; + width: number; + height: number; + /** Number of channels (3 for RGB, 4 for RGBA) */ + channels?: 3 | 4; +} + +/** All supported image source types */ +export type ImageSource = DataUrlSource | CanvasSource | BufferSource; + +// ============================================================================= +// DEVICE PRESETS +// ============================================================================= + +/** Device category */ +export type DeviceCategory = 'phone' | 'tablet' | 'desktop'; + +/** Device preset definition */ +export interface DevicePreset { + /** Unique identifier (e.g., 'iphone-15-pro-max') */ + id: string; + /** Display name (e.g., 'iPhone 15 Pro Max') */ + name: string; + /** Device category */ + category: DeviceCategory; + /** Width in pixels */ + width: number; + /** Height in pixels */ + height: number; + /** Pixel density ratio (optional) */ + pixelRatio?: number; +} + +/** Custom device dimensions */ +export interface CustomDevice { + width: number; + height: number; +} + +/** Device option - either a preset ID or custom dimensions */ +export type DeviceOption = string | CustomDevice; + +// ============================================================================= +// LAYOUT OPTIONS +// ============================================================================= + +/** Corner position for corner layout */ +export type CornerPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; + +/** Center layout - image centered on wallpaper */ +export interface CenterLayout { + type: 'center'; + /** Scale factor (default: 1.0) */ + scale?: number; + /** Offset from center in pixels [x, y] */ + offset?: [number, number]; +} + +/** Corner layout - image in one corner */ +export interface CornerLayout { + type: 'corner'; + /** Which corner to place the image */ + position: CornerPosition; + /** Scale factor (default: 1.0) */ + scale?: number; + /** Padding from edges in pixels (default: 40) */ + padding?: number; +} + +/** Pattern layout - tiled image */ +export interface PatternLayout { + type: 'pattern'; + /** Scale factor for each tile (default: 0.5) */ + scale?: number; + /** Gap between tiles in pixels (default: 20) */ + gap?: number; + /** Opacity of pattern (default: 0.15) */ + opacity?: number; +} + +/** All layout types */ +export type Layout = CenterLayout | CornerLayout | PatternLayout; + +// ============================================================================= +// BACKGROUND OPTIONS +// ============================================================================= + +/** Solid color background */ +export interface SolidBackground { + type: 'solid'; + /** Color in hex format (e.g., '#1a1a2e') */ + color: string; +} + +/** Gradient background */ +export interface GradientBackground { + type: 'gradient'; + /** Array of color stops in hex format */ + colors: string[]; + /** Gradient angle in degrees (default: 180 = top to bottom) */ + angle?: number; +} + +/** All background types */ +export type Background = SolidBackground | GradientBackground; + +// ============================================================================= +// WALLPAPER OPTIONS +// ============================================================================= + +/** Output image format */ +export type OutputFormat = 'png' | 'jpeg'; + +/** Complete wallpaper generation options */ +export interface WallpaperOptions { + /** Target device (preset ID or custom dimensions) */ + device: DeviceOption; + /** Image layout configuration */ + layout: Layout; + /** Background configuration */ + background: Background; + /** Output format (default: 'png') */ + format?: OutputFormat; + /** JPEG quality 0-100 (only for jpeg format, default: 90) */ + quality?: number; +} + +// ============================================================================= +// RESULT TYPES +// ============================================================================= + +/** Wallpaper generation result */ +export interface WallpaperResult { + /** Data URL of the generated wallpaper */ + dataUrl: string; + /** Width of the generated image */ + width: number; + /** Height of the generated image */ + height: number; + /** Output format */ + format: OutputFormat; + /** Size in bytes (approximate) */ + size: number; +} + +// ============================================================================= +// GENERATOR INTERFACE +// ============================================================================= + +/** Wallpaper generator interface */ +export interface WallpaperGenerator { + /** + * Generate a wallpaper from an image source + * @param source - The source image (data URL, canvas, or buffer) + * @param options - Wallpaper generation options + * @returns Promise with the generated wallpaper result + */ + generate(source: ImageSource, options: WallpaperOptions): Promise; + + /** + * Generate a preview (smaller, faster) of the wallpaper + * @param source - The source image + * @param options - Wallpaper generation options + * @returns Promise with data URL of preview image + */ + preview(source: ImageSource, options: WallpaperOptions): Promise; + + /** + * Get list of all supported device presets + * @returns Array of device presets + */ + getSupportedDevices(): DevicePreset[]; + + /** + * Get device presets by category + * @param category - Device category to filter by + * @returns Array of device presets in that category + */ + getDevicesByCategory(category: DeviceCategory): DevicePreset[]; +} + +// ============================================================================= +// SVELTE COMPONENT PROPS +// ============================================================================= + +/** Props for WallpaperModal Svelte component */ +export interface WallpaperModalProps { + /** Whether the modal is visible */ + show: boolean; + /** Source image as data URL */ + imageDataUrl: string; + /** Optional source image dimensions (for better scaling) */ + imageSize?: { width: number; height: number }; + /** Callback when modal is closed */ + onClose: () => void; + /** Optional callback when wallpaper is generated */ + onGenerate?: (result: WallpaperResult) => void; +} + +// ============================================================================= +// PRESET CONSTANTS +// ============================================================================= + +/** Default layout options */ +export const DEFAULT_CENTER_LAYOUT: CenterLayout = { + type: 'center', + scale: 1.0, +}; + +export const DEFAULT_CORNER_LAYOUT: CornerLayout = { + type: 'corner', + position: 'bottom-right', + scale: 0.3, + padding: 40, +}; + +export const DEFAULT_PATTERN_LAYOUT: PatternLayout = { + type: 'pattern', + scale: 0.5, + gap: 20, + opacity: 0.15, +}; + +/** Default background */ +export const DEFAULT_BACKGROUND: SolidBackground = { + type: 'solid', + color: '#1a1a2e', +}; + +/** Default gradient backgrounds */ +export const GRADIENT_PRESETS: Record = { + dark: { + type: 'gradient', + colors: ['#1a1a2e', '#16213e', '#0f3460'], + angle: 180, + }, + sunset: { + type: 'gradient', + colors: ['#ff6b6b', '#feca57', '#48dbfb'], + angle: 135, + }, + ocean: { + type: 'gradient', + colors: ['#0f0c29', '#302b63', '#24243e'], + angle: 180, + }, + forest: { + type: 'gradient', + colors: ['#134e5e', '#71b280'], + angle: 180, + }, + purple: { + type: 'gradient', + colors: ['#667eea', '#764ba2'], + angle: 135, + }, +}; diff --git a/packages/wallpaper-generator/tsconfig.json b/packages/wallpaper-generator/tsconfig.json new file mode 100644 index 000000000..a5a6dd598 --- /dev/null +++ b/packages/wallpaper-generator/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "noEmit": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed2837f9f..b221877c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,7 +148,7 @@ importers: devDependencies: '@nestjs/cli': specifier: ^10.4.9 - version: 10.4.9 + version: 10.4.9(esbuild@0.19.12) '@nestjs/schematics': specifier: ^10.2.3 version: 10.2.3(chokidar@3.6.0)(typescript@5.9.3) @@ -196,10 +196,10 @@ importers: version: 0.5.21 ts-jest: specifier: ^29.2.5 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.19.12)(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) ts-loader: specifier: ^9.5.1 - version: 9.5.4(typescript@5.9.3)(webpack@5.100.2) + version: 9.5.4(typescript@5.9.3)(webpack@5.97.1(esbuild@0.19.12)) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.19.1)(typescript@5.9.3) @@ -223,14 +223,14 @@ importers: version: link:../../../../packages/shared-landing-ui astro: specifier: ^5.16.0 - version: 5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.1) + version: 5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.1) typescript: specifier: ^5.9.2 version: 5.9.3 devDependencies: '@astrojs/tailwind': specifier: ^6.0.2 - version: 6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.1))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + version: 6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.1))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) '@tailwindcss/typography': specifier: ^0.5.18 version: 0.5.19(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.1)) @@ -239,13 +239,13 @@ importers: version: 20.19.25 eslint: specifier: ^9.0.0 - version: 9.39.1(jiti@2.6.1) + version: 9.39.1(jiti@1.21.7) eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.2(eslint@9.39.1(jiti@2.6.1)) + version: 9.1.2(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-astro: specifier: ^1.0.0 - version: 1.5.0(eslint@9.39.1(jiti@2.6.1)) + version: 1.5.0(eslint@9.39.1(jiti@1.21.7)) prettier: specifier: ^3.6.2 version: 3.6.2 @@ -635,19 +635,19 @@ importers: version: 18.3.27 '@typescript-eslint/eslint-plugin': specifier: ^7.7.0 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) '@typescript-eslint/parser': specifier: ^7.7.0 - version: 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + version: 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) dotenv: specifier: ^16.4.7 version: 16.6.1 eslint: specifier: ^9.39.1 - version: 9.39.1(jiti@1.21.7) + version: 9.39.1(jiti@2.6.1) eslint-config-universe: specifier: ^12.0.1 - version: 12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2)(typescript@5.3.3) + version: 12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2)(typescript@5.3.3) prettier: specifier: ^3.2.5 version: 3.6.2 @@ -1798,7 +1798,7 @@ importers: version: 8.0.9(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) expo-router: specifier: ~6.0.15 - version: 6.0.15(g2vconqrtzzmzlh6ymhbjirn5e) + version: 6.0.15(vmxlpuhz6xqbe2ee7fdabyqx3y) expo-status-bar: specifier: ~3.0.8 version: 3.0.8(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) @@ -1929,6 +1929,9 @@ importers: '@manacore/shared-utils': specifier: workspace:* version: link:../../../../packages/shared-utils + '@manacore/wallpaper-generator': + specifier: workspace:* + version: link:../../../../packages/wallpaper-generator svelte-dnd-action: specifier: ^0.9.68 version: 0.9.68(svelte@5.44.0) @@ -2228,7 +2231,7 @@ importers: version: 8.0.9(expo@54.0.13)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) expo-router: specifier: ~6.0.10 - version: 6.0.15(ewsdnidpxwg6dzyorlbigkbme4) + version: 6.0.15(psh6y5usp77eac7hbbid4ov2mi) expo-secure-store: specifier: ^15.0.7 version: 15.0.7(expo@54.0.13) @@ -2636,7 +2639,7 @@ importers: version: 0.5.21 ts-jest: specifier: ^29.2.5 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.19.12)(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) ts-loader: specifier: ^9.5.1 version: 9.5.4(typescript@5.9.3)(webpack@5.100.2) @@ -3291,7 +3294,7 @@ importers: version: 18.2.0(expo@54.0.12)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)) expo-router: specifier: ~6.0.10 - version: 6.0.15(f6my4lgi43u5yo7kczxd3pw7ru) + version: 6.0.15(supujcsjl47mo53hnmela4rs24) expo-secure-store: specifier: ~15.0.7 version: 15.0.7(expo@54.0.12) @@ -4383,10 +4386,10 @@ importers: version: 0.30.6 jest: specifier: ^30.2.0 - version: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + version: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0)) ts-jest: specifier: ^29.2.5 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.0)(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.0)(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0)))(typescript@5.9.3) tsx: specifier: ^4.19.4 version: 4.20.6 @@ -4785,10 +4788,10 @@ importers: version: 0.30.6 jest: specifier: ^30.2.0 - version: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + version: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0)) ts-jest: specifier: ^29.2.5 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.0)(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.0)(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0)))(typescript@5.9.3) tsx: specifier: ^4.19.4 version: 4.20.6 @@ -5378,6 +5381,9 @@ importers: packages/qr-export: dependencies: + '@manacore/wallpaper-generator': + specifier: workspace:* + version: link:../wallpaper-generator jsqr: specifier: ^1.4.0 version: 1.4.0 @@ -6041,6 +6047,9 @@ importers: packages/spiral-db: dependencies: + '@manacore/wallpaper-generator': + specifier: workspace:* + version: link:../wallpaper-generator pako: specifier: ^2.1.0 version: 2.1.0 @@ -6074,7 +6083,7 @@ importers: version: 1.57.0 jest: specifier: ^29.0.0 - version: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) + version: 29.7.0(@types/node@24.10.1) vitest: specifier: ^3.0.0 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1) @@ -6086,6 +6095,25 @@ importers: specifier: ^5.9.3 version: 5.9.3 + packages/wallpaper-generator: + dependencies: + sharp: + specifier: ^0.33.0 + version: 0.33.5 + svelte: + specifier: ^5.0.0 + version: 5.44.0 + devDependencies: + '@types/node': + specifier: ^24.10.1 + version: 24.10.1 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^3.0.5 + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1) + services/llm-playground: dependencies: '@manacore/shared-auth': @@ -6221,7 +6249,7 @@ importers: version: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) ts-jest: specifier: ^29.2.5 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.19.12)(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.19.1)(typescript@5.9.3) @@ -6405,7 +6433,7 @@ importers: version: 7.1.4 ts-jest: specifier: ^29.2.5 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.19.12)(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) ts-loader: specifier: ^9.5.1 version: 9.5.4(typescript@5.9.3)(webpack@5.100.2) @@ -6517,7 +6545,7 @@ importers: version: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) ts-jest: specifier: ^29.2.5 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.19.12)(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.19.1)(typescript@5.9.3) @@ -6707,7 +6735,7 @@ importers: version: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) ts-jest: specifier: ^29.2.5 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.19.12)(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.19.1)(typescript@5.9.3) @@ -6780,7 +6808,7 @@ importers: version: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) ts-jest: specifier: ^29.2.5 - version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.19.12)(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.19.1)(typescript@5.9.3) @@ -10056,7 +10084,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {node: '>=0.10.0'} + engines: {'0': node >=0.10.0} '@expo/cli@0.22.26': resolution: {integrity: sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==} @@ -25035,6 +25063,16 @@ snapshots: transitivePeerDependencies: - ts-node + '@astrojs/tailwind@6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.1))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))': + dependencies: + astro: 5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.1) + autoprefixer: 10.4.22(postcss@8.5.6) + postcss: 8.5.6 + postcss-load-config: 4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + tailwindcss: 3.4.18(tsx@4.21.0)(yaml@2.8.1) + transitivePeerDependencies: + - ts-node + '@astrojs/tailwind@6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.1))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))': dependencies: astro: 5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.1) @@ -27796,7 +27834,7 @@ snapshots: wrap-ansi: 7.0.0 ws: 8.18.3 optionalDependencies: - expo-router: 6.0.15(f6my4lgi43u5yo7kczxd3pw7ru) + expo-router: 6.0.15(supujcsjl47mo53hnmela4rs24) react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) transitivePeerDependencies: - '@modelcontextprotocol/sdk' @@ -27873,7 +27911,7 @@ snapshots: wrap-ansi: 7.0.0 ws: 8.18.3 optionalDependencies: - expo-router: 6.0.15(ewsdnidpxwg6dzyorlbigkbme4) + expo-router: 6.0.15(psh6y5usp77eac7hbbid4ov2mi) react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) transitivePeerDependencies: - '@modelcontextprotocol/sdk' @@ -27950,7 +27988,7 @@ snapshots: wrap-ansi: 7.0.0 ws: 8.18.3 optionalDependencies: - expo-router: 6.0.15(lvmr432nn4pnebfhbi2qjoy364) + expo-router: 6.0.15(xyagqkzos5etzn52s4may7634u) react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) transitivePeerDependencies: - '@modelcontextprotocol/sdk' @@ -28027,7 +28065,7 @@ snapshots: wrap-ansi: 7.0.0 ws: 8.18.3 optionalDependencies: - expo-router: 6.0.15(6hayu32hencph7rqfkncbd2qum) + expo-router: 6.0.15(k2muy65dii4k2uiuhg4mwyy6ki) react-native: 0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1) transitivePeerDependencies: - '@modelcontextprotocol/sdk' @@ -28104,7 +28142,7 @@ snapshots: wrap-ansi: 7.0.0 ws: 8.18.3 optionalDependencies: - expo-router: 6.0.15(g2vconqrtzzmzlh6ymhbjirn5e) + expo-router: 6.0.15(7mqaurqidri6vkknnsci36yp4e) react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) transitivePeerDependencies: - '@modelcontextprotocol/sdk' @@ -29412,6 +29450,41 @@ snapshots: jest-util: 30.2.0 slash: 3.0.0 + '@jest/core@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.19.1 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@22.19.1) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))': dependencies: '@jest/console': 29.7.0 @@ -29447,42 +29520,7 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.19.1 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - - '@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))': + '@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))': dependencies: '@jest/console': 30.2.0 '@jest/pattern': 30.0.1 @@ -29497,7 +29535,7 @@ snapshots: exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.2.0 - jest-config: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0)) jest-haste-map: 30.2.0 jest-message-util: 30.2.0 jest-regex-util: 30.0.1 @@ -29517,7 +29555,6 @@ snapshots: - esbuild-register - supports-color - ts-node - optional: true '@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))': dependencies: @@ -29555,80 +29592,6 @@ snapshots: - supports-color - ts-node - '@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3))': - dependencies: - '@jest/console': 30.2.0 - '@jest/pattern': 30.0.1 - '@jest/reporters': 30.2.0 - '@jest/test-result': 30.2.0 - '@jest/transform': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 22.19.1 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 4.3.1 - exit-x: 0.2.2 - graceful-fs: 4.2.11 - jest-changed-files: 30.2.0 - jest-config: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)) - jest-haste-map: 30.2.0 - jest-message-util: 30.2.0 - jest-regex-util: 30.0.1 - jest-resolve: 30.2.0 - jest-resolve-dependencies: 30.2.0 - jest-runner: 30.2.0 - jest-runtime: 30.2.0 - jest-snapshot: 30.2.0 - jest-util: 30.2.0 - jest-validate: 30.2.0 - jest-watcher: 30.2.0 - micromatch: 4.0.8 - pretty-format: 30.2.0 - slash: 3.0.0 - transitivePeerDependencies: - - babel-plugin-macros - - esbuild-register - - supports-color - - ts-node - optional: true - - '@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))': - dependencies: - '@jest/console': 30.2.0 - '@jest/pattern': 30.0.1 - '@jest/reporters': 30.2.0 - '@jest/test-result': 30.2.0 - '@jest/transform': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 22.19.1 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 4.3.1 - exit-x: 0.2.2 - graceful-fs: 4.2.11 - jest-changed-files: 30.2.0 - jest-config: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) - jest-haste-map: 30.2.0 - jest-message-util: 30.2.0 - jest-regex-util: 30.0.1 - jest-resolve: 30.2.0 - jest-resolve-dependencies: 30.2.0 - jest-runner: 30.2.0 - jest-runtime: 30.2.0 - jest-snapshot: 30.2.0 - jest-util: 30.2.0 - jest-validate: 30.2.0 - jest-watcher: 30.2.0 - micromatch: 4.0.8 - pretty-format: 30.2.0 - slash: 3.0.0 - transitivePeerDependencies: - - babel-plugin-macros - - esbuild-register - - supports-color - - ts-node - optional: true - '@jest/create-cache-key-function@29.7.0': dependencies: '@jest/types': 29.6.3 @@ -30096,6 +30059,32 @@ snapshots: - uglify-js - webpack-cli + '@nestjs/cli@10.4.9(esbuild@0.19.12)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics-cli': 17.3.11(chokidar@3.6.0) + '@nestjs/schematics': 10.2.3(chokidar@3.6.0)(typescript@5.7.2) + chalk: 4.1.2 + chokidar: 3.6.0 + cli-table3: 0.6.5 + commander: 4.1.1 + fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.19.12)) + glob: 10.4.5 + inquirer: 8.2.6 + node-emoji: 1.11.0 + ora: 5.4.1 + tree-kill: 1.2.2 + tsconfig-paths: 4.2.0 + tsconfig-paths-webpack-plugin: 4.2.0 + typescript: 5.7.2 + webpack: 5.97.1(esbuild@0.19.12) + webpack-node-externals: 3.0.0 + transitivePeerDependencies: + - esbuild + - uglify-js + - webpack-cli + '@nestjs/cli@10.4.9(esbuild@0.27.0)': dependencies: '@angular-devkit/core': 17.3.11(chokidar@3.6.0) @@ -33828,59 +33817,7 @@ snapshots: picocolors: 1.1.1 pretty-format: 27.5.1 - '@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - jest-matcher-utils: 30.2.0 - picocolors: 1.1.1 - pretty-format: 30.2.0 - react: 19.1.0 - react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) - react-test-renderer: 19.1.0(react@19.1.0) - redent: 3.0.0 - optionalDependencies: - jest: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) - optional: true - - '@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - jest-matcher-utils: 30.2.0 - picocolors: 1.1.1 - pretty-format: 30.2.0 - react: 19.1.0 - react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) - react-test-renderer: 19.1.0(react@19.1.0) - redent: 3.0.0 - optionalDependencies: - jest: 30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)) - optional: true - - '@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react-test-renderer@19.1.0(react@18.3.1))(react@18.3.1)': - dependencies: - jest-matcher-utils: 30.2.0 - picocolors: 1.1.1 - pretty-format: 30.2.0 - react: 18.3.1 - react-native: 0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1) - react-test-renderer: 19.1.0(react@18.3.1) - redent: 3.0.0 - optionalDependencies: - jest: 30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)) - optional: true - - '@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - jest-matcher-utils: 30.2.0 - picocolors: 1.1.1 - pretty-format: 30.2.0 - react: 19.1.0 - react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) - react-test-renderer: 19.1.0(react@19.1.0) - redent: 3.0.0 - optionalDependencies: - jest: 30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) - optional: true - - '@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': + '@testing-library/react-native@13.3.3(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: jest-matcher-utils: 30.2.0 picocolors: 1.1.1 @@ -33890,7 +33827,59 @@ snapshots: react-test-renderer: 19.1.0(react@19.1.0) redent: 3.0.0 optionalDependencies: - jest: 30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) + jest: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + optional: true + + '@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + jest-matcher-utils: 30.2.0 + picocolors: 1.1.1 + pretty-format: 30.2.0 + react: 19.1.0 + react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) + react-test-renderer: 19.1.0(react@19.1.0) + redent: 3.0.0 + optionalDependencies: + jest: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)) + optional: true + + '@testing-library/react-native@13.3.3(jest@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + jest-matcher-utils: 30.2.0 + picocolors: 1.1.1 + pretty-format: 30.2.0 + react: 19.1.0 + react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) + react-test-renderer: 19.1.0(react@19.1.0) + redent: 3.0.0 + optionalDependencies: + jest: 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) + optional: true + + '@testing-library/react-native@13.3.3(jest@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react-test-renderer@19.1.0(react@18.3.1))(react@18.3.1)': + dependencies: + jest-matcher-utils: 30.2.0 + picocolors: 1.1.1 + pretty-format: 30.2.0 + react: 18.3.1 + react-native: 0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1) + react-test-renderer: 19.1.0(react@18.3.1) + redent: 3.0.0 + optionalDependencies: + jest: 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) + optional: true + + '@testing-library/react-native@13.3.3(jest@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + jest-matcher-utils: 30.2.0 + picocolors: 1.1.1 + pretty-format: 30.2.0 + react: 19.1.0 + react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) + react-test-renderer: 19.1.0(react@19.1.0) + redent: 3.0.0 + optionalDependencies: + jest: 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) optional: true '@testing-library/svelte-core@1.0.0(svelte@5.44.0)': @@ -34457,16 +34446,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) - '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + '@typescript-eslint/type-utils': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.4.3 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.1(jiti@2.6.1) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -34515,15 +34504,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + '@typescript-eslint/parser': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) - '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + '@typescript-eslint/type-utils': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) '@typescript-eslint/visitor-keys': 7.18.0 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.1(jiti@2.6.1) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -34615,14 +34604,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': + '@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': dependencies: '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.4.3 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.1(jiti@2.6.1) optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: @@ -34654,14 +34643,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': + '@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': dependencies: '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.3.3) '@typescript-eslint/visitor-keys': 7.18.0 debug: 4.4.3 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.1(jiti@2.6.1) optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: @@ -34787,12 +34776,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': + '@typescript-eslint/type-utils@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) - '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) debug: 4.4.3 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.1(jiti@2.6.1) ts-api-utils: 1.4.3(typescript@5.3.3) optionalDependencies: typescript: 5.3.3 @@ -34823,12 +34812,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': + '@typescript-eslint/type-utils@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': dependencies: '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.3.3) - '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) debug: 4.4.3 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.1(jiti@2.6.1) ts-api-utils: 1.4.3(typescript@5.3.3) optionalDependencies: typescript: 5.3.3 @@ -35010,15 +34999,15 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': + '@typescript-eslint/utils@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) '@types/json-schema': 7.0.15 '@types/semver': 7.7.1 '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.1(jiti@2.6.1) semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -35049,13 +35038,13 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': + '@typescript-eslint/utils@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.3.3) - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.1(jiti@2.6.1) transitivePeerDependencies: - supports-color - typescript @@ -35272,11 +35261,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/browser@3.2.4(playwright@1.57.0)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1))(vitest@3.2.4)': + '@vitest/browser@3.2.4(playwright@1.57.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1))(vitest@3.2.4)': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1)) '@vitest/utils': 3.2.4 magic-string: 0.30.21 sirv: 3.0.2 @@ -35372,6 +35361,15 @@ snapshots: optionalDependencies: vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1) + '@vitest/mocker@3.2.4(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1) + optional: true + '@vitest/mocker@4.0.14(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@vitest/spy': 4.0.14 @@ -36043,6 +36041,108 @@ snapshots: transitivePeerDependencies: - supports-color + astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.1): + dependencies: + '@astrojs/compiler': 2.13.0 + '@astrojs/internal-helpers': 0.7.5 + '@astrojs/markdown-remark': 6.3.9 + '@astrojs/telemetry': 3.3.0 + '@capsizecss/unpack': 3.0.1 + '@oslojs/encoding': 1.1.0 + '@rollup/pluginutils': 5.3.0(rollup@4.53.3) + acorn: 8.15.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + boxen: 8.0.1 + ci-info: 4.3.1 + clsx: 2.1.1 + common-ancestor-path: 1.0.1 + cookie: 1.1.0 + cssesc: 3.0.0 + debug: 4.4.3 + deterministic-object-hash: 2.0.2 + devalue: 5.5.0 + diff: 5.2.0 + dlv: 1.1.3 + dset: 3.1.4 + es-module-lexer: 1.7.0 + esbuild: 0.25.12 + estree-walker: 3.0.3 + flattie: 1.1.1 + fontace: 0.3.1 + github-slugger: 2.0.0 + html-escaper: 3.0.3 + http-cache-semantics: 4.2.0 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + magic-string: 0.30.21 + magicast: 0.5.1 + mrmime: 2.0.1 + neotraverse: 0.6.18 + p-limit: 6.2.0 + p-queue: 8.1.1 + package-manager-detector: 1.5.0 + piccolore: 0.1.3 + picomatch: 4.0.3 + prompts: 2.4.2 + rehype: 13.0.2 + semver: 7.7.3 + shiki: 3.15.0 + smol-toml: 1.5.2 + svgo: 4.0.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tsconfck: 3.1.6(typescript@5.9.3) + ultrahtml: 1.6.0 + unifont: 0.6.0 + unist-util-visit: 5.0.0 + unstorage: 1.17.3(@netlify/blobs@10.4.1)(ioredis@5.9.2) + vfile: 6.0.3 + vite: 6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1) + vitefu: 1.1.1(vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1)) + xxhash-wasm: 1.1.0 + yargs-parser: 21.1.1 + yocto-spinner: 0.2.3 + zod: 3.25.76 + zod-to-json-schema: 3.25.0(zod@3.25.76) + zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76) + optionalDependencies: + sharp: 0.34.5 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - db0 + - idb-keyval + - ioredis + - jiti + - less + - lightningcss + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - uploadthing + - yaml + astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.1): dependencies: '@astrojs/compiler': 2.13.0 @@ -37359,13 +37459,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)): + create-jest@29.7.0(@types/node@24.10.1): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@24.10.1) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -38479,6 +38579,11 @@ snapshots: escape-string-regexp@5.0.0: {} + eslint-compat-utils@0.6.5(eslint@9.39.1(jiti@1.21.7)): + dependencies: + eslint: 9.39.1(jiti@1.21.7) + semver: 7.7.3 + eslint-compat-utils@0.6.5(eslint@9.39.1(jiti@2.6.1)): dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -38489,9 +38594,9 @@ snapshots: '@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-expo: 1.0.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@2.6.1)) globals: 16.5.0 @@ -38506,9 +38611,9 @@ snapshots: '@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3) '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3) eslint: 9.39.1(jiti@2.6.1) - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-expo: 0.1.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@2.6.1)) globals: 16.5.0 @@ -38526,14 +38631,14 @@ snapshots: dependencies: eslint: 8.57.1 - eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@1.21.7)): - dependencies: - eslint: 9.39.1(jiti@1.21.7) - eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@2.6.1)): dependencies: eslint: 9.39.1(jiti@2.6.1) + eslint-config-prettier@9.1.2(eslint@9.39.1(jiti@1.21.7)): + dependencies: + eslint: 9.39.1(jiti@1.21.7) + eslint-config-prettier@9.1.2(eslint@9.39.1(jiti@2.6.1)): dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -38558,17 +38663,17 @@ snapshots: - supports-color - typescript - eslint-config-universe@12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2)(typescript@5.3.3): + eslint-config-universe@12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2)(typescript@5.3.3): dependencies: - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) - '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) - eslint: 9.39.1(jiti@1.21.7) - eslint-config-prettier: 8.10.2(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-node: 11.1.0(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-prettier: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2) - eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-react-hooks: 4.6.2(eslint@9.39.1(jiti@1.21.7)) + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + eslint: 9.39.1(jiti@2.6.1) + eslint-config-prettier: 8.10.2(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-node: 11.1.0(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-prettier: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2) + eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-react-hooks: 4.6.2(eslint@9.39.1(jiti@2.6.1)) optionalDependencies: prettier: 3.6.2 transitivePeerDependencies: @@ -38606,7 +38711,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -38617,7 +38722,22 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3 + eslint: 9.39.1(jiti@2.6.1) + get-tsconfig: 4.13.0 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.15 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -38631,12 +38751,12 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@1.21.7)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) - eslint: 9.39.1(jiti@1.21.7) + '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color @@ -38651,25 +38771,39 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3) eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)) + transitivePeerDependencies: + - supports-color + + eslint-plugin-astro@1.5.0(eslint@9.39.1(jiti@1.21.7)): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@jridgewell/sourcemap-codec': 1.5.5 + '@typescript-eslint/types': 8.48.0 + astro-eslint-parser: 1.2.2 + eslint: 9.39.1(jiti@1.21.7) + eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@1.21.7)) + globals: 16.5.0 + postcss: 8.5.6 + postcss-selector-parser: 7.1.0 transitivePeerDependencies: - supports-color @@ -38693,12 +38827,6 @@ snapshots: eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-es@3.0.1(eslint@9.39.1(jiti@1.21.7)): - dependencies: - eslint: 9.39.1(jiti@1.21.7) - eslint-utils: 2.1.0 - regexpp: 3.2.0 - eslint-plugin-es@3.0.1(eslint@9.39.1(jiti@2.6.1)): dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -38752,7 +38880,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -38761,9 +38889,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@1.21.7)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -38775,7 +38903,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -38810,7 +38938,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -38821,7 +38949,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -38839,7 +38967,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -38850,7 +38978,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -38878,16 +39006,6 @@ snapshots: resolve: 1.22.11 semver: 6.3.1 - eslint-plugin-node@11.1.0(eslint@9.39.1(jiti@1.21.7)): - dependencies: - eslint: 9.39.1(jiti@1.21.7) - eslint-plugin-es: 3.0.1(eslint@9.39.1(jiti@1.21.7)) - eslint-utils: 2.1.0 - ignore: 5.3.2 - minimatch: 3.1.2 - resolve: 1.22.11 - semver: 6.3.1 - eslint-plugin-node@11.1.0(eslint@9.39.1(jiti@2.6.1)): dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -38918,16 +39036,6 @@ snapshots: '@types/eslint': 9.6.1 eslint-config-prettier: 8.10.2(eslint@8.57.1) - eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2): - dependencies: - eslint: 9.39.1(jiti@1.21.7) - prettier: 3.6.2 - prettier-linter-helpers: 1.0.0 - synckit: 0.11.11 - optionalDependencies: - '@types/eslint': 9.6.1 - eslint-config-prettier: 8.10.2(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2): dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -38952,10 +39060,6 @@ snapshots: dependencies: eslint: 8.57.1 - eslint-plugin-react-hooks@4.6.2(eslint@9.39.1(jiti@1.21.7)): - dependencies: - eslint: 9.39.1(jiti@1.21.7) - eslint-plugin-react-hooks@4.6.2(eslint@9.39.1(jiti@2.6.1)): dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -38986,28 +39090,6 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@1.21.7)): - dependencies: - array-includes: 3.1.9 - array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.3 - array.prototype.tosorted: 1.1.4 - doctrine: 2.1.0 - es-iterator-helpers: 1.2.1 - eslint: 9.39.1(jiti@1.21.7) - estraverse: 5.3.0 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.2 - object.entries: 1.1.9 - object.fromentries: 2.0.8 - object.values: 1.2.1 - prop-types: 15.8.1 - resolve: 2.0.0-next.5 - semver: 6.3.1 - string.prototype.matchall: 4.0.12 - string.prototype.repeat: 1.0.0 - eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@2.6.1)): dependencies: array-includes: 3.1.9 @@ -40166,146 +40248,7 @@ snapshots: - react-native - supports-color - expo-router@6.0.15(6hayu32hencph7rqfkncbd2qum): - dependencies: - '@expo/metro-runtime': 6.1.2(expo@54.0.25)(react-dom@19.1.0(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) - '@expo/schema-utils': 0.1.7 - '@radix-ui/react-slot': 1.2.0(@types/react@18.3.27)(react@18.3.1) - '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.1.0(react@18.3.1))(react@18.3.1) - '@react-navigation/bottom-tabs': 7.8.6(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) - '@react-navigation/native': 7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) - '@react-navigation/native-stack': 7.8.0(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) - client-only: 0.0.1 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - expo: 54.0.25(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native-webview@13.12.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) - expo-constants: 18.0.10(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1)) - expo-linking: 8.0.9(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) - expo-server: 1.0.4 - fast-deep-equal: 3.1.3 - invariant: 2.2.4 - nanoid: 3.3.11 - query-string: 7.1.3 - react: 18.3.1 - react-fast-compare: 3.2.2 - react-native: 0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1) - react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) - react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) - react-native-screens: 4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) - semver: 7.6.3 - server-only: 0.0.1 - sf-symbols-typescript: 2.1.0 - shallowequal: 1.1.0 - use-latest-callback: 0.2.6(react@18.3.1) - vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.1.0(react@18.3.1))(react@18.3.1) - optionalDependencies: - '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) - '@testing-library/react-native': 13.3.3(jest@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react-test-renderer@19.1.0(react@18.3.1))(react@18.3.1) - react-dom: 19.1.0(react@18.3.1) - react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) - react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) - react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.1.0(react@18.3.1))(react@18.3.1) - react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@18.3.1))(react@18.3.1)(webpack@5.100.2(esbuild@0.27.0)) - transitivePeerDependencies: - - '@react-native-masked-view/masked-view' - - '@types/react' - - '@types/react-dom' - - supports-color - optional: true - - expo-router@6.0.15(ewsdnidpxwg6dzyorlbigkbme4): - dependencies: - '@expo/metro-runtime': 6.1.2(expo@54.0.13)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@expo/schema-utils': 0.1.7 - '@radix-ui/react-slot': 1.2.0(@types/react@19.2.7)(react@19.1.0) - '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-navigation/bottom-tabs': 7.8.6(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@react-navigation/native': 7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@react-navigation/native-stack': 7.8.0(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - client-only: 0.0.1 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - expo: 54.0.13(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native-webview@13.12.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - expo-constants: 18.0.10(expo@54.0.13)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)) - expo-linking: 8.0.9(expo@54.0.13)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - expo-server: 1.0.4 - fast-deep-equal: 3.1.3 - invariant: 2.2.4 - nanoid: 3.3.11 - query-string: 7.1.3 - react: 19.1.0 - react-fast-compare: 3.2.2 - react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) - react-native-is-edge-to-edge: 1.2.1(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - react-native-safe-area-context: 5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - react-native-screens: 4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - semver: 7.6.3 - server-only: 0.0.1 - sf-symbols-typescript: 2.1.0 - shallowequal: 1.1.0 - use-latest-callback: 0.2.6(react@19.1.0) - vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - optionalDependencies: - '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@testing-library/react-native': 13.3.3(jest@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) - react-dom: 19.1.0(react@19.1.0) - react-native-gesture-handler: 2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2(esbuild@0.27.0)) - transitivePeerDependencies: - - '@react-native-masked-view/masked-view' - - '@types/react' - - '@types/react-dom' - - supports-color - - expo-router@6.0.15(f6my4lgi43u5yo7kczxd3pw7ru): - dependencies: - '@expo/metro-runtime': 6.1.2(expo@54.0.12)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@expo/schema-utils': 0.1.7 - '@radix-ui/react-slot': 1.2.0(@types/react@19.2.7)(react@19.1.0) - '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-navigation/bottom-tabs': 7.8.6(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@react-navigation/native': 7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@react-navigation/native-stack': 7.8.0(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - client-only: 0.0.1 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - expo: 54.0.12(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native-webview@13.12.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - expo-constants: 18.0.10(expo@54.0.12)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)) - expo-linking: 8.0.9(expo@54.0.12)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - expo-server: 1.0.4 - fast-deep-equal: 3.1.3 - invariant: 2.2.4 - nanoid: 3.3.11 - query-string: 7.1.3 - react: 19.1.0 - react-fast-compare: 3.2.2 - react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) - react-native-is-edge-to-edge: 1.2.1(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - react-native-safe-area-context: 5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - react-native-screens: 4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - semver: 7.6.3 - server-only: 0.0.1 - sf-symbols-typescript: 2.1.0 - shallowequal: 1.1.0 - use-latest-callback: 0.2.6(react@19.1.0) - vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - optionalDependencies: - '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@testing-library/react-native': 13.3.3(jest@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) - react-dom: 19.1.0(react@19.1.0) - react-native-gesture-handler: 2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2(esbuild@0.27.0)) - transitivePeerDependencies: - - '@react-native-masked-view/masked-view' - - '@types/react' - - '@types/react-dom' - - supports-color - - expo-router@6.0.15(g2vconqrtzzmzlh6ymhbjirn5e): + expo-router@6.0.15(7mqaurqidri6vkknnsci36yp4e): dependencies: '@expo/metro-runtime': 6.1.2(expo@54.0.25)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) '@expo/schema-utils': 0.1.7 @@ -40339,7 +40282,193 @@ snapshots: vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) optionalDependencies: '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@testing-library/react-native': 13.3.3(jest@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) + '@testing-library/react-native': 13.3.3(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) + react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.97.1(esbuild@0.19.12)) + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + - '@types/react' + - '@types/react-dom' + - supports-color + optional: true + + expo-router@6.0.15(k2muy65dii4k2uiuhg4mwyy6ki): + dependencies: + '@expo/metro-runtime': 6.1.2(expo@54.0.25)(react-dom@19.1.0(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + '@expo/schema-utils': 0.1.7 + '@radix-ui/react-slot': 1.2.0(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.1.0(react@18.3.1))(react@18.3.1) + '@react-navigation/bottom-tabs': 7.8.6(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + '@react-navigation/native': 7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + '@react-navigation/native-stack': 7.8.0(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + client-only: 0.0.1 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + expo: 54.0.25(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native-webview@13.12.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + expo-constants: 18.0.10(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1)) + expo-linking: 8.0.9(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + expo-server: 1.0.4 + fast-deep-equal: 3.1.3 + invariant: 2.2.4 + nanoid: 3.3.11 + query-string: 7.1.3 + react: 18.3.1 + react-fast-compare: 3.2.2 + react-native: 0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1) + react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + react-native-screens: 4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + semver: 7.6.3 + server-only: 0.0.1 + sf-symbols-typescript: 2.1.0 + shallowequal: 1.1.0 + use-latest-callback: 0.2.6(react@18.3.1) + vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.1.0(react@18.3.1))(react@18.3.1) + optionalDependencies: + '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + '@testing-library/react-native': 13.3.3(jest@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react-test-renderer@19.1.0(react@18.3.1))(react@18.3.1) + react-dom: 19.1.0(react@18.3.1) + react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@18.3.27)(react@18.3.1))(react@18.3.1) + react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.1.0(react@18.3.1))(react@18.3.1) + react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@18.3.1))(react@18.3.1)(webpack@5.100.2(esbuild@0.27.0)) + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + - '@types/react' + - '@types/react-dom' + - supports-color + optional: true + + expo-router@6.0.15(psh6y5usp77eac7hbbid4ov2mi): + dependencies: + '@expo/metro-runtime': 6.1.2(expo@54.0.13)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@expo/schema-utils': 0.1.7 + '@radix-ui/react-slot': 1.2.0(@types/react@19.2.7)(react@19.1.0) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-navigation/bottom-tabs': 7.8.6(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@react-navigation/native': 7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@react-navigation/native-stack': 7.8.0(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + client-only: 0.0.1 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + expo: 54.0.13(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native-webview@13.12.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + expo-constants: 18.0.10(expo@54.0.13)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)) + expo-linking: 8.0.9(expo@54.0.13)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + expo-server: 1.0.4 + fast-deep-equal: 3.1.3 + invariant: 2.2.4 + nanoid: 3.3.11 + query-string: 7.1.3 + react: 19.1.0 + react-fast-compare: 3.2.2 + react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) + react-native-is-edge-to-edge: 1.2.1(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-safe-area-context: 5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-screens: 4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + semver: 7.6.3 + server-only: 0.0.1 + sf-symbols-typescript: 2.1.0 + shallowequal: 1.1.0 + use-latest-callback: 0.2.6(react@19.1.0) + vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + optionalDependencies: + '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@testing-library/react-native': 13.3.3(jest@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) + react-native-gesture-handler: 2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2(esbuild@0.27.0)) + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + - '@types/react' + - '@types/react-dom' + - supports-color + + expo-router@6.0.15(supujcsjl47mo53hnmela4rs24): + dependencies: + '@expo/metro-runtime': 6.1.2(expo@54.0.12)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@expo/schema-utils': 0.1.7 + '@radix-ui/react-slot': 1.2.0(@types/react@19.2.7)(react@19.1.0) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-navigation/bottom-tabs': 7.8.6(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@react-navigation/native': 7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@react-navigation/native-stack': 7.8.0(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + client-only: 0.0.1 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + expo: 54.0.12(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native-webview@13.12.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + expo-constants: 18.0.10(expo@54.0.12)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)) + expo-linking: 8.0.9(expo@54.0.12)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + expo-server: 1.0.4 + fast-deep-equal: 3.1.3 + invariant: 2.2.4 + nanoid: 3.3.11 + query-string: 7.1.3 + react: 19.1.0 + react-fast-compare: 3.2.2 + react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) + react-native-is-edge-to-edge: 1.2.1(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-safe-area-context: 5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-screens: 4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + semver: 7.6.3 + server-only: 0.0.1 + sf-symbols-typescript: 2.1.0 + shallowequal: 1.1.0 + use-latest-callback: 0.2.6(react@19.1.0) + vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + optionalDependencies: + '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@testing-library/react-native': 13.3.3(jest@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) + react-native-gesture-handler: 2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2(esbuild@0.27.0)) + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + - '@types/react' + - '@types/react-dom' + - supports-color + + expo-router@6.0.15(vmxlpuhz6xqbe2ee7fdabyqx3y): + dependencies: + '@expo/metro-runtime': 6.1.2(expo@54.0.25)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@expo/schema-utils': 0.1.7 + '@radix-ui/react-slot': 1.2.0(@types/react@19.2.7)(react@19.1.0) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-navigation/bottom-tabs': 7.8.6(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@react-navigation/native': 7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@react-navigation/native-stack': 7.8.0(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + client-only: 0.0.1 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + expo: 54.0.25(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native-webview@13.12.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + expo-constants: 18.0.10(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)) + expo-linking: 8.0.9(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + expo-server: 1.0.4 + fast-deep-equal: 3.1.3 + invariant: 2.2.4 + nanoid: 3.3.11 + query-string: 7.1.3 + react: 19.1.0 + react-fast-compare: 3.2.2 + react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0) + react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + react-native-screens: 4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + semver: 7.6.3 + server-only: 0.0.1 + sf-symbols-typescript: 2.1.0 + shallowequal: 1.1.0 + use-latest-callback: 0.2.6(react@19.1.0) + vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + optionalDependencies: + '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) + '@testing-library/react-native': 13.3.3(jest@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) react-dom: 19.1.0(react@19.1.0) react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) @@ -40351,7 +40480,7 @@ snapshots: - '@types/react-dom' - supports-color - expo-router@6.0.15(lvmr432nn4pnebfhbi2qjoy364): + expo-router@6.0.15(xyagqkzos5etzn52s4may7634u): dependencies: '@expo/metro-runtime': 6.1.2(expo@54.0.25)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) '@expo/schema-utils': 0.1.7 @@ -40385,7 +40514,7 @@ snapshots: vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) optionalDependencies: '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) - '@testing-library/react-native': 13.3.3(jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) + '@testing-library/react-native': 13.3.3(jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) react-dom: 19.1.0(react@19.1.0) react-native-gesture-handler: 2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0) @@ -41292,6 +41421,23 @@ snapshots: forever-agent@0.6.1: {} + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.19.12)): + dependencies: + '@babel/code-frame': 7.27.1 + chalk: 4.1.2 + chokidar: 3.6.0 + cosmiconfig: 8.3.6(typescript@5.7.2) + deepmerge: 4.3.1 + fs-extra: 10.1.0 + memfs: 3.5.3 + minimatch: 3.1.2 + node-abort-controller: 3.1.1 + schema-utils: 3.3.0 + semver: 7.7.3 + tapable: 2.3.0 + typescript: 5.7.2 + webpack: 5.97.1(esbuild@0.19.12) + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.27.0)): dependencies: '@babel/code-frame': 7.27.1 @@ -42779,16 +42925,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)): + jest-cli@29.7.0(@types/node@24.10.1): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) + '@jest/core': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) + create-jest: 29.7.0(@types/node@24.10.1) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) + jest-config: 29.7.0(@types/node@24.10.1) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -42798,15 +42944,15 @@ snapshots: - supports-color - ts-node - jest-cli@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)): + jest-cli@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)): dependencies: - '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)) jest-util: 30.2.0 jest-validate: 30.2.0 yargs: 17.7.2 @@ -42818,6 +42964,25 @@ snapshots: - ts-node optional: true + jest-cli@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0)): + dependencies: + '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) + '@jest/test-result': 30.2.0 + '@jest/types': 30.2.0 + chalk: 4.1.2 + exit-x: 0.2.2 + import-local: 3.2.0 + jest-config: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0)) + jest-util: 30.2.0 + jest-validate: 30.2.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + jest-cli@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): dependencies: '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) @@ -42837,15 +43002,15 @@ snapshots: - supports-color - ts-node - jest-cli@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)): + jest-cli@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)): dependencies: - '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)) + '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)) + jest-config: 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) jest-util: 30.2.0 jest-validate: 30.2.0 yargs: 17.7.2 @@ -42857,25 +43022,35 @@ snapshots: - ts-node optional: true - jest-cli@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)): + jest-config@29.7.0(@types/node@22.19.1): dependencies: - '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) - '@jest/test-result': 30.2.0 - '@jest/types': 30.2.0 + '@babel/core': 7.28.5 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.5) chalk: 4.1.2 - exit-x: 0.2.2 - import-local: 3.2.0 - jest-config: 30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) - jest-util: 30.2.0 - jest-validate: 30.2.0 - yargs: 17.7.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.19.1 transitivePeerDependencies: - - '@types/node' - babel-plugin-macros - - esbuild-register - supports-color - - ts-node - optional: true jest-config@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): dependencies: @@ -42908,38 +43083,7 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)): - dependencies: - '@babel/core': 7.28.5 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.28.5) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.19.1 - ts-node: 10.9.2(@types/node@24.10.1)(typescript@5.9.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)): + jest-config@29.7.0(@types/node@24.10.1): dependencies: '@babel/core': 7.28.5 '@jest/test-sequencer': 29.7.0 @@ -42965,12 +43109,11 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 24.10.1 - ts-node: 10.9.2(@types/node@24.10.1)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)): + jest-config@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)): dependencies: '@babel/core': 7.28.5 '@jest/get-type': 30.1.0 @@ -42999,13 +43142,12 @@ snapshots: optionalDependencies: '@types/node': 20.19.25 esbuild-register: 3.6.0(esbuild@0.27.0) - ts-node: 10.9.2(@types/node@20.19.25)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color optional: true - jest-config@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)): + jest-config@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0)): dependencies: '@babel/core': 7.28.5 '@jest/get-type': 30.1.0 @@ -43034,11 +43176,9 @@ snapshots: optionalDependencies: '@types/node': 22.19.1 esbuild-register: 3.6.0(esbuild@0.27.0) - ts-node: 10.9.2(@types/node@20.19.25)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color - optional: true jest-config@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): dependencies: @@ -43074,7 +43214,7 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)): + jest-config@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)): dependencies: '@babel/core': 7.28.5 '@jest/get-type': 30.1.0 @@ -43101,114 +43241,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.19.1 esbuild-register: 3.6.0(esbuild@0.27.0) - ts-node: 10.9.2(@types/node@24.10.1)(typescript@5.8.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - optional: true - - jest-config@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)): - dependencies: - '@babel/core': 7.28.5 - '@jest/get-type': 30.1.0 - '@jest/pattern': 30.0.1 - '@jest/test-sequencer': 30.2.0 - '@jest/types': 30.2.0 - babel-jest: 30.2.0(@babel/core@7.28.5) - chalk: 4.1.2 - ci-info: 4.3.1 - deepmerge: 4.3.1 - glob: 10.5.0 - graceful-fs: 4.2.11 - jest-circus: 30.2.0 - jest-docblock: 30.2.0 - jest-environment-node: 30.2.0 - jest-regex-util: 30.0.1 - jest-resolve: 30.2.0 - jest-runner: 30.2.0 - jest-util: 30.2.0 - jest-validate: 30.2.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 30.2.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.19.1 - esbuild-register: 3.6.0(esbuild@0.27.0) - ts-node: 10.9.2(@types/node@24.10.1)(typescript@5.9.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - optional: true - - jest-config@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)): - dependencies: - '@babel/core': 7.28.5 - '@jest/get-type': 30.1.0 - '@jest/pattern': 30.0.1 - '@jest/test-sequencer': 30.2.0 - '@jest/types': 30.2.0 - babel-jest: 30.2.0(@babel/core@7.28.5) - chalk: 4.1.2 - ci-info: 4.3.1 - deepmerge: 4.3.1 - glob: 10.5.0 - graceful-fs: 4.2.11 - jest-circus: 30.2.0 - jest-docblock: 30.2.0 - jest-environment-node: 30.2.0 - jest-regex-util: 30.0.1 - jest-resolve: 30.2.0 - jest-runner: 30.2.0 - jest-util: 30.2.0 - jest-validate: 30.2.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 30.2.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 24.10.1 - esbuild-register: 3.6.0(esbuild@0.27.0) - ts-node: 10.9.2(@types/node@24.10.1)(typescript@5.8.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - optional: true - - jest-config@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)): - dependencies: - '@babel/core': 7.28.5 - '@jest/get-type': 30.1.0 - '@jest/pattern': 30.0.1 - '@jest/test-sequencer': 30.2.0 - '@jest/types': 30.2.0 - babel-jest: 30.2.0(@babel/core@7.28.5) - chalk: 4.1.2 - ci-info: 4.3.1 - deepmerge: 4.3.1 - glob: 10.5.0 - graceful-fs: 4.2.11 - jest-circus: 30.2.0 - jest-docblock: 30.2.0 - jest-environment-node: 30.2.0 - jest-regex-util: 30.0.1 - jest-resolve: 30.2.0 - jest-runner: 30.2.0 - jest-util: 30.2.0 - jest-validate: 30.2.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 30.2.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 24.10.1 - esbuild-register: 3.6.0(esbuild@0.27.0) - ts-node: 10.9.2(@types/node@24.10.1)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -43662,24 +43695,24 @@ snapshots: - supports-color - ts-node - jest@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)): + jest@29.7.0(@types/node@24.10.1): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) + '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) + jest-cli: 29.7.0(@types/node@24.10.1) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node - jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)): + jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)): dependencies: - '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) '@jest/types': 30.2.0 import-local: 3.2.0 - jest-cli: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + jest-cli: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -43688,6 +43721,19 @@ snapshots: - ts-node optional: true + jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0)): + dependencies: + '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) + '@jest/types': 30.2.0 + import-local: 3.2.0 + jest-cli: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): dependencies: '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) @@ -43701,26 +43747,12 @@ snapshots: - supports-color - ts-node - jest@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)): + jest@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)): dependencies: - '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)) + '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) '@jest/types': 30.2.0 import-local: 3.2.0 - jest-cli: 30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - esbuild-register - - supports-color - - ts-node - optional: true - - jest@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)): - dependencies: - '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) - '@jest/types': 30.2.0 - import-local: 3.2.0 - jest-cli: 30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)) + jest-cli: 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -48108,6 +48140,16 @@ snapshots: webpack-sources: 3.3.3 optional: true + react-server-dom-webpack@19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.97.1(esbuild@0.19.12)): + dependencies: + acorn-loose: 8.5.2 + neo-async: 2.6.2 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + webpack: 5.97.1(esbuild@0.19.12) + webpack-sources: 3.3.3 + optional: true + react-style-singleton@2.2.3(@types/react@18.3.27)(react@18.3.1): dependencies: get-nonce: 1.0.1 @@ -49616,6 +49658,17 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 + terser-webpack-plugin@5.3.14(esbuild@0.19.12)(webpack@5.97.1(esbuild@0.19.12)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + serialize-javascript: 6.0.2 + terser: 5.44.1 + webpack: 5.97.1(esbuild@0.19.12) + optionalDependencies: + esbuild: 0.19.12 + terser-webpack-plugin@5.3.14(esbuild@0.27.0)(webpack@5.100.2(esbuild@0.27.0)): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -49828,6 +49881,27 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.19.12)(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.8 + jest: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.3 + type-fest: 4.41.0 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.28.5 + '@jest/transform': 30.2.0 + '@jest/types': 30.2.0 + babel-jest: 30.2.0(@babel/core@7.28.5) + esbuild: 0.19.12 + jest-util: 30.2.0 + ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.0)(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 @@ -49849,12 +49923,12 @@ snapshots: esbuild: 0.27.0 jest-util: 30.2.0 - ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.0)(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + jest: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -49867,6 +49941,7 @@ snapshots: '@jest/transform': 30.2.0 '@jest/types': 30.2.0 babel-jest: 30.2.0(@babel/core@7.28.5) + esbuild: 0.27.0 jest-util: 30.2.0 ts-loader@9.5.4(typescript@5.9.3)(webpack@5.100.2(esbuild@0.27.0)): @@ -49889,6 +49964,16 @@ snapshots: typescript: 5.9.3 webpack: 5.100.2 + ts-loader@9.5.4(typescript@5.9.3)(webpack@5.97.1(esbuild@0.19.12)): + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.18.3 + micromatch: 4.0.8 + semver: 7.7.3 + source-map: 0.7.6 + typescript: 5.9.3 + webpack: 5.97.1(esbuild@0.19.12) + ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -49926,25 +50011,6 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.12 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 24.10.1 - acorn: 8.15.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.8.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - optional: true - ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -50680,6 +50746,23 @@ snapshots: lightningcss: 1.30.2 terser: 5.44.1 + vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 20.19.25 + fsevents: 2.3.3 + jiti: 1.21.7 + lightningcss: 1.30.2 + terser: 5.44.1 + tsx: 4.21.0 + yaml: 2.8.1 + vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1): dependencies: esbuild: 0.25.12 @@ -50783,6 +50866,10 @@ snapshots: tsx: 4.21.0 yaml: 2.8.1 + vitefu@1.1.1(vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1)): + optionalDependencies: + vite: 6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1) + vitefu@1.1.1(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1)): optionalDependencies: vite: 6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1) @@ -50943,7 +51030,7 @@ snapshots: optionalDependencies: '@types/debug': 4.1.12 '@types/node': 24.10.1 - '@vitest/browser': 3.2.4(playwright@1.57.0)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1))(vitest@3.2.4) + '@vitest/browser': 3.2.4(playwright@1.57.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.1))(vitest@3.2.4) '@vitest/ui': 3.2.4(vitest@3.2.4) jsdom: 27.2.0 transitivePeerDependencies: @@ -51282,6 +51369,36 @@ snapshots: - esbuild - uglify-js + webpack@5.97.1(esbuild@0.19.12): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.15.0 + browserslist: 4.28.0 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.3 + es-module-lexer: 1.7.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.1 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.3.0 + terser-webpack-plugin: 5.3.14(esbuild@0.19.12)(webpack@5.97.1(esbuild@0.19.12)) + watchpack: 2.4.4 + webpack-sources: 3.3.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + webpack@5.97.1(esbuild@0.27.0): dependencies: '@types/eslint-scope': 3.7.7