diff --git a/.gitignore b/.gitignore index 06e6038..0e9fe9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules dist *.tsbuildinfo +design/ \ No newline at end of file diff --git a/demo/main.ts b/demo/main.ts index cad53e0..43afb95 100644 --- a/demo/main.ts +++ b/demo/main.ts @@ -13,7 +13,10 @@ import { fern, fernSchema, fernPresets, flower, flowerSchema, flowerPresets, deadTree, deadTreeSchema, deadTreePresets, + leafyTree, leafyTreeSchema, leafyTreePresets, rock, rockSchema, rockPresets, + sharpRock, sharpRockSchema, sharpRockPresets, + blockRock, blockRockSchema, blockRockPresets, } from '../src/generators' import { createEditor } from './editor/editor' import type { OptionSchema } from '../src/core/schema' @@ -36,8 +39,11 @@ const GENERATORS: Record = { grass: { label: 'Grass', gen: grass, schema: grassSchema, presets: grassPresets, sizeKey: 'height', defaultSize: 0.6 }, fern: { label: 'Fern', gen: fern, schema: fernSchema, presets: fernPresets, sizeKey: 'length', defaultSize: 1.0 }, flower: { label: 'Flower', gen: flower, schema: flowerSchema, presets: flowerPresets, sizeKey: 'height', defaultSize: 0.5 }, - dead: { label: 'Dead', gen: deadTree, schema: deadTreeSchema, presets: deadTreePresets, sizeKey: 'height', defaultSize: 1.5 }, + leafy: { label: 'Leafy', gen: leafyTree, schema: leafyTreeSchema, presets: leafyTreePresets, sizeKey: 'height', defaultSize: 2.5 }, + dead: { label: 'Dead', gen: deadTree, schema: deadTreeSchema, presets: deadTreePresets, sizeKey: 'height', defaultSize: 3 }, rock: { label: 'Rock', gen: rock, schema: rockSchema, presets: rockPresets, sizeKey: 'size', defaultSize: 0.6 }, + sharp: { label: 'Sharp', gen: sharpRock, schema: sharpRockSchema, presets: sharpRockPresets, sizeKey: 'size', defaultSize: 1.0 }, + block: { label: 'Blocks', gen: blockRock, schema: blockRockSchema, presets: blockRockPresets, sizeKey: 'size', defaultSize: 0.7 }, } // --- Renderer --- diff --git a/src/build/index.ts b/src/build/index.ts index 25e3d44..472a731 100644 --- a/src/build/index.ts +++ b/src/build/index.ts @@ -5,3 +5,4 @@ export { facetShade, heightShade, type FacetShadeOptions } from './shade' export { scatterOnSurface, type SurfacePoint, type ScatterOnSurfaceOptions } from './surface' export { blade, type BladeOptions } from './blade' export { branches, type BranchesOptions, type BranchResult, type BranchTip } from './branches' +export { metaballs, surfaceNets, type MetaBall, type MetaballsOptions, type SurfaceNetsOptions } from './metaballs' diff --git a/src/build/metaballs.ts b/src/build/metaballs.ts new file mode 100644 index 0000000..de6d3d6 --- /dev/null +++ b/src/build/metaballs.ts @@ -0,0 +1,217 @@ +import * as THREE from 'three' +import { Mesh } from '../core/mesh' +import type { Vec3 } from '../core/types' + +// --- Naive Surface Nets (dual isosurface extraction), after Mikola Lysenko. --- +// Samples a scalar field on a grid and reconstructs a watertight surface around the +// `inside` region — the right way to mesh an SDF / metaball field (genuine merged +// lobes and necks, not just a bumpy sphere). + +const CUBE_EDGES = new Int32Array(24) +const EDGE_TABLE = new Int32Array(256) +;(function initTables() { + let k = 0 + for (let i = 0; i < 8; i++) { + for (let j = 1; j <= 4; j <<= 1) { + const p = i ^ j + if (i <= p) { CUBE_EDGES[k++] = i; CUBE_EDGES[k++] = p } + } + } + for (let i = 0; i < 256; i++) { + let em = 0 + for (let j = 0; j < 24; j += 2) { + const a = !!(i & (1 << CUBE_EDGES[j])) + const b = !!(i & (1 << CUBE_EDGES[j + 1])) + em |= a !== b ? (1 << (j >> 1)) : 0 + } + EDGE_TABLE[i] = em + } +})() + +/** Mesh the zero-isosurface of `data` (inside where data < 0) on a grid of `dims` samples. */ +function netsFromVolume(data: Float32Array, dims: [number, number, number]) { + const vertices: number[][] = [] + const faces: number[][] = [] + const R = [1, dims[0] + 1, (dims[0] + 1) * (dims[1] + 1)] + const grid = new Float32Array(8) + let bufNo = 1 + const buffer = new Int32Array(R[2] * 2) + let n = 0 + const x = [0, 0, 0] + + for (x[2] = 0; x[2] < dims[2] - 1; ++x[2], n += dims[0], bufNo ^= 1, R[2] = -R[2]) { + let m = 1 + (dims[0] + 1) * (1 + bufNo * (dims[1] + 1)) + for (x[1] = 0; x[1] < dims[1] - 1; ++x[1], ++n, m += 2) + for (x[0] = 0; x[0] < dims[0] - 1; ++x[0], ++n, ++m) { + let mask = 0, g = 0, idx = n + for (let k = 0; k < 2; ++k, idx += dims[0] * (dims[1] - 2)) + for (let j = 0; j < 2; ++j, idx += dims[0] - 2) + for (let i = 0; i < 2; ++i, ++g, ++idx) { + const p = data[idx] + grid[g] = p + mask |= p < 0 ? 1 << g : 0 + } + if (mask === 0 || mask === 0xff) continue + + const edgeMask = EDGE_TABLE[mask] + const v = [0, 0, 0] + let eCount = 0 + for (let i = 0; i < 12; ++i) { + if (!(edgeMask & (1 << i))) continue + const e0 = CUBE_EDGES[i << 1] + const e1 = CUBE_EDGES[(i << 1) + 1] + const g0 = grid[e0] + const g1 = grid[e1] + let t = g0 - g1 + if (Math.abs(t) > 1e-6) t = g0 / t; else continue + ++eCount + for (let j = 0, kk = 1; j < 3; ++j, kk <<= 1) { + const a = e0 & kk, b = e1 & kk + if (a !== b) v[j] += a ? 1 - t : t + else v[j] += a ? 1 : 0 + } + } + if (eCount === 0) continue + const s = 1 / eCount + v[0] = x[0] + s * v[0]; v[1] = x[1] + s * v[1]; v[2] = x[2] + s * v[2] + + buffer[m] = vertices.length + vertices.push([v[0], v[1], v[2]]) + + for (let i = 0; i < 3; ++i) { + if (!(edgeMask & (1 << i))) continue + const iu = (i + 1) % 3, iv = (i + 2) % 3 + if (x[iu] === 0 || x[iv] === 0) continue + const du = R[iu], dv = R[iv] + if (mask & 1) faces.push([buffer[m], buffer[m - du], buffer[m - du - dv], buffer[m - dv]]) + else faces.push([buffer[m], buffer[m - dv], buffer[m - du - dv], buffer[m - du]]) + } + } + } + return { vertices, faces } +} + +function emptyMesh(): Mesh { + const g = new THREE.BufferGeometry() + g.setAttribute('position', new THREE.BufferAttribute(new Float32Array(0), 3)) + return new Mesh(g) +} + +export interface SurfaceNetsOptions { + /** Bounding box to sample within. */ + min: Vec3 + max: Vec3 + /** Number of samples along the longest axis (others scaled to keep cubic cells). Default 32. */ + resolution?: number + /** Isolevel of the field to extract. Default 0. */ + iso?: number + /** Whether "inside" the surface is where field is below or above the isolevel. Default 'below'. */ + inside?: 'below' | 'above' +} + +/** + * Reconstruct a mesh of the `iso` level-set of an arbitrary scalar field / SDF via + * surface nets. Returns flat-shaded (non-indexed) triangles — chunky and low-poly at + * modest resolution, the surface tightening as resolution rises. + */ +export function surfaceNets(field: (x: number, y: number, z: number) => number, options: SurfaceNetsOptions): Mesh { + const iso = options.iso ?? 0 + const above = options.inside === 'above' + const [minx, miny, minz] = options.min + const [maxx, maxy, maxz] = options.max + const ext = [maxx - minx, maxy - miny, maxz - minz] + const longest = Math.max(ext[0], ext[1], ext[2]) || 1 + const spacing = longest / (options.resolution ?? 32) + const dims: [number, number, number] = [ + Math.max(2, Math.ceil(ext[0] / spacing) + 2), + Math.max(2, Math.ceil(ext[1] / spacing) + 2), + Math.max(2, Math.ceil(ext[2] / spacing) + 2), + ] + const [nx, ny, nz] = dims + + // Sample the field (pad by one cell so the surface never clips the grid edge). + const ox = minx - spacing, oy = miny - spacing, oz = minz - spacing + const data = new Float32Array(nx * ny * nz) + let i = 0 + for (let z = 0; z < nz; z++) + for (let y = 0; y < ny; y++) + for (let x = 0; x < nx; x++) { + const f = field(ox + x * spacing, oy + y * spacing, oz + z * spacing) + data[i++] = above ? iso - f : f - iso + } + + const { vertices, faces } = netsFromVolume(data, dims) + if (faces.length === 0) return emptyMesh() + + const pos: number[] = [] + const wx = (vi: number) => ox + vertices[vi][0] * spacing + const wy = (vi: number) => oy + vertices[vi][1] * spacing + const wz = (vi: number) => oz + vertices[vi][2] * spacing + for (const f of faces) { + const a = f[0], b = f[1], c = f[2], d = f[3] + pos.push(wx(a), wy(a), wz(a), wx(b), wy(b), wz(b), wx(c), wy(c), wz(c)) + pos.push(wx(a), wy(a), wz(a), wx(c), wy(c), wz(c), wx(d), wy(d), wz(d)) + } + + const geo = new THREE.BufferGeometry() + geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(pos), 3)) + geo.computeVertexNormals() + return new Mesh(geo) +} + +export interface MetaBall { + center: Vec3 + radius: number +} + +export interface MetaballsOptions { + /** Samples along the longest axis of the bounding box. Default 28. */ + resolution?: number + /** Field threshold. Lower = balls reach further and merge more. Default 0.5. */ + isolevel?: number + /** Each ball's influence radius = radius × support. Default 2.2. */ + support?: number +} + +/** + * Build one watertight surface enveloping a set of metaballs — they melt together with + * smooth necks where they meet, true to the field's topology (unlike a star-convex + * hull). Uses a smooth finite-support kernel sampled into {@link surfaceNets}. + */ +export function metaballs(balls: MetaBall[], options: MetaballsOptions = {}): Mesh { + if (balls.length === 0) return emptyMesh() + const support = options.support ?? 2.2 + const iso = options.isolevel ?? 0.5 + + let minx = Infinity, miny = Infinity, minz = Infinity + let maxx = -Infinity, maxy = -Infinity, maxz = -Infinity + for (const b of balls) { + const R = b.radius * support + minx = Math.min(minx, b.center[0] - R); maxx = Math.max(maxx, b.center[0] + R) + miny = Math.min(miny, b.center[1] - R); maxy = Math.max(maxy, b.center[1] + R) + minz = Math.min(minz, b.center[2] - R); maxz = Math.max(maxz, b.center[2] + R) + } + + const field = (x: number, y: number, z: number): number => { + let s = 0 + for (const b of balls) { + const R = b.radius * support + const dx = x - b.center[0], dy = y - b.center[1], dz = z - b.center[2] + const d2 = dx * dx + dy * dy + dz * dz + const R2 = R * R + if (d2 < R2) { + const q = 1 - d2 / R2 + s += q * q + } + } + return s + } + + return surfaceNets(field, { + min: [minx, miny, minz], + max: [maxx, maxy, maxz], + resolution: options.resolution ?? 28, + iso, + inside: 'above', + }) +} diff --git a/src/generators/rocks/block-rock.ts b/src/generators/rocks/block-rock.ts new file mode 100644 index 0000000..9f689fc --- /dev/null +++ b/src/generators/rocks/block-rock.ts @@ -0,0 +1,118 @@ +import { setup, facetShade } from '../../build' +import { pickRandom } from '../../color' +import { UberNoise } from '../../noise' +import { box } from '../../primitives' +import { merge, snow as applySnow } from '../../ops' +import type { Mesh } from '../../core/mesh' +import type { OptionSchema, OptionInput } from '../../core/schema' + +export const blockRockSchema = { + seed: { type: 'integer', default: 1, min: 1, max: 100, label: 'Seed' }, + size: { type: 'range', default: 0.7, min: 0.2, max: 2.5, step: 0.05, label: 'Size' }, + blocks: { type: 'integer', default: 3, min: 1, max: 10, label: 'Blocks' }, + aspect: { type: 'range', default: 1.0, min: 0.4, max: 2.5, step: 0.05, label: 'Aspect (Height)' }, + irregularity: { type: 'range', default: 0.45, min: 0, max: 0.8, step: 0.05, label: 'Irregularity' }, + spread: { type: 'range', default: 0.7, min: 0.3, max: 1.2, step: 0.05, label: 'Spread' }, + tilt: { type: 'range', default: 15, min: 0, max: 35, step: 1, label: 'Tilt (°)' }, + noise: { type: 'range', default: 0.18, min: 0, max: 0.5, step: 0.02, label: 'Lumpiness' }, + jitter: { type: 'range', default: 0.1, min: 0, max: 0.2, step: 0.005, label: 'Jitter' }, + sink: { type: 'range', default: 0.15, min: 0, max: 0.5, step: 0.05, label: 'Sink' }, + mossColors: { type: 'color-array', default: [], min: 0, max: 4, label: 'Moss Colors' }, + mossAngle: { type: 'range', default: 40, min: 0, max: 80, step: 5, label: 'Moss Min Angle (°)' }, + snowColors: { type: 'color-array', default: [], min: 0, max: 6, label: 'Snow Colors' }, + snowAngle: { type: 'range', default: 35, min: 0, max: 80, step: 5, label: 'Snow Min Angle (°)' }, + snowDepth: { type: 'range', default: 0, min: 0, max: 0.3, step: 0.01, label: 'Snow Depth' }, + colors: { type: 'color-array', default: ['#56514b', '#6a645c', '#7e776d'], min: 1, max: 6, label: 'Rock Colors' }, +} satisfies OptionSchema + +export type BlockRockOptions = Partial> & { preset?: string } + +export const blockRockPresets: Record> = { + default: {}, + monolith: { blocks: 1, size: 1.4, aspect: 2.0, tilt: 5, spread: 0, irregularity: 0.2, noise: 0.12 }, + rubble: { blocks: 6, spread: 1.0, irregularity: 0.55, aspect: 0.7, tilt: 20 }, + sandstone: { colors: ['#a98a62', '#bb9b70', '#c9ad82'], aspect: 0.55, tilt: 6 }, + mossy: { mossColors: ['#3f6a2a', '#4e7d33', '#5c8c3a'], mossAngle: 35 }, + snowy: { snowColors: ['#eef0f5', '#e4e8f0', '#f4f6fb'], snowDepth: 0.06, snowAngle: 18 }, +} + +export function blockRock(options: BlockRockOptions = {}): Mesh { + const { o, rng } = setup(blockRockSchema, options, blockRockPresets) + const shapeRng = rng.stream('shape') + const colorRng = rng.stream('color') + const mossRng = rng.stream('moss') + const snowRng = rng.stream('snow') + + const colorNoise = new UberNoise({ seed: colorRng.seed(), scale: 2 }) + const hasMoss = o.mossColors.length > 0 + const mossNoise = hasMoss ? new UberNoise({ seed: mossRng.seed(), scale: 2.5 }) : null + const mossThreshold = Math.sin((o.mossAngle * Math.PI) / 180) + + const hasSnow = o.snowColors.length > 0 + const useGeoSnow = hasSnow && o.snowDepth > 0 + const snowNoise = hasSnow && !useGeoSnow ? new UberNoise({ seed: snowRng.seed(), scale: 2 }) : null + const snowThreshold = Math.sin((o.snowAngle * Math.PI) / 180) + + const tiltMax = (o.tilt * Math.PI) / 180 + const blocks: Mesh[] = [] + let anchorR = 0 + for (let i = 0; i < o.blocks; i++) { + // Biggest block at the center, smaller ones scattered around it. + const rank = i / Math.max(1, o.blocks - 1) + const s = o.size * (1 - 0.45 * rank) * shapeRng.float(0.85, 1.15) + const w = s * (1 + o.irregularity * shapeRng.float(-1, 1)) + const h = s * o.aspect * (1 + o.irregularity * shapeRng.float(-1, 1)) + const d = s * (1 + o.irregularity * shapeRng.float(-1, 1)) + // Satellites sit a half-extent-aware distance from the central block, at evenly + // distributed angles — close enough to embed, far enough that faces of two blocks + // never sit nearly coplanar inside each other (which reads as z-fighting). + const r = (w + d) / 4 + if (i === 0) anchorR = r + const angle = ((i - 1) / Math.max(1, o.blocks - 1)) * Math.PI * 2 + shapeRng.float(-0.4, 0.4) + const dist = i === 0 ? 0 : (anchorR + r) * o.spread * shapeRng.float(0.9, 1.1) + const yaw = shapeRng.float(0, Math.PI * 2) + const tiltX = tiltMax * shapeRng.float(-1, 1) + const tiltZ = tiltMax * shapeRng.float(-1, 1) + // Per-block noise: blocks are warped while centered at the origin, so a shared + // noise field would give every block the same dents. + const warpNoise = new UberNoise({ seed: shapeRng.seed(), scale: 1.5 / s, octaves: 2 }) + const jitterSeed = shapeRng.seed() + + let block = box({ size: [w, h, d] }) + .subdivide(1) + .displaceNoise(warpNoise, s * o.noise, { direction: 'radial' }) + .jitter(s * o.jitter, { seed: jitterSeed }) + .rotateY(yaw) + .rotateX(tiltX) + .rotateZ(tiltZ) + .translate(Math.cos(angle) * dist, h * (0.5 - o.sink), Math.sin(angle) * dist) + + const base = pickRandom(o.colors, colorRng) + const moss = hasMoss ? pickRandom(o.mossColors, mossRng) : null + // Painted overlay: snow sits on top of moss, so it wins the slot when both are set. + const overlay = snowNoise + ? { color: pickRandom(o.snowColors, snowRng), noise: snowNoise, threshold: snowThreshold, noiseAmount: 0.2 } + : moss && mossNoise + ? { color: moss, noise: mossNoise, threshold: mossThreshold, noiseAmount: 0.25 } + : undefined + block = block.faceColor(facetShade({ + base, + noise: colorNoise, + ambient: 0.5, + range: 0.5, + noiseAmount: 0.12, + snow: overlay, + })) + blocks.push(block) + } + + const result = merge(...blocks) + if (!useGeoSnow) return result + + return applySnow(result, { + depth: o.snowDepth, + minAngle: 90 - o.snowAngle, + color: pickRandom(o.snowColors, snowRng), + seed: snowRng.seed(), + }) +} diff --git a/src/generators/rocks/index.ts b/src/generators/rocks/index.ts index f71666e..d2ae20c 100644 --- a/src/generators/rocks/index.ts +++ b/src/generators/rocks/index.ts @@ -1 +1,3 @@ export { rock, rockSchema, rockPresets, type RockOptions } from './rock' +export { sharpRock, sharpRockSchema, sharpRockPresets, type SharpRockOptions } from './sharp-rock' +export { blockRock, blockRockSchema, blockRockPresets, type BlockRockOptions } from './block-rock' diff --git a/src/generators/rocks/rock.ts b/src/generators/rocks/rock.ts index a2d0518..52e18eb 100644 --- a/src/generators/rocks/rock.ts +++ b/src/generators/rocks/rock.ts @@ -1,4 +1,5 @@ import { setup, foliageBlob, facetShade } from '../../build' +import { snow as applySnow } from '../../ops' import { pickRandom } from '../../color' import { UberNoise } from '../../noise' import type { Mesh } from '../../core/mesh' @@ -14,6 +15,9 @@ export const rockSchema = { jitter: { type: 'range', default: 0.02, min: 0, max: 0.1, step: 0.005, label: 'Jitter' }, mossColors: { type: 'color-array', default: [], min: 0, max: 4, label: 'Moss Colors' }, mossAngle: { type: 'range', default: 40, min: 0, max: 80, step: 5, label: 'Moss Min Angle (°)' }, + snowColors: { type: 'color-array', default: [], min: 0, max: 6, label: 'Snow Colors' }, + snowAngle: { type: 'range', default: 35, min: 0, max: 80, step: 5, label: 'Snow Min Angle (°)' }, + snowDepth: { type: 'range', default: 0, min: 0, max: 0.3, step: 0.01, label: 'Snow Depth' }, colors: { type: 'color-array', default: ['#56514b', '#6a645c', '#7e776d'], min: 1, max: 6, label: 'Rock Colors' }, } satisfies OptionSchema @@ -25,6 +29,7 @@ export const rockPresets: Record> = { sharp: { noise: 0.7, detail: 0.3, squash: 1.0 }, mossy: { mossColors: ['#3f6a2a', '#4e7d33', '#5c8c3a'], noise: 0.4 }, slate: { squash: 0.45, flatten: 0.4, colors: ['#48504f', '#586160', '#6a7270'] }, + snowy: { snowColors: ['#eef0f5', '#e4e8f0', '#f4f6fb'], snowDepth: 0.06, snowAngle: 30 }, } export function rock(options: RockOptions = {}): Mesh { @@ -32,12 +37,18 @@ export function rock(options: RockOptions = {}): Mesh { const shapeRng = rng.stream('shape') const colorRng = rng.stream('color') const mossRng = rng.stream('moss') + const snowRng = rng.stream('snow') const colorNoise = new UberNoise({ seed: colorRng.seed(), scale: 2 }) const hasMoss = o.mossColors.length > 0 const mossNoise = hasMoss ? new UberNoise({ seed: mossRng.seed(), scale: 2.5 }) : null const mossThreshold = Math.sin((o.mossAngle * Math.PI) / 180) + const hasSnow = o.snowColors.length > 0 + const useGeoSnow = hasSnow && o.snowDepth > 0 + const snowNoise = hasSnow && !useGeoSnow ? new UberNoise({ seed: snowRng.seed(), scale: 2 }) : null + const snowThreshold = Math.sin((o.snowAngle * Math.PI) / 180) + // Lumpy faceted blob, flattened vertically. const blob = foliageBlob({ radius: o.size, @@ -55,7 +66,14 @@ export function rock(options: RockOptions = {}): Mesh { const base = pickRandom(o.colors, colorRng) const moss = hasMoss ? pickRandom(o.mossColors, mossRng) : null - return blob + // Painted overlay: snow sits on top of moss, so it wins the slot when both are set. + const overlay = snowNoise + ? { color: pickRandom(o.snowColors, snowRng), noise: snowNoise, threshold: snowThreshold, noiseAmount: 0.2 } + : moss && mossNoise + ? { color: moss, noise: mossNoise, threshold: mossThreshold, noiseAmount: 0.25 } + : undefined + + const shaped = blob .warp((p) => (p[1] < baseY ? [p[0], baseY, p[2]] : p)) .translate(0, -baseY, 0) .faceColor(facetShade({ @@ -64,8 +82,14 @@ export function rock(options: RockOptions = {}): Mesh { ambient: 0.5, range: 0.5, noiseAmount: 0.12, - snow: moss && mossNoise - ? { color: moss, noise: mossNoise, threshold: mossThreshold, noiseAmount: 0.25 } - : undefined, + snow: overlay, })) + if (!useGeoSnow) return shaped + + return applySnow(shaped, { + depth: o.snowDepth, + minAngle: 90 - o.snowAngle, + color: pickRandom(o.snowColors, snowRng), + seed: snowRng.seed(), + }) } diff --git a/src/generators/rocks/sharp-rock.ts b/src/generators/rocks/sharp-rock.ts new file mode 100644 index 0000000..65ac14a --- /dev/null +++ b/src/generators/rocks/sharp-rock.ts @@ -0,0 +1,103 @@ +import { setup, facetShade } from '../../build' +import { pickRandom } from '../../color' +import { UberNoise } from '../../noise' +import { cone } from '../../primitives' +import { merge, snow as applySnow } from '../../ops' +import type { Mesh } from '../../core/mesh' +import type { OptionSchema, OptionInput } from '../../core/schema' + +export const sharpRockSchema = { + seed: { type: 'integer', default: 1, min: 1, max: 100, label: 'Seed' }, + size: { type: 'range', default: 1.0, min: 0.3, max: 3, step: 0.05, label: 'Size' }, + shards: { type: 'integer', default: 5, min: 1, max: 12, label: 'Shards' }, + width: { type: 'range', default: 0.3, min: 0.1, max: 0.6, step: 0.05, label: 'Shard Width' }, + spread: { type: 'range', default: 0.45, min: 0, max: 1, step: 0.05, label: 'Spread' }, + tilt: { type: 'range', default: 22, min: 0, max: 50, step: 1, label: 'Tilt (°)' }, + jitter: { type: 'range', default: 0.05, min: 0, max: 0.15, step: 0.005, label: 'Jitter' }, + mossColors: { type: 'color-array', default: [], min: 0, max: 4, label: 'Moss Colors' }, + mossAngle: { type: 'range', default: 35, min: 0, max: 80, step: 5, label: 'Moss Min Angle (°)' }, + snowColors: { type: 'color-array', default: [], min: 0, max: 6, label: 'Snow Colors' }, + snowAngle: { type: 'range', default: 10, min: 0, max: 80, step: 5, label: 'Snow Min Angle (°)' }, + snowDepth: { type: 'range', default: 0, min: 0, max: 0.3, step: 0.01, label: 'Snow Depth' }, + colors: { type: 'color-array', default: ['#5b5650', '#6e6862', '#7d766c'], min: 1, max: 6, label: 'Rock Colors' }, +} satisfies OptionSchema + +export type SharpRockOptions = Partial> & { preset?: string } + +export const sharpRockPresets: Record> = { + default: {}, + spire: { shards: 3, width: 0.2, tilt: 8, spread: 0.25, size: 1.6 }, + shardfield: { shards: 9, spread: 0.85, tilt: 35, size: 0.8 }, + obsidian: { colors: ['#23262c', '#2e3138', '#3a3e47'], width: 0.25, tilt: 28 }, + mossy: { mossColors: ['#3f6a2a', '#4e7d33', '#5c8c3a'], mossAngle: 25 }, + snowy: { snowColors: ['#eef0f5', '#e4e8f0', '#f4f6fb'], snowDepth: 0.04, snowAngle: 12 }, +} + +export function sharpRock(options: SharpRockOptions = {}): Mesh { + const { o, rng } = setup(sharpRockSchema, options, sharpRockPresets) + const shapeRng = rng.stream('shape') + const colorRng = rng.stream('color') + const mossRng = rng.stream('moss') + const snowRng = rng.stream('snow') + + const colorNoise = new UberNoise({ seed: colorRng.seed(), scale: 2 }) + const hasMoss = o.mossColors.length > 0 + const mossNoise = hasMoss ? new UberNoise({ seed: mossRng.seed(), scale: 2.5 }) : null + const mossThreshold = Math.sin((o.mossAngle * Math.PI) / 180) + + const hasSnow = o.snowColors.length > 0 + const useGeoSnow = hasSnow && o.snowDepth > 0 + const snowNoise = hasSnow && !useGeoSnow ? new UberNoise({ seed: snowRng.seed(), scale: 2 }) : null + const snowThreshold = Math.sin((o.snowAngle * Math.PI) / 180) + + const shards: Mesh[] = [] + for (let i = 0; i < o.shards; i++) { + // Tallest shard at the center, the rest descend and lean outward. + const rank = i / Math.max(1, o.shards - 1) + const height = o.size * (1 - 0.55 * rank) * shapeRng.float(0.85, 1.15) + const radius = height * o.width * shapeRng.float(0.8, 1.25) + const angle = shapeRng.float(0, Math.PI * 2) + const dist = o.size * o.spread * rank + const lean = (o.tilt * Math.PI) / 180 * rank * shapeRng.float(0.5, 1) + const segments = shapeRng.int(4, 6) + const jitterSeed = shapeRng.seed() + + // Leaning lifts one side of the base — sink the shard so it stays grounded. + const sink = height * 0.05 + radius * Math.sin(lean) * 0.6 + + let shard = cone({ radius, height, segments }) + .subdivide(1) + .jitter(height * o.jitter, { seed: jitterSeed }) + .translate(0, height / 2, 0) + .rotate([Math.sin(angle), 0, -Math.cos(angle)], lean) + .translate(Math.cos(angle) * dist, -sink, Math.sin(angle) * dist) + + const base = pickRandom(o.colors, colorRng) + const moss = hasMoss ? pickRandom(o.mossColors, mossRng) : null + // Painted overlay: snow sits on top of moss, so it wins the slot when both are set. + const overlay = snowNoise + ? { color: pickRandom(o.snowColors, snowRng), noise: snowNoise, threshold: snowThreshold, noiseAmount: 0.2 } + : moss && mossNoise + ? { color: moss, noise: mossNoise, threshold: mossThreshold, noiseAmount: 0.25 } + : undefined + shard = shard.faceColor(facetShade({ + base, + noise: colorNoise, + ambient: 0.5, + range: 0.5, + noiseAmount: 0.12, + snow: overlay, + })) + shards.push(shard) + } + + const result = merge(...shards) + if (!useGeoSnow) return result + + return applySnow(result, { + depth: o.snowDepth, + minAngle: 90 - o.snowAngle, + color: pickRandom(o.snowColors, snowRng), + seed: snowRng.seed(), + }) +} diff --git a/src/generators/vegetation/trees/index.ts b/src/generators/vegetation/trees/index.ts index f1cf869..7d8fc43 100644 --- a/src/generators/vegetation/trees/index.ts +++ b/src/generators/vegetation/trees/index.ts @@ -2,3 +2,4 @@ export { tree, treeSchema, treePresets, type TreeOptions } from './common-tree' export { pine, pineSchema, pinePresets, type PineOptions } from './pine-tree' export { palm, palmSchema, palmPresets, type PalmOptions } from './palm-tree' export { deadTree, deadTreeSchema, deadTreePresets, type DeadTreeOptions } from './dead-tree' +export { leafyTree, leafyTreeSchema, leafyTreePresets, type LeafyTreeOptions } from './leafy-tree' diff --git a/src/generators/vegetation/trees/leafy-tree.ts b/src/generators/vegetation/trees/leafy-tree.ts new file mode 100644 index 0000000..b36e587 --- /dev/null +++ b/src/generators/vegetation/trees/leafy-tree.ts @@ -0,0 +1,127 @@ +import { merge, snow as applySnow } from '../../../ops' +import { setup, branches, foliageBlob, facetShade, heightShade, metaballs, type MetaBall } from '../../../build' +import { pickRandom } from '../../../color' +import { UberNoise } from '../../../noise' +import type { Mesh } from '../../../core/mesh' +import type { OptionSchema, OptionInput } from '../../../core/schema' + +export const leafyTreeSchema = { + seed: { type: 'integer', default: 1, min: 1, max: 100, label: 'Seed' }, + height: { type: 'range', default: 2.5, min: 1.2, max: 5, step: 0.1, label: 'Trunk Length' }, + trunkRadius: { type: 'range', default: 0.13, min: 0.05, max: 0.35, step: 0.01, label: 'Trunk Radius' }, + levels: { type: 'integer', default: 3, min: 2, max: 5, label: 'Branch Levels' }, + branches: { type: 'integer', default: 3, min: 2, max: 4, label: 'Splits' }, + spread: { type: 'range', default: 0.7, min: 0.3, max: 1.1, step: 0.05, label: 'Spread' }, + upBias: { type: 'range', default: 0.35, min: 0, max: 0.6, step: 0.02, label: 'Upward Bias' }, + wander: { type: 'range', default: 0.4, min: 0, max: 1, step: 0.05, label: 'Gnarl' }, + taper: { type: 'range', default: 0.5, min: 0.3, max: 1.2, step: 0.05, label: 'Taper' }, + leafSize: { type: 'range', default: 0.55, min: 0.2, max: 1.2, step: 0.05, label: 'Leaf Cluster Size' }, + blobCanopy: { type: 'boolean', default: false, label: 'Merge Canopy' }, + canopyNoise: { type: 'range', default: 0.5, min: 0, max: 1.2, step: 0.05, label: 'Canopy Noise' }, + jitter: { type: 'range', default: 0.05, min: 0, max: 0.15, step: 0.005, label: 'Jitter' }, + snowColors: { type: 'color-array', default: [], min: 0, max: 6, label: 'Snow Colors' }, + snowAngle: { type: 'range', default: 30, min: 0, max: 80, step: 5, label: 'Snow Min Angle (°)' }, + snowDepth: { type: 'range', default: 0, min: 0, max: 0.3, step: 0.01, label: 'Snow Depth' }, + trunkColors: { type: 'color-array', default: ['#241a0f', '#3f2c1a', '#574028'], min: 2, max: 6, label: 'Trunk Colors' }, + leafColors: { type: 'color-array', default: ['#2f6a1e', '#3a7d24', '#46902c', '#2a6a20'], min: 1, max: 8, label: 'Leaf Colors' }, +} satisfies OptionSchema + +export type LeafyTreeOptions = Partial> & { preset?: string } + +export const leafyTreePresets: Record> = { + default: {}, + autumn: { leafColors: ['#c4641e', '#d4882a', '#b5471a', '#a8651e', '#d9a531'] }, + cherry: { leafColors: ['#e58fb0', '#ec9fbb', '#d97a9e', '#f2b3c8'] }, + oak: { height: 2.0, spread: 0.9, leafSize: 0.7, branches: 4, levels: 4 }, + poplar: { height: 4, spread: 0.4, upBias: 0.55, leafSize: 0.45 }, + blob: { blobCanopy: true, leafSize: 0.55, canopyNoise: 0.7 }, + winter: { + leafColors: ['#2a5a1e', '#1f5018'], + snowColors: ['#eef0f5', '#e4e8f0', '#f4f6fb'], + snowDepth: 0.05, + snowAngle: 25, + }, +} + +export function leafyTree(options: LeafyTreeOptions = {}): Mesh { + const { o, rng } = setup(leafyTreeSchema, options, leafyTreePresets) + const leafRng = rng.stream('leaf') + const colorRng = rng.stream('color') + const snowRng = rng.stream('snow') + + // Bare branch skeleton. + const { mesh: limbMesh, tips } = branches({ + rng: rng.stream('branch'), + length: o.height, + radius: o.trunkRadius, + depth: o.levels, + children: o.branches, + spread: o.spread, + upBias: o.upBias, + wander: o.wander, + taper: o.taper, + }) + const treeTop = limbMesh.boundingBox.max.y || o.height + const trunk = limbMesh.vertexColor(heightShade(o.trunkColors, treeTop)) + + // Canopy: a foliage cluster at each branch tip. + const colorNoise = new UberNoise({ seed: colorRng.seed(), scale: 1.5 }) + const hasSnow = o.snowColors.length > 0 + const useGeoSnow = hasSnow && o.snowDepth > 0 + const snowNoise = hasSnow ? new UberNoise({ seed: snowRng.seed(), scale: 2 }) : null + const snowThreshold = Math.sin((o.snowAngle * Math.PI) / 180) + + const snowOpt = (hasSnow && !useGeoSnow && snowNoise) + ? { noise: snowNoise, threshold: snowThreshold, noiseAmount: 0.15 } + : null + const shadeFor = (b: [number, number, number]) => facetShade({ + base: b, + noise: colorNoise, + ambient: 0.6, + range: 0.4, + noiseAmount: 0.15, + snow: snowOpt ? { color: pickRandom(o.snowColors, snowRng), ...snowOpt } : undefined, + }) + + const parts: Mesh[] = [trunk] + + if (o.blobCanopy) { + // Merge all leaf clusters into one melted-blob canopy via a metaball isosurface. + const balls: MetaBall[] = tips.map((tip) => ({ + center: tip.position, + radius: o.leafSize * (0.7 + leafRng() * 0.6), + })) + parts.push( + metaballs(balls, { resolution: 26, isolevel: 0.5, support: 2.0 }) + .jitter(o.leafSize * o.jitter, { seed: leafRng.seed() }) + .faceColor(shadeFor(pickRandom(o.leafColors, colorRng))), + ) + } else { + // A separate foliage cluster at each branch tip. + for (const tip of tips) { + const r = o.leafSize * (0.7 + leafRng() * 0.6) + parts.push( + foliageBlob({ + radius: r, + detail: r * 0.7, + noiseSeed: leafRng.seed(), + noiseAmount: o.canopyNoise, + jitter: r * o.jitter, + jitterSeed: leafRng.seed(), + }) + .translate(tip.position[0], tip.position[1], tip.position[2]) + .faceColor(shadeFor(pickRandom(o.leafColors, colorRng))), + ) + } + } + + const result = merge(...parts) + if (!useGeoSnow) return result + + return applySnow(result, { + depth: o.snowDepth, + minAngle: 90 - o.snowAngle, + color: pickRandom(o.snowColors, snowRng), + seed: snowRng.seed(), + }) +} diff --git a/src/index.ts b/src/index.ts index 203c1a5..b3f0858 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,8 +26,8 @@ export type { Rng } from './core/rng' export { scatterOnSphere } from './core/scatter' // Build — composable model primitives -export { setup, trunk, foliageBlob, facetShade, heightShade, scatterOnSurface, blade, branches } from './build' -export type { TrunkOptions, FoliageBlobOptions, FacetShadeOptions, SurfacePoint, ScatterOnSurfaceOptions, BladeOptions, BranchesOptions, BranchResult, BranchTip } from './build' +export { setup, trunk, foliageBlob, facetShade, heightShade, scatterOnSurface, blade, branches, metaballs, surfaceNets } from './build' +export type { TrunkOptions, FoliageBlobOptions, FacetShadeOptions, SurfacePoint, ScatterOnSurfaceOptions, BladeOptions, BranchesOptions, BranchResult, BranchTip, MetaBall, MetaballsOptions, SurfaceNetsOptions } from './build' // Schema & options export { resolveOptions } from './core/schema' @@ -42,4 +42,7 @@ export { grass, grassSchema, grassPresets } from './generators' export { fern, fernSchema, fernPresets } from './generators' export { flower, flowerSchema, flowerPresets } from './generators' export { deadTree, deadTreeSchema, deadTreePresets } from './generators' +export { leafyTree, leafyTreeSchema, leafyTreePresets } from './generators' export { rock, rockSchema, rockPresets } from './generators' +export { sharpRock, sharpRockSchema, sharpRockPresets } from './generators' +export { blockRock, blockRockSchema, blockRockPresets } from './generators' diff --git a/tests/build.test.ts b/tests/build.test.ts index 7c826d3..a5817a0 100644 --- a/tests/build.test.ts +++ b/tests/build.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -import { setup, trunk, foliageBlob, facetShade, heightShade, scatterOnSurface, blade, branches } from '../src/build' +import { setup, trunk, foliageBlob, facetShade, heightShade, scatterOnSurface, blade, branches, metaballs, surfaceNets } from '../src/build' import { sphere, box, plane } from '../src/primitives' import type { OptionSchema } from '../src/core/schema' import type { Vec3 } from '../src/core/types' @@ -168,6 +168,55 @@ describe('branches', () => { }) }) +describe('surfaceNets', () => { + it('meshes a sphere SDF to roughly a unit sphere', () => { + const m = surfaceNets((x, y, z) => x * x + y * y + z * z, { + min: [-1.5, -1.5, -1.5], max: [1.5, 1.5, 1.5], resolution: 24, iso: 1, inside: 'below', + }) + expect(m.vertexCount).toBeGreaterThan(0) + const bb = m.boundingBox + expect(bb.max.x).toBeGreaterThan(0.85) + expect(bb.max.x).toBeLessThan(1.15) + expect(bb.min.y).toBeLessThan(-0.85) + }) + + it('returns an empty mesh when the field never crosses the isolevel', () => { + const m = surfaceNets(() => 5, { min: [-1, -1, -1], max: [1, 1, 1], iso: 1, inside: 'below' }) + expect(m.vertexCount).toBe(0) + }) +}) + +describe('metaballs', () => { + it('returns an empty mesh for no balls', () => { + expect(metaballs([]).vertexCount).toBe(0) + }) + + it('a single ball produces a closed blob around its radius', () => { + const m = metaballs([{ center: [0, 0, 0], radius: 1 }], { resolution: 24 }) + expect(m.vertexCount).toBeGreaterThan(0) + const bb = m.boundingBox + expect(bb.max.x).toBeGreaterThan(0.6) + expect(bb.max.x).toBeLessThan(1.6) + }) + + it('two nearby balls merge into one surface spanning both', () => { + const m = metaballs( + [{ center: [-0.8, 0, 0], radius: 0.8 }, { center: [0.8, 0, 0], radius: 0.8 }], + { resolution: 28 }, + ) + const bb = m.boundingBox + expect(bb.min.x).toBeLessThan(-0.8) + expect(bb.max.x).toBeGreaterThan(0.8) + }) + + it('is deterministic', () => { + const balls = [{ center: [0, 0, 0] as Vec3, radius: 1 }, { center: [0.5, 1, 0] as Vec3, radius: 0.7 }] + const a = metaballs(balls).positions + const b = metaballs(balls).positions + expect(Array.from(a)).toEqual(Array.from(b)) + }) +}) + describe('scatterOnSurface', () => { it('returns the requested number of points with unit normals', () => { const pts = scatterOnSurface(sphere({ radius: 1 }), 20, { seed: 1 }) diff --git a/tests/generators.test.ts b/tests/generators.test.ts index 2334418..9144b88 100644 --- a/tests/generators.test.ts +++ b/tests/generators.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -import { tree, pine, palm, bush, grass, fern, flower, deadTree, rock } from '../src/generators' +import { tree, pine, palm, bush, grass, fern, flower, deadTree, leafyTree, rock, sharpRock, blockRock } from '../src/generators' import type { Mesh } from '../src/core/mesh' const generators = [ @@ -11,11 +11,14 @@ const generators = [ { name: 'fern', gen: fern }, { name: 'flower', gen: flower }, { name: 'dead', gen: deadTree }, + { name: 'leafy', gen: leafyTree }, { name: 'rock', gen: rock }, + { name: 'sharp', gen: sharpRock }, + { name: 'block', gen: blockRock }, ] as const -// Generators that support snow (trees + shrubs). Grass/ferns don't take snow options. -const snowGenerators = generators.filter((g) => ['tree', 'pine', 'palm', 'bush'].includes(g.name)) +// Generators that support snow (trees, shrubs, rocks). Grass/ferns don't take snow options. +const snowGenerators = generators.filter((g) => ['tree', 'pine', 'palm', 'bush', 'leafy', 'rock', 'sharp', 'block'].includes(g.name)) // Golden snapshots captured after the named-stream RNG migration. A change here means // generator output shifted — intentional changes should update these numbers deliberately. @@ -28,7 +31,10 @@ const golden: Record = { fern: { verts: 6480 }, flower: { verts: 654 }, dead: { verts: 1400 }, + leafy: { verts: 7020 }, rock: { verts: 1440 }, + sharp: { verts: 696 }, + block: { verts: 432 }, } function allFinite(m: Mesh): boolean { diff --git a/webapp/.gitignore b/webapp/.gitignore new file mode 100644 index 0000000..41f1bdc --- /dev/null +++ b/webapp/.gitignore @@ -0,0 +1,25 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +# Playwright +test-results diff --git a/webapp/.npmrc b/webapp/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/webapp/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/webapp/.prettierignore b/webapp/.prettierignore new file mode 100644 index 0000000..7d74fe2 --- /dev/null +++ b/webapp/.prettierignore @@ -0,0 +1,9 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Miscellaneous +/static/ diff --git a/webapp/.prettierrc b/webapp/.prettierrc new file mode 100644 index 0000000..819fa57 --- /dev/null +++ b/webapp/.prettierrc @@ -0,0 +1,16 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ], + "tailwindStylesheet": "./src/routes/layout.css" +} diff --git a/webapp/.vscode/extensions.json b/webapp/.vscode/extensions.json new file mode 100644 index 0000000..175f389 --- /dev/null +++ b/webapp/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "svelte.svelte-vscode", + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "bradlc.vscode-tailwindcss" + ] +} diff --git a/webapp/.vscode/settings.json b/webapp/.vscode/settings.json new file mode 100644 index 0000000..bc31e15 --- /dev/null +++ b/webapp/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "*.css": "tailwindcss" + } +} diff --git a/webapp/README.md b/webapp/README.md new file mode 100644 index 0000000..1cb7741 --- /dev/null +++ b/webapp/README.md @@ -0,0 +1,42 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```sh +# create a new project +npx sv create my-app +``` + +To recreate this project with the same configuration: + +```sh +# recreate this project +pnpm dlx sv@0.16.1 create --template minimal --types ts --add prettier eslint vitest="usages:unit,component" playwright tailwindcss="plugins:typography,forms" --install pnpm webapp +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```sh +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```sh +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/webapp/eslint.config.js b/webapp/eslint.config.js new file mode 100644 index 0000000..ed35999 --- /dev/null +++ b/webapp/eslint.config.js @@ -0,0 +1,41 @@ +import prettier from 'eslint-config-prettier'; +import path from 'node:path'; +import js from '@eslint/js'; +import svelte from 'eslint-plugin-svelte'; +import { defineConfig, includeIgnoreFile } from 'eslint/config'; +import globals from 'globals'; +import ts from 'typescript-eslint'; + +const gitignorePath = path.resolve(import.meta.dirname, '.gitignore'); + +export default defineConfig( + includeIgnoreFile(gitignorePath), + js.configs.recommended, + ts.configs.recommended, + svelte.configs.recommended, + prettier, + svelte.configs.prettier, + { + languageOptions: { globals: { ...globals.browser, ...globals.node } }, + rules: { + // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. + // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors + 'no-undef': 'off' + } + }, + { + files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], + languageOptions: { + parserOptions: { + projectService: true, + extraFileExtensions: ['.svelte'], + parser: ts.parser + } + } + }, + { + // Override or add rule settings here, such as: + // 'svelte/button-has-type': 'error' + rules: {} + } +); diff --git a/webapp/package.json b/webapp/package.json new file mode 100644 index 0000000..eeed952 --- /dev/null +++ b/webapp/package.json @@ -0,0 +1,47 @@ +{ + "name": "webapp", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "prettier --check . && eslint .", + "format": "prettier --write .", + "test:unit": "vitest", + "test": "npm run test:unit -- --run && npm run test:e2e", + "test:e2e": "playwright install && playwright test" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@playwright/test": "^1.60.0", + "@sveltejs/adapter-auto": "^7.0.1", + "@sveltejs/kit": "^2.63.0", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tailwindcss/forms": "^0.5.11", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.3.0", + "@types/node": "^22", + "@vitest/browser-playwright": "^4.1.8", + "eslint": "^10.4.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-svelte": "^3.19.0", + "globals": "^17.6.0", + "playwright": "^1.60.0", + "prettier": "^3.8.3", + "prettier-plugin-svelte": "^4.1.0", + "prettier-plugin-tailwindcss": "^0.8.0", + "svelte": "^5.56.1", + "svelte-check": "^4.6.0", + "tailwindcss": "^4.3.0", + "typescript": "^6.0.3", + "typescript-eslint": "^8.60.1", + "vite": "^8.0.16", + "vitest": "^4.1.8", + "vitest-browser-svelte": "^2.1.1" + } +} diff --git a/webapp/playwright.config.ts b/webapp/playwright.config.ts new file mode 100644 index 0000000..9eea31b --- /dev/null +++ b/webapp/playwright.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + webServer: { command: 'npm run build && npm run preview', port: 4173 }, + testMatch: '**/*.e2e.{ts,js}' +}); diff --git a/webapp/pnpm-lock.yaml b/webapp/pnpm-lock.yaml new file mode 100644 index 0000000..388e014 --- /dev/null +++ b/webapp/pnpm-lock.yaml @@ -0,0 +1,2491 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.4.1(jiti@2.7.0)) + '@playwright/test': + specifier: ^1.60.0 + version: 1.60.0 + '@sveltejs/adapter-auto': + specifier: ^7.0.1 + version: 7.0.1(@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0))) + '@sveltejs/kit': + specifier: ^2.63.0 + version: 2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)) + '@sveltejs/vite-plugin-svelte': + specifier: ^7.1.2 + version: 7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)) + '@tailwindcss/forms': + specifier: ^0.5.11 + version: 0.5.11(tailwindcss@4.3.0) + '@tailwindcss/typography': + specifier: ^0.5.19 + version: 0.5.20(tailwindcss@4.3.0) + '@tailwindcss/vite': + specifier: ^4.3.0 + version: 4.3.0(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)) + '@types/node': + specifier: ^22 + version: 22.19.20 + '@vitest/browser-playwright': + specifier: ^4.1.8 + version: 4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0))(vitest@4.1.8) + eslint: + specifier: ^10.4.1 + version: 10.4.1(jiti@2.7.0) + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@10.4.1(jiti@2.7.0)) + eslint-plugin-svelte: + specifier: ^3.19.0 + version: 3.19.0(eslint@10.4.1(jiti@2.7.0))(svelte@5.56.3(@typescript-eslint/types@8.61.0)) + globals: + specifier: ^17.6.0 + version: 17.6.0 + playwright: + specifier: ^1.60.0 + version: 1.60.0 + prettier: + specifier: ^3.8.3 + version: 3.8.4 + prettier-plugin-svelte: + specifier: ^4.1.0 + version: 4.1.0(prettier@3.8.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0)) + prettier-plugin-tailwindcss: + specifier: ^0.8.0 + version: 0.8.0(prettier-plugin-svelte@4.1.0(prettier@3.8.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0)))(prettier@3.8.4) + svelte: + specifier: ^5.56.1 + version: 5.56.3(@typescript-eslint/types@8.61.0) + svelte-check: + specifier: ^4.6.0 + version: 4.6.0(picomatch@4.0.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3) + tailwindcss: + specifier: ^4.3.0 + version: 4.3.0 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + typescript-eslint: + specifier: ^8.60.1 + version: 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + vite: + specifier: ^8.0.16 + version: 8.0.16(@types/node@22.19.20)(jiti@2.7.0) + vitest: + specifier: ^4.1.8 + version: 4.1.8(@types/node@22.19.20)(@vitest/browser-playwright@4.1.8)(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)) + vitest-browser-svelte: + specifier: ^2.1.1 + version: 2.1.1(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vitest@4.1.8) + +packages: + + '@blazediff/core@1.9.1': + resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + + '@playwright/test@1.60.0': + resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} + engines: {node: '>=18'} + hasBin: true + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@sveltejs/acorn-typescript@1.0.10': + resolution: {integrity: sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/adapter-auto@7.0.1': + resolution: {integrity: sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + + '@sveltejs/kit@2.64.0': + resolution: {integrity: sha512-24MXA/xyugUqJd09DD4v4bLd4k7FaYhdBck/pTXiu3XFtDBcwXYpJc1SO1+lqdjkNBUo1r4eek5FS7cfzBQUmA==} + engines: {node: '>=18.13'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.0.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: ^5.3.3 || ^6.0.0 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + typescript: + optional: true + + '@sveltejs/load-config@0.1.1': + resolution: {integrity: sha512-BXXm+VOH/9X4N7Dd1iZ2MqA1h7M+9i2noI8QYuLDY8QcN2WHYn7D/VK/+IJNfcAmRw7ACNJ538UT9GXIhnBTiA==} + engines: {node: '>= 18.0.0'} + + '@sveltejs/vite-plugin-svelte@7.1.2': + resolution: {integrity: sha512-DrUBA2UXRfDmUX/ZTiEopd3X40yavsJF1FX2RygcuIScHL7o5YX1fMvoYnDhjeJQC4weCOklirpNWlcb2NiSeA==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + svelte: ^5.46.4 + vite: ^8.0.0-beta.7 || ^8.0.0 + + '@tailwindcss/forms@0.5.11': + resolution: {integrity: sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==} + peerDependencies: + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1' + + '@tailwindcss/node@4.3.0': + resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} + + '@tailwindcss/oxide-android-arm64@4.3.0': + resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.0': + resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.0': + resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.0': + resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.0': + resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==} + engines: {node: '>= 20'} + + '@tailwindcss/typography@0.5.20': + resolution: {integrity: sha512-hwbzQuNUfcPvbegQFatVPl/MY/tcM9KLl963hQ5laJKPh81TEZ1+dNG9PirGvcaDBkp+BCshExAyKVPW91dozw==} + peerDependencies: + tailwindcss: '>=3.0.0 || >=4.0.0 || insiders' + + '@tailwindcss/vite@4.3.0': + resolution: {integrity: sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@testing-library/svelte-core@1.0.0': + resolution: {integrity: sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ==} + engines: {node: '>=16'} + peerDependencies: + svelte: ^3 || ^4 || ^5 || ^5.0.0-next.0 + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@22.19.20': + resolution: {integrity: sha512-6tELRwSDYWW9EdZhbeZmYGZ1/7Djkt+Ah3/ScEYT9cDord7UJzasR/4D3VONg9tQI5CDp+/CZC1AXj2pCFOvpw==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@typescript-eslint/eslint-plugin@8.61.0': + resolution: {integrity: sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.61.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.61.0': + resolution: {integrity: sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.61.0': + resolution: {integrity: sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.61.0': + resolution: {integrity: sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.61.0': + resolution: {integrity: sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.61.0': + resolution: {integrity: sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.61.0': + resolution: {integrity: sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.61.0': + resolution: {integrity: sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.61.0': + resolution: {integrity: sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.61.0': + resolution: {integrity: sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitest/browser-playwright@4.1.8': + resolution: {integrity: sha512-SR7FqgegaexEg73xvf3ArtygXegagMdXnL0EZMpxrWvvhQxvicD/E8p0ib0J91riPRtQUViyh67Xjw3NqvyhVg==} + peerDependencies: + playwright: '*' + vitest: 4.1.8 + + '@vitest/browser@4.1.8': + resolution: {integrity: sha512-u21VzX07HzlJYpFgkxmjEXar/tG2UqWGgyGG/46SrrPc7rSdCTPw5vuowopO9CIqF8UCUQzDFdbVnNpw6N0BfQ==} + peerDependencies: + vitest: 4.1.8 + + '@vitest/expect@4.1.8': + resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==} + + '@vitest/mocker@4.1.8': + resolution: {integrity: sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.8': + resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==} + + '@vitest/runner@4.1.8': + resolution: {integrity: sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==} + + '@vitest/snapshot@4.1.8': + resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==} + + '@vitest/spy@4.1.8': + resolution: {integrity: sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==} + + '@vitest/utils@4.1.8': + resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + aria-query@5.3.1: + resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devalue@5.8.1: + resolution: {integrity: sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==} + + enhanced-resolve@5.23.0: + resolution: {integrity: sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==} + engines: {node: '>=10.13.0'} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-svelte@3.19.0: + resolution: {integrity: sha512-t3rNaZeXz4d2gG4uJyMEYfJCFKf22+SWbSizIIXIWKu4wM+XPLiMWuSSr/C5821JmFeN9ogK+eExbG+z+twyxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.1 || ^9.0.0 || ^10.0.0 + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.4.1: + resolution: {integrity: sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrap@2.2.11: + resolution: {integrity: sha512-gPdx+I+BjYEinNMQaBXFjbaJVyoPMU4ZODg5mE+M4DqVG9VusAVHHjcBX+zqyITlI0DIARwDMMzZwAWj36dRoQ==} + peerDependencies: + '@typescript-eslint/types': ^8.2.0 + peerDependenciesMeta: + '@typescript-eslint/types': + optional: true + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + globals@17.6.0: + resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} + engines: {node: '>=18'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mini-svg-data-uri@1.4.4: + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} + hasBin: true + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + obug@2.1.2: + resolution: {integrity: sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==} + engines: {node: '>=12.20.0'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + playwright-core@1.60.0: + resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.60.0: + resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} + engines: {node: '>=18'} + hasBin: true + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + + postcss-load-config@3.1.4: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.4.31 + + postcss-scss@4.0.9: + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + + postcss-selector-parser@7.1.2: + resolution: {integrity: sha512-Wjvt4scRFouioIInHf51IFNP4ltJ2EngJM+cZPGiqbKetBfmP3vpdPV8ID2S6JS6/jdo74N8+aEYH9lQr2C6sA==} + engines: {node: '>=4'} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-svelte@4.1.0: + resolution: {integrity: sha512-YZkhA2Q9oOerFFG9tq+2f98WYT7Z2JgrybJrAyrB78jpsH9i/DdgplXemehuFPgsldetFNCcR/yCcYlDjPy94Q==} + engines: {node: '>=20'} + peerDependencies: + prettier: ^3.0.0 + svelte: ^5.0.0 + + prettier-plugin-tailwindcss@0.8.0: + resolution: {integrity: sha512-V8ITGH87yuBDF6JpEZTOVlUz/saAwqb8f3HRgUj8Lh+tGCcrmorhsLpYqzygwFwK0PE2Ib6Mv3M7T/uE2tZV1g==} + engines: {node: '>=20.19'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-hermes': '*' + '@prettier/plugin-oxc': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-hermes': + optional: true + '@prettier/plugin-oxc': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-svelte: + optional: true + + prettier@3.8.4: + resolution: {integrity: sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + semver@7.8.3: + resolution: {integrity: sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@3.1.0: + resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + svelte-check@4.6.0: + resolution: {integrity: sha512-KhVnDFDSid57mmZtHz8gfW8AAGylOZ0vPnOIzVmAL+urzwK8sBYXRss953gD8T0OdgAQ11mdWhE6uadmtOz8TQ==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' + + svelte-eslint-parser@1.8.0: + resolution: {integrity: sha512-mikR1qwIVy3t5WthUoAXkMwxkXvabZP9FJgdx35Ei7EbGWmctva1Pih16Koeor/bdNNq8NXHlwKGS6NkYTawLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.34.1} + peerDependencies: + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true + + svelte@5.56.3: + resolution: {integrity: sha512-w7JvrM5IFl5cmfbY0TLik9o7mjRUJmRMhOR51tBPu708Gr/MjbGs7VnJnr/B0CaXeI4vtnOh7RKxDr0cwhMdDA==} + engines: {node: '>=18'} + + tailwindcss@4.3.0: + resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.61.0: + resolution: {integrity: sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + + vitest-browser-svelte@2.1.1: + resolution: {integrity: sha512-qbunYRSm+N92r9bfTkdDTpBZESLmp4QFz2SluV3n/x8U7ysosfeXYJZ4vXbJ0Y0LzoqqDnV5LHprmFgn4Eo+Ug==} + peerDependencies: + svelte: ^3 || ^4 || ^5 || ^5.0.0-next.0 + vitest: ^4.0.0 + + vitest@4.1.8: + resolution: {integrity: sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.8 + '@vitest/browser-preview': 4.1.8 + '@vitest/browser-webdriverio': 4.1.8 + '@vitest/coverage-istanbul': 4.1.8 + '@vitest/coverage-v8': 4.1.8 + '@vitest/ui': 4.1.8 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yaml@1.10.3: + resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} + engines: {node: '>= 6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} + +snapshots: + + '@blazediff/core@1.9.1': {} + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.1(jiti@2.7.0))': + dependencies: + eslint: 10.4.1(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.6.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.4.1(jiti@2.7.0))': + optionalDependencies: + eslint: 10.4.1(jiti@2.7.0) + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.2': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@oxc-project/types@0.133.0': {} + + '@playwright/test@1.60.0': + dependencies: + playwright: 1.60.0 + + '@polka/url@1.0.0-next.29': {} + + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@sveltejs/acorn-typescript@1.0.10(acorn@8.16.0)': + dependencies: + acorn: 8.16.0 + + '@sveltejs/adapter-auto@7.0.1(@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)))': + dependencies: + '@sveltejs/kit': 2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)) + + '@sveltejs/kit@2.64.0(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0))': + dependencies: + '@standard-schema/spec': 1.1.0 + '@sveltejs/acorn-typescript': 1.0.10(acorn@8.16.0) + '@sveltejs/vite-plugin-svelte': 7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)) + '@types/cookie': 0.6.0 + acorn: 8.16.0 + cookie: 0.6.0 + devalue: 5.8.1 + esm-env: 1.2.2 + kleur: 4.1.5 + magic-string: 0.30.21 + mrmime: 2.0.1 + set-cookie-parser: 3.1.0 + sirv: 3.0.2 + svelte: 5.56.3(@typescript-eslint/types@8.61.0) + vite: 8.0.16(@types/node@22.19.20)(jiti@2.7.0) + optionalDependencies: + typescript: 6.0.3 + + '@sveltejs/load-config@0.1.1': {} + + '@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0))': + dependencies: + deepmerge: 4.3.1 + magic-string: 0.30.21 + obug: 2.1.2 + svelte: 5.56.3(@typescript-eslint/types@8.61.0) + vite: 8.0.16(@types/node@22.19.20)(jiti@2.7.0) + vitefu: 1.1.3(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)) + + '@tailwindcss/forms@0.5.11(tailwindcss@4.3.0)': + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 4.3.0 + + '@tailwindcss/node@4.3.0': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.23.0 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.0 + + '@tailwindcss/oxide-android-arm64@4.3.0': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.0': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.0': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + optional: true + + '@tailwindcss/oxide@4.3.0': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-x64': 4.3.0 + '@tailwindcss/oxide-freebsd-x64': 4.3.0 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.0 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-x64-musl': 4.3.0 + '@tailwindcss/oxide-wasm32-wasi': 4.3.0 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 + + '@tailwindcss/typography@0.5.20(tailwindcss@4.3.0)': + dependencies: + postcss-selector-parser: 6.0.10 + tailwindcss: 4.3.0 + + '@tailwindcss/vite@4.3.0(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0))': + dependencies: + '@tailwindcss/node': 4.3.0 + '@tailwindcss/oxide': 4.3.0 + tailwindcss: 4.3.0 + vite: 8.0.16(@types/node@22.19.20)(jiti@2.7.0) + + '@testing-library/svelte-core@1.0.0(svelte@5.56.3(@typescript-eslint/types@8.61.0))': + dependencies: + svelte: 5.56.3(@typescript-eslint/types@8.61.0) + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/cookie@0.6.0': {} + + '@types/deep-eql@4.0.2': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@22.19.20': + dependencies: + undici-types: 6.21.0 + + '@types/trusted-types@2.0.7': {} + + '@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.61.0 + '@typescript-eslint/type-utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.61.0 + eslint: 10.4.1(jiti@2.7.0) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.61.0 + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.61.0 + debug: 4.4.3 + eslint: 10.4.1(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.61.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@6.0.3) + '@typescript-eslint/types': 8.61.0 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.61.0': + dependencies: + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/visitor-keys': 8.61.0 + + '@typescript-eslint/tsconfig-utils@8.61.0(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + debug: 4.4.3 + eslint: 10.4.1(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.61.0': {} + + '@typescript-eslint/typescript-estree@8.61.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.61.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@6.0.3) + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/visitor-keys': 8.61.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.3 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.61.0 + '@typescript-eslint/types': 8.61.0 + '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3) + eslint: 10.4.1(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.61.0': + dependencies: + '@typescript-eslint/types': 8.61.0 + eslint-visitor-keys: 5.0.1 + + '@vitest/browser-playwright@4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0))(vitest@4.1.8)': + dependencies: + '@vitest/browser': 4.1.8(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0))(vitest@4.1.8) + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)) + playwright: 1.60.0 + tinyrainbow: 3.1.0 + vitest: 4.1.8(@types/node@22.19.20)(@vitest/browser-playwright@4.1.8)(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@4.1.8(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0))(vitest@4.1.8)': + dependencies: + '@blazediff/core': 1.9.1 + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)) + '@vitest/utils': 4.1.8 + magic-string: 0.30.21 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.0 + vitest: 4.1.8(@types/node@22.19.20)(@vitest/browser-playwright@4.1.8)(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)) + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/expect@4.1.8': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.8 + '@vitest/utils': 4.1.8 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0))': + dependencies: + '@vitest/spy': 4.1.8 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.16(@types/node@22.19.20)(jiti@2.7.0) + + '@vitest/pretty-format@4.1.8': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.8': + dependencies: + '@vitest/utils': 4.1.8 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.8': + dependencies: + '@vitest/pretty-format': 4.1.8 + '@vitest/utils': 4.1.8 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.8': {} + + '@vitest/utils@4.1.8': + dependencies: + '@vitest/pretty-format': 4.1.8 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + aria-query@5.3.1: {} + + assertion-error@2.0.1: {} + + axobject-query@4.1.0: {} + + balanced-match@4.0.4: {} + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + chai@6.2.2: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + clsx@2.1.1: {} + + convert-source-map@2.0.0: {} + + cookie@0.6.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + detect-libc@2.1.2: {} + + devalue@5.8.1: {} + + enhanced-resolve@5.23.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + es-module-lexer@2.1.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.8(eslint@10.4.1(jiti@2.7.0)): + dependencies: + eslint: 10.4.1(jiti@2.7.0) + + eslint-plugin-svelte@3.19.0(eslint@10.4.1(jiti@2.7.0))(svelte@5.56.3(@typescript-eslint/types@8.61.0)): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@jridgewell/sourcemap-codec': 1.5.5 + eslint: 10.4.1(jiti@2.7.0) + esutils: 2.0.3 + globals: 16.5.0 + known-css-properties: 0.37.0 + postcss: 8.5.15 + postcss-load-config: 3.1.4(postcss@8.5.15) + postcss-safe-parser: 7.0.1(postcss@8.5.15) + semver: 7.8.3 + svelte-eslint-parser: 1.8.0(svelte@5.56.3(@typescript-eslint/types@8.61.0)) + optionalDependencies: + svelte: 5.56.3(@typescript-eslint/types@8.61.0) + transitivePeerDependencies: + - ts-node + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.4.1(jiti@2.7.0): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.6.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.2 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 + transitivePeerDependencies: + - supports-color + + esm-env@1.2.2: {} + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrap@2.2.11(@typescript-eslint/types@8.61.0): + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + optionalDependencies: + '@typescript-eslint/types': 8.61.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + esutils@2.0.3: {} + + expect-type@1.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@16.5.0: {} + + globals@17.6.0: {} + + graceful-fs@4.2.11: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + isexe@2.0.0: {} + + jiti@2.7.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@4.1.5: {} + + known-css-properties@0.37.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lilconfig@2.1.0: {} + + locate-character@3.0.0: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mini-svg-data-uri@1.4.4: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + mri@1.2.0: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.12: {} + + natural-compare@1.4.0: {} + + obug@2.1.2: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + playwright-core@1.60.0: {} + + playwright@1.60.0: + dependencies: + playwright-core: 1.60.0 + optionalDependencies: + fsevents: 2.3.2 + + pngjs@7.0.0: {} + + postcss-load-config@3.1.4(postcss@8.5.15): + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.3 + optionalDependencies: + postcss: 8.5.15 + + postcss-safe-parser@7.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + + postcss-scss@4.0.9(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@7.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-plugin-svelte@4.1.0(prettier@3.8.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0)): + dependencies: + prettier: 3.8.4 + svelte: 5.56.3(@typescript-eslint/types@8.61.0) + + prettier-plugin-tailwindcss@0.8.0(prettier-plugin-svelte@4.1.0(prettier@3.8.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0)))(prettier@3.8.4): + dependencies: + prettier: 3.8.4 + optionalDependencies: + prettier-plugin-svelte: 4.1.0(prettier@3.8.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0)) + + prettier@3.8.4: {} + + punycode@2.3.1: {} + + readdirp@4.1.2: {} + + rolldown@1.0.3: + dependencies: + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + semver@7.8.3: {} + + set-cookie-parser@3.1.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@4.1.0: {} + + svelte-check@4.6.0(picomatch@4.0.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@sveltejs/load-config': 0.1.1 + chokidar: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picocolors: 1.1.1 + sade: 1.8.1 + svelte: 5.56.3(@typescript-eslint/types@8.61.0) + typescript: 6.0.3 + transitivePeerDependencies: + - picomatch + + svelte-eslint-parser@1.8.0(svelte@5.56.3(@typescript-eslint/types@8.61.0)): + dependencies: + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + postcss: 8.5.15 + postcss-scss: 4.0.9(postcss@8.5.15) + postcss-selector-parser: 7.1.2 + semver: 7.8.3 + optionalDependencies: + svelte: 5.56.3(@typescript-eslint/types@8.61.0) + + svelte@5.56.3(@typescript-eslint/types@8.61.0): + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + '@sveltejs/acorn-typescript': 1.0.10(acorn@8.16.0) + '@types/estree': 1.0.9 + '@types/trusted-types': 2.0.7 + acorn: 8.16.0 + aria-query: 5.3.1 + axobject-query: 4.1.0 + clsx: 2.1.1 + devalue: 5.8.1 + esm-env: 1.2.2 + esrap: 2.2.11(@typescript-eslint/types@8.61.0) + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.21 + zimmerframe: 1.1.4 + transitivePeerDependencies: + - '@typescript-eslint/types' + + tailwindcss@4.3.0: {} + + tapable@2.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@1.2.4: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + + totalist@3.0.1: {} + + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + tslib@2.8.1: + optional: true + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.4.1(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + typescript@6.0.3: {} + + undici-types@6.21.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 22.19.20 + fsevents: 2.3.3 + jiti: 2.7.0 + + vitefu@1.1.3(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)): + optionalDependencies: + vite: 8.0.16(@types/node@22.19.20)(jiti@2.7.0) + + vitest-browser-svelte@2.1.1(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vitest@4.1.8): + dependencies: + '@testing-library/svelte-core': 1.0.0(svelte@5.56.3(@typescript-eslint/types@8.61.0)) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) + vitest: 4.1.8(@types/node@22.19.20)(@vitest/browser-playwright@4.1.8)(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)) + + vitest@4.1.8(@types/node@22.19.20)(@vitest/browser-playwright@4.1.8)(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)): + dependencies: + '@vitest/expect': 4.1.8 + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0)) + '@vitest/pretty-format': 4.1.8 + '@vitest/runner': 4.1.8 + '@vitest/snapshot': 4.1.8 + '@vitest/spy': 4.1.8 + '@vitest/utils': 4.1.8 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.2 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.0.16(@types/node@22.19.20)(jiti@2.7.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.20 + '@vitest/browser-playwright': 4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@22.19.20)(jiti@2.7.0))(vitest@4.1.8) + transitivePeerDependencies: + - msw + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + ws@8.21.0: {} + + yaml@1.10.3: {} + + yocto-queue@0.1.0: {} + + zimmerframe@1.1.4: {} diff --git a/webapp/pnpm-workspace.yaml b/webapp/pnpm-workspace.yaml new file mode 100644 index 0000000..5b23a1c --- /dev/null +++ b/webapp/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +onlyBuiltDependencies: + - '@tailwindcss/oxide' + - esbuild diff --git a/webapp/src/app.d.ts b/webapp/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/webapp/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/webapp/src/app.html b/webapp/src/app.html new file mode 100644 index 0000000..6a2bb58 --- /dev/null +++ b/webapp/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/webapp/src/lib/assets/favicon.svg b/webapp/src/lib/assets/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/webapp/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/webapp/src/lib/index.ts b/webapp/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/webapp/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/webapp/src/lib/vitest-examples/Welcome.svelte b/webapp/src/lib/vitest-examples/Welcome.svelte new file mode 100644 index 0000000..c7b8460 --- /dev/null +++ b/webapp/src/lib/vitest-examples/Welcome.svelte @@ -0,0 +1,8 @@ + + +

{greet(host)}

+

{greet(guest)}

diff --git a/webapp/src/lib/vitest-examples/Welcome.svelte.spec.ts b/webapp/src/lib/vitest-examples/Welcome.svelte.spec.ts new file mode 100644 index 0000000..e6e648c --- /dev/null +++ b/webapp/src/lib/vitest-examples/Welcome.svelte.spec.ts @@ -0,0 +1,15 @@ +import { page } from 'vitest/browser'; +import { describe, expect, it } from 'vitest'; +import { render } from 'vitest-browser-svelte'; +import Welcome from './Welcome.svelte'; + +describe('Welcome.svelte', () => { + it('renders greetings for host and guest', async () => { + render(Welcome, { host: 'SvelteKit', guest: 'Vitest' }); + + await expect + .element(page.getByRole('heading', { level: 1 })) + .toHaveTextContent('Hello, SvelteKit!'); + await expect.element(page.getByText('Hello, Vitest!')).toBeInTheDocument(); + }); +}); diff --git a/webapp/src/lib/vitest-examples/greet.spec.ts b/webapp/src/lib/vitest-examples/greet.spec.ts new file mode 100644 index 0000000..f2d6b12 --- /dev/null +++ b/webapp/src/lib/vitest-examples/greet.spec.ts @@ -0,0 +1,8 @@ +import { describe, it, expect } from 'vitest'; +import { greet } from './greet'; + +describe('greet', () => { + it('returns a greeting', () => { + expect(greet('Svelte')).toBe('Hello, Svelte!'); + }); +}); diff --git a/webapp/src/lib/vitest-examples/greet.ts b/webapp/src/lib/vitest-examples/greet.ts new file mode 100644 index 0000000..304b482 --- /dev/null +++ b/webapp/src/lib/vitest-examples/greet.ts @@ -0,0 +1,3 @@ +export function greet(name: string): string { + return 'Hello, ' + name + '!'; +} diff --git a/webapp/src/routes/+layout.svelte b/webapp/src/routes/+layout.svelte new file mode 100644 index 0000000..0d8eb03 --- /dev/null +++ b/webapp/src/routes/+layout.svelte @@ -0,0 +1,9 @@ + + + +{@render children()} diff --git a/webapp/src/routes/+page.svelte b/webapp/src/routes/+page.svelte new file mode 100644 index 0000000..cc88df0 --- /dev/null +++ b/webapp/src/routes/+page.svelte @@ -0,0 +1,2 @@ +

Welcome to SvelteKit

+

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

diff --git a/webapp/src/routes/demo/+page.svelte b/webapp/src/routes/demo/+page.svelte new file mode 100644 index 0000000..372e8bc --- /dev/null +++ b/webapp/src/routes/demo/+page.svelte @@ -0,0 +1,5 @@ + + +playwright diff --git a/webapp/src/routes/demo/playwright/+page.svelte b/webapp/src/routes/demo/playwright/+page.svelte new file mode 100644 index 0000000..5f0f2c9 --- /dev/null +++ b/webapp/src/routes/demo/playwright/+page.svelte @@ -0,0 +1 @@ +

Playwright e2e test demo

diff --git a/webapp/src/routes/demo/playwright/page.svelte.e2e.ts b/webapp/src/routes/demo/playwright/page.svelte.e2e.ts new file mode 100644 index 0000000..1000be6 --- /dev/null +++ b/webapp/src/routes/demo/playwright/page.svelte.e2e.ts @@ -0,0 +1,6 @@ +import { expect, test } from '@playwright/test'; + +test('has expected h1', async ({ page }) => { + await page.goto('/demo/playwright'); + await expect(page.locator('h1')).toBeVisible(); +}); diff --git a/webapp/src/routes/layout.css b/webapp/src/routes/layout.css new file mode 100644 index 0000000..cd67023 --- /dev/null +++ b/webapp/src/routes/layout.css @@ -0,0 +1,3 @@ +@import 'tailwindcss'; +@plugin '@tailwindcss/forms'; +@plugin '@tailwindcss/typography'; diff --git a/webapp/static/robots.txt b/webapp/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/webapp/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/webapp/tsconfig.json b/webapp/tsconfig.json new file mode 100644 index 0000000..2c2ed3c --- /dev/null +++ b/webapp/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/webapp/vite.config.ts b/webapp/vite.config.ts new file mode 100644 index 0000000..e65d7e8 --- /dev/null +++ b/webapp/vite.config.ts @@ -0,0 +1,51 @@ +import tailwindcss from '@tailwindcss/vite'; +import { defineConfig } from 'vitest/config'; +import { playwright } from '@vitest/browser-playwright'; +import adapter from '@sveltejs/adapter-auto'; +import { sveltekit } from '@sveltejs/kit/vite'; + +export default defineConfig({ + plugins: [ + tailwindcss(), + sveltekit({ + compilerOptions: { + // Force runes mode for the project, except for libraries. Can be removed in svelte 6. + runes: ({ filename }) => + filename.split(/[/\\]/).includes('node_modules') ? undefined : true + }, + + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter() + }) + ], + test: { + expect: { requireAssertions: true }, + projects: [ + { + extends: './vite.config.ts', + test: { + name: 'client', + browser: { + enabled: true, + provider: playwright(), + instances: [{ browser: 'chromium', headless: true }] + }, + include: ['src/**/*.svelte.{test,spec}.{js,ts}'], + exclude: ['src/lib/server/**'] + } + }, + + { + extends: './vite.config.ts', + test: { + name: 'server', + environment: 'node', + include: ['src/**/*.{test,spec}.{js,ts}'], + exclude: ['src/**/*.svelte.{test,spec}.{js,ts}'] + } + } + ] + } +});