diff --git a/bun.lock b/bun.lock index 8def1b8..3549b96 100644 --- a/bun.lock +++ b/bun.lock @@ -5,15 +5,15 @@ "": { "name": "nage", "devDependencies": { - "@biomejs/biome": "2.3.14", - "@types/bun": "1.3.8", - "lz-string": "1.5.0", - "solid-js": "1.9.11", - "vite": "7.3.1", - "vite-plugin-solid": "2.11.10", + "@biomejs/biome": "latest", + "@types/bun": "latest", + "lz-string": "latest", + "solid-js": "latest", + "vite": "latest", + "vite-plugin-solid": "latest", }, "peerDependencies": { - "typescript": "5.9.3", + "typescript": "latest", }, }, }, @@ -54,23 +54,23 @@ "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - "@biomejs/biome": ["@biomejs/biome@2.3.14", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.14", "@biomejs/cli-darwin-x64": "2.3.14", "@biomejs/cli-linux-arm64": "2.3.14", "@biomejs/cli-linux-arm64-musl": "2.3.14", "@biomejs/cli-linux-x64": "2.3.14", "@biomejs/cli-linux-x64-musl": "2.3.14", "@biomejs/cli-win32-arm64": "2.3.14", "@biomejs/cli-win32-x64": "2.3.14" }, "bin": { "biome": "bin/biome" } }, "sha512-QMT6QviX0WqXJCaiqVMiBUCr5WRQ1iFSjvOLoTk6auKukJMvnMzWucXpwZB0e8F00/1/BsS9DzcKgWH+CLqVuA=="], + "@biomejs/biome": ["@biomejs/biome@2.4.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.4", "@biomejs/cli-darwin-x64": "2.4.4", "@biomejs/cli-linux-arm64": "2.4.4", "@biomejs/cli-linux-arm64-musl": "2.4.4", "@biomejs/cli-linux-x64": "2.4.4", "@biomejs/cli-linux-x64-musl": "2.4.4", "@biomejs/cli-win32-arm64": "2.4.4", "@biomejs/cli-win32-x64": "2.4.4" }, "bin": { "biome": "bin/biome" } }, "sha512-tigwWS5KfJf0cABVd52NVaXyAVv4qpUXOWJ1rxFL8xF1RVoeS2q/LK+FHgYoKMclJCuRoCWAPy1IXaN9/mS61Q=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UJGPpvWJMkLxSRtpCAKfKh41Q4JJXisvxZL8ChN1eNW3m/WlPFJ6EFDCE7YfUb4XS8ZFi3C1dFpxUJ0Ety5n+A=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jZ+Xc6qvD6tTH5jM6eKX44dcbyNqJHssfl2nnwT6vma6B1sj7ZLTGIk6N5QwVBs5xGN52r3trk5fgd3sQ9We9A=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-PNkLNQG6RLo8lG7QoWe/hhnMxJIt1tEimoXpGQjwS/dkdNiKBLPv4RpeQl8o3s1OKI3ZOR5XPiYtmbGGHAOnLA=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-Dh1a/+W+SUCXhEdL7TiX3ArPTFCQKJTI1mGncZNWfO+6suk+gYA4lNyJcBB+pwvF49uw0pEbUS49BgYOY4hzUg=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-KT67FKfzIw6DNnUNdYlBg+eU24Go3n75GWK6NwU4+yJmDYFe9i/MjiI+U/iEzKvo0g7G7MZqoyrhIYuND2w8QQ=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-V/NFfbWhsUU6w+m5WYbBenlEAz8eYnSqRMDMAW3K+3v0tYVkNyZn8VU0XPxk/lOqNXLSCCrV7FmV/u3SjCBShg=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-LInRbXhYujtL3sH2TMCH/UBwJZsoGwfQjBrMfl84CD4hL/41C/EU5mldqf1yoFpsI0iPWuU83U+nB2TUUypWeg=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+sPAXq3bxmFwhVFJnSwkSF5Rw2ZAJMH3MF6C9IveAEOdSpgajPhoQhbbAK12SehN9j2QrHpk4J/cHsa/HqWaYQ=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-ZsZzQsl9U+wxFrGGS4f6UxREUlgHwmEfu1IrXlgNFrNnd5Th6lIJr8KmSzu/+meSa9f4rzFrbEW9LBBA6ScoMA=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-R4+ZCDtG9kHArasyBO+UBD6jr/FcFCTH8QkNTOCu0pRJzCWyWC4EtZa2AmUZB5h3e0jD7bRV2KvrENcf8rndBg=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-KQU7EkbBBuHPW3/rAcoiVmhlPtDSGOGRPv9js7qJVpYTzjQmVR+C9Rfcz+ti8YCH+zT1J52tuBybtP4IodjxZQ=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gGvFTGpOIQDb5CQ2VC0n9Z2UEqlP46c4aNgHmAMytYieTGEcfqhfCFnhs6xjt0S3igE6q5GLuIXtdQt3Izok+g=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-+IKYkj/pUBbnRf1G1+RlyA3LWiDgra1xpS7H2g4BuOzzRbRB+hmlw0yFsLprHhbbt7jUzbzAbAjK/Pn0FDnh1A=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-trzCqM7x+Gn832zZHgr28JoYagQNX4CZkUZhMUac2YxvvyDRLJDrb5m9IA7CaZLlX6lTQmADVfLEKP1et1Ma4Q=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.14", "", { "os": "win32", "cpu": "x64" }, "sha512-oizCjdyQ3WJEswpb3Chdngeat56rIdSYK12JI3iI11Mt5T5EXcZ7WLuowzEaFPNJ3zmOQFliMN8QY1Pi+qsfdQ=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.4", "", { "os": "win32", "cpu": "x64" }, "sha512-gnOHKVPFAAPrpoPt2t+Q6FZ7RPry/FDV3GcpU53P3PtLNnQjBmKyN2Vh/JtqXet+H4pme8CC76rScwdjDcT1/A=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], @@ -192,7 +192,7 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="], + "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], @@ -206,7 +206,7 @@ "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], - "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], + "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], "caniuse-lite": ["caniuse-lite@1.0.30001763", "", {}, "sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ=="], diff --git a/engine/Drawable.ts b/engine/Drawable.ts new file mode 100644 index 0000000..a10448b --- /dev/null +++ b/engine/Drawable.ts @@ -0,0 +1,29 @@ +import { drawSprite, type Sprite, spriteSheetMap } from "#/engine/Sprite.ts"; +import type { Vector } from "#/engine/Vector.ts"; + +export type Drawable = { + position: Vector; + sprite: Sprite; +}; + +export function isDrawable( + entity: { + position?: Vector; + sprite?: Sprite; + } & T, +): entity is Drawable & T { + return ( + typeof entity.position === "object" && typeof entity.sprite === "object" + ); +} + +export function draw(ctx: CanvasRenderingContext2D, drawable: Drawable): void { + drawSprite( + ctx, + spriteSheetMap.get(drawable.sprite.src)!, + drawable.sprite.xStart, + drawable.sprite.yStart, + drawable.position.x, + drawable.position.y, + ); +} diff --git a/engine/Scene.ts b/engine/Scene.ts index 7274050..54a79a1 100644 --- a/engine/Scene.ts +++ b/engine/Scene.ts @@ -1,3 +1,4 @@ +import { type Drawable, draw, isDrawable } from "#/engine/Drawable.ts"; import { entityMap, entityProcessMap, @@ -5,7 +6,7 @@ import { } from "#/engine/Entity.ts"; import { type Graph, getNeighbours } from "#/engine/Graph.ts"; import { useWithAsyncCache } from "#/engine/lib/cache.ts"; -import { playAnimation } from "./Sprite"; +import { animateSprite, type SpriteAnimation } from "#/engine/Sprite.ts"; export type EntityData = { name: string; @@ -22,11 +23,13 @@ export type SceneData = { export type EntityMap = Map< string, { - process: ProcessEntity; - instances: Array; + process: ProcessEntity<{ id: number }>; + instances: Array<{ id: number }>; } >; +export type AnimationMap = Map; + export type Scene = { data: SceneData; entityMap: EntityMap; @@ -49,7 +52,11 @@ export const sceneDataMap = new Map(); export const sceneEntiyMap = new Map(); export const sceneGraph: Graph = new Map(); -const sceneInstancesDrawMap = new Map>(); +const sceneInstancesDrawMap = new Map< + string, + Array +>(); +const sceneInstancesAnimationMap = new Map(); export const [loadScene, sceneCache] = useWithAsyncCache( async (name: string) => { @@ -60,7 +67,7 @@ export const [loadScene, sceneCache] = useWithAsyncCache( entityInstanceMap.clear(); await Promise.all( - data.entities.map(async (entityData: EntityData) => { + data.entities.map(async (entityData: EntityData, index: number) => { await import(`#/game/entities/${entityData.name}/entity.ts`); if (!entityInstanceMap.has(entityData.name)) { @@ -75,6 +82,7 @@ export const [loadScene, sceneCache] = useWithAsyncCache( structuredClone({ ...entityMap.get(entityData.name)!, ...entityData, + id: index, }), ); } catch (error) { @@ -104,19 +112,17 @@ function processEntities(delta: number): void { } } -// TODO: type stuff and fixme WIP function processSystems(ctx: CanvasRenderingContext2D, delta: number): void { const drawables = sceneInstancesDrawMap.get(currentScene.data.name)!; + const animationsMap = sceneInstancesAnimationMap.get(currentScene.data.name)!; + for (const drawable of drawables.sort( (a, b) => a.position.y - b.position.y, )) { - playAnimation( - ctx, - drawable.animation, - drawable.position.x, - drawable.position.y, - delta, - ); + if (animationsMap.has(drawable.id)) { + animateSprite(animationsMap.get(drawable.id)!, delta); + } + draw(ctx, drawable); } } @@ -176,19 +182,29 @@ export async function setScene(name: string): Promise { const data = sceneDataMap.get(name)!; const process = sceneProcessMap.get(name)!; const entityInstanceMap = sceneEntiyMap.get(name)!; - // TODO: test for drawable first - sceneInstancesDrawMap.set( - data.name, - entityInstanceMap.values().reduce( - (acc, cur) => { - for (const instance of cur.instances) { - acc.push(instance); - } - return acc; - }, - [] as Array, - ), - ); + + const drawableInstances: Array = []; + sceneInstancesDrawMap.set(data.name, drawableInstances); + + const animateInstances: AnimationMap = new Map(); + sceneInstancesAnimationMap.set(data.name, animateInstances); + + for (const entity of entityInstanceMap.values()) { + for (const instance of entity.instances) { + if (isDrawable(instance)) { + drawableInstances.push(instance); + } + if ( + (instance as { id: number; animation: SpriteAnimation }).animation !== + undefined + ) { + animateInstances.set( + instance.id, + (instance as { id: number; animation: SpriteAnimation }).animation, // FIXME + ); + } + } + } const preProcess = scenePreProcessMap.get(name); if (preProcess) { diff --git a/engine/Sprite.ts b/engine/Sprite.ts index a69f315..91f2149 100644 --- a/engine/Sprite.ts +++ b/engine/Sprite.ts @@ -1,4 +1,12 @@ -import { loadImage } from "./lib/loadImage"; +import { loadImage } from "#/engine/lib/loadImage.ts"; + +export type Sprite = { + src: string; + xStart: number; + yStart: number; + width: number; + height: number; +}; export type SpriteSheet = { image: HTMLImageElement; @@ -9,7 +17,7 @@ export type SpriteSheet = { }; export type SpriteAnimation = { - src: string; + sprite: Sprite; xIndex: number; yIndex: number; frameTime: number; @@ -49,12 +57,14 @@ export async function useSpriteSheetSrc( } export function createSpriteAnimation( - src: string, + sprite: Sprite, frameTime: number, yIndex: number, ): SpriteAnimation { + sprite.yStart = yIndex * spriteSheetMap.get(sprite.src)!.frameHeight; + return { - src, + sprite, xIndex: 0, yIndex, frameTime, @@ -65,8 +75,8 @@ export function createSpriteAnimation( export function drawSprite( ctx: CanvasRenderingContext2D, sheet: SpriteSheet, - xIndex: number, - yIndex: number, + xStart: number, + yStart: number, x: number, y: number, width?: number, @@ -74,8 +84,8 @@ export function drawSprite( ): void { ctx.drawImage( sheet.image, - xIndex * sheet.frameWidth, - yIndex * sheet.frameHeight, + xStart, + yStart, sheet.frameWidth, sheet.frameHeight, x, @@ -85,15 +95,8 @@ export function drawSprite( ); } -export function playAnimation( - ctx: CanvasRenderingContext2D, - animation: SpriteAnimation, - x: number, - y: number, - delta: number, -): void { - const sheet = spriteSheetMap.get(animation.src)!; - +export function animateSprite(animation: SpriteAnimation, delta: number): void { + const sheet = spriteSheetMap.get(animation.sprite.src)!; if ((animation.frameDelta += delta) > animation.frameTime) { animation.frameDelta = 0; if ((animation.xIndex += 1) >= sheet.xFrames) { @@ -101,5 +104,5 @@ export function playAnimation( } } - drawSprite(ctx, sheet, animation.xIndex, animation.yIndex, x, y); + animation.sprite.xStart = animation.xIndex * sheet.frameWidth; } diff --git a/game/entities/hero/entity.ts b/game/entities/hero/entity.ts index 645a1e7..c700597 100644 --- a/game/entities/hero/entity.ts +++ b/game/entities/hero/entity.ts @@ -5,6 +5,7 @@ import { keyboardInput } from "#/engine/Keyboard.ts"; import { pointer } from "#/engine/Pointer.ts"; import { createSpriteAnimation, + type Sprite, type SpriteAnimation, useSpriteSheetSrc, } from "#/engine/Sprite.ts"; @@ -15,17 +16,26 @@ export type Hero = { position: Vector; velocity: Vector; body: Circle; + sprite: Sprite; animation: SpriteAnimation; }; const speed = 0.05; const idleSheet = await useSpriteSheetSrc(charIdle, 2, 4); +const sprite = { + src: idleSheet, + xStart: 0, + yStart: 0, + width: 16, + height: 16, +}; const state: Hero = { position: createVector(), velocity: createVector(), body: createCircle(createVector(), 4), - animation: createSpriteAnimation(idleSheet, 500, 2), + sprite, + animation: createSpriteAnimation(sprite, 500, 2), }; const process = defineEntity("hero", state); diff --git a/game/entities/portal/entity.ts b/game/entities/portal/entity.ts index 44b4c14..b203149 100644 --- a/game/entities/portal/entity.ts +++ b/game/entities/portal/entity.ts @@ -1,11 +1,19 @@ import { defineEntity } from "#/engine/Entity.ts"; import { createSpriteAnimation, useSpriteSheetSrc } from "#/engine/Sprite.ts"; -import { createVector } from "#/engine/Vector"; +import { createVector } from "#/engine/Vector.ts"; import portal from "#/game/assets/portal.png"; const portalSheet = await useSpriteSheetSrc(portal, 3, 1); +const sprite = { + src: portalSheet, + xStart: 0, + yStart: 0, + width: 16, + height: 16, +}; defineEntity("portal", { position: createVector(64, 64), - animation: createSpriteAnimation(portalSheet, 400, 0), + sprite, + animation: createSpriteAnimation(sprite, 400, 0), }); diff --git a/game/entities/testEntity/entity.ts b/game/entities/testEntity/entity.ts index 990de7c..d816fd4 100644 --- a/game/entities/testEntity/entity.ts +++ b/game/entities/testEntity/entity.ts @@ -1,6 +1,7 @@ import { defineEntity } from "#/engine/Entity.ts"; import { createSpriteAnimation, + type Sprite, type SpriteAnimation, useSpriteSheetSrc, } from "#/engine/Sprite.ts"; @@ -9,11 +10,28 @@ import { viewport } from "#/engine/Viewport.ts"; import charIdle from "#/game/assets/char-idle.png"; import charWalk from "#/game/assets/char-walk.png"; -const sprites = { +const sheets = { idle: await useSpriteSheetSrc(charIdle, 2, 4), walk: await useSpriteSheetSrc(charWalk, 4, 4), }; +const sprites = { + idle: { + src: sheets.idle, + xStart: 0, + yStart: 0, + width: 16, + height: 16, + }, + walk: { + src: sheets.walk, + xStart: 0, + yStart: 0, + width: 16, + height: 16, + }, +}; + const animations = { idle: createSpriteAnimation(sprites.idle, 500, 2), walk: createSpriteAnimation(sprites.walk, 250, 2), @@ -26,11 +44,18 @@ const process = defineEntity<{ idle: SpriteAnimation; walk: SpriteAnimation; }; + sprite: Sprite; + sprites: { + idle: Sprite; + walk: Sprite; + }; stuff: number; }>("testEntity", { position: createVector(), - animations, animation: animations.idle, + animations, + sprite: sprites.idle, + sprites, stuff: 1, }); @@ -39,8 +64,10 @@ process((entity, _delta) => { if (entity.position.x > viewport.canvas.width - 16) { entity.stuff = -1; entity.animation = entity.animations.walk; + entity.sprite = entity.sprites.walk; } else if (entity.position.x < 0) { entity.stuff = 1; entity.animation = entity.animations.idle; + entity.sprite = entity.sprites.idle; } }); diff --git a/game/scenes/testScene/scene.ts b/game/scenes/testScene/scene.ts index fab35f3..b0a06c3 100644 --- a/game/scenes/testScene/scene.ts +++ b/game/scenes/testScene/scene.ts @@ -3,7 +3,6 @@ import { pointer } from "#/engine/Pointer.ts"; import { defineScene } from "#/engine/Scene.ts"; import { spriteSheetMap } from "#/engine/Sprite.ts"; import { createVector } from "#/engine/Vector"; -import type { Hero } from "#/game/entities/hero/entity.ts"; import data from "./data.json"; const { process, preProcess, linkScenes } = defineScene(data); @@ -12,11 +11,11 @@ linkScenes(["fooScene"] as const); preProcess((entityInstanceMap) => { const entry = entityInstanceMap.get("hero")!; - const instance = entry.instances[0] as Hero; - const sheet = spriteSheetMap.get(instance.animation.src)!; + const instance = entry.instances[0]; + // const sheet = spriteSheetMap.get(instance.animation.src)!; - instance.position.x = data.width - sheet.frameHeight; - instance.position.y = data.height - sheet.frameWidth; + instance.position.x = data.width - 16; + instance.position.y = data.height - 16; }); const cirle = createCircle(createVector(), 4); diff --git a/package.json b/package.json index 73657d3..2438898 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "check": "biome check --write" }, "devDependencies": { - "@biomejs/biome": "2.3.14", - "@types/bun": "1.3.8", + "@biomejs/biome": "2.4.4", + "@types/bun": "1.3.9", "lz-string": "1.5.0", "solid-js": "1.9.11", "vite": "7.3.1",