your personal website on atproto - mirror blento.app

small fixes

Florian e9a0ea5f 6a64ae29

+13 -38
+12 -37
src/lib/cards/DinoGameCard/DinoGameCard.svelte src/lib/cards/GameCards/DinoGameCard/DinoGameCard.svelte
··· 1 <script lang="ts"> 2 - import type { ContentComponentProps } from '../types'; 3 import { onMount, onDestroy } from 'svelte'; 4 5 let { item }: ContentComponentProps = $props(); ··· 16 // Sprite images (processed with transparent backgrounds) 17 let spritesLoaded = $state(false); 18 const sprites: Record<string, HTMLCanvasElement> = {}; 19 - let tilemap: HTMLImageElement | null = null; 20 21 // Tile size (original is 16x16) 22 const TILE_SIZE = 16; 23 - const TILEMAP_COLS = 20; 24 25 // Dynamic scaling - will be calculated based on canvas size 26 let scale = 2.5; ··· 88 }; 89 90 // Extract a tile from the tilemap and process it (white to black) 91 - function extractTile( 92 - img: HTMLImageElement, 93 - row: number, 94 - col: number 95 - ): HTMLCanvasElement { 96 const offscreen = document.createElement('canvas'); 97 offscreen.width = TILE_SIZE; 98 offscreen.height = TILE_SIZE; ··· 105 106 offCtx.drawImage(img, sx, sy, TILE_SIZE, TILE_SIZE, 0, 0, TILE_SIZE, TILE_SIZE); 107 108 - // Process: turn white to black for light mode 109 - const imageData = offCtx.getImageData(0, 0, TILE_SIZE, TILE_SIZE); 110 - const data = imageData.data; 111 - 112 - for (let i = 0; i < data.length; i += 4) { 113 - const r = data[i]; 114 - const g = data[i + 1]; 115 - const b = data[i + 2]; 116 - 117 - // Turn white/near-white pixels to black 118 - if (r > 220 && g > 220 && b > 220) { 119 - data[i] = 0; 120 - data[i + 1] = 0; 121 - data[i + 2] = 0; 122 - } 123 - } 124 - 125 - offCtx.putImageData(imageData, 0, 0); 126 return offscreen; 127 } 128 ··· 130 return new Promise<void>((resolve) => { 131 const img = new Image(); 132 img.onload = () => { 133 - tilemap = img; 134 - // Extract all sprites from the tilemap 135 for (const [key, pos] of Object.entries(SPRITE_POSITIONS)) { 136 sprites[key] = extractTile(img, pos.row, pos.col); 137 } ··· 485 // Draw game over text (no overlay background) 486 if (gameState === 'gameover') { 487 ctx.fillStyle = '#000000'; 488 - ctx.font = `bold ${Math.max(14, Math.floor(16 * (scale / 2.5)))}px monospace`; 489 ctx.textAlign = 'center'; 490 - ctx.fillText('GAME OVER', canvasWidth / 2, canvasHeight / 2 - 30); 491 } 492 493 animationId = requestAnimationFrame(gameLoop); ··· 503 initGroundTiles(); 504 } 505 506 onMount(async () => { 507 ctx = canvas.getContext('2d'); 508 await loadSprites(); 509 resizeCanvas(); 510 511 - const resizeObserver = new ResizeObserver(() => { 512 resizeCanvas(); 513 }); 514 resizeObserver.observe(canvas.parentElement!); 515 516 gameLoop(); 517 - 518 - return () => { 519 - resizeObserver.disconnect(); 520 - }; 521 }); 522 523 onDestroy(() => { 524 if (animationId) { 525 cancelAnimationFrame(animationId); 526 } ··· 530 <svelte:window onkeydown={handleKeyDown} onkeyup={handleKeyUp} /> 531 532 <div class="relative h-full w-full overflow-hidden"> 533 - <canvas bind:this={canvas} class="h-full w-full dark:invert" ontouchstart={handleTouch}></canvas> 534 535 {#if gameState === 'idle' || gameState === 'gameover'} 536 <button 537 onclick={startGame} 538 - class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform cursor-pointer rounded-lg border-2 border-black bg-white/30 px-6 py-3 font-mono text-sm font-bold text-black transition-colors hover:bg-black hover:text-white" 539 > 540 {gameState === 'gameover' ? 'PLAY AGAIN' : 'START'} 541 </button>
··· 1 <script lang="ts"> 2 + import type { ContentComponentProps } from '../../types'; 3 import { onMount, onDestroy } from 'svelte'; 4 5 let { item }: ContentComponentProps = $props(); ··· 16 // Sprite images (processed with transparent backgrounds) 17 let spritesLoaded = $state(false); 18 const sprites: Record<string, HTMLCanvasElement> = {}; 19 20 // Tile size (original is 16x16) 21 const TILE_SIZE = 16; 22 23 // Dynamic scaling - will be calculated based on canvas size 24 let scale = 2.5; ··· 86 }; 87 88 // Extract a tile from the tilemap and process it (white to black) 89 + function extractTile(img: HTMLImageElement, row: number, col: number): HTMLCanvasElement { 90 const offscreen = document.createElement('canvas'); 91 offscreen.width = TILE_SIZE; 92 offscreen.height = TILE_SIZE; ··· 99 100 offCtx.drawImage(img, sx, sy, TILE_SIZE, TILE_SIZE, 0, 0, TILE_SIZE, TILE_SIZE); 101 102 return offscreen; 103 } 104 ··· 106 return new Promise<void>((resolve) => { 107 const img = new Image(); 108 img.onload = () => { 109 for (const [key, pos] of Object.entries(SPRITE_POSITIONS)) { 110 sprites[key] = extractTile(img, pos.row, pos.col); 111 } ··· 459 // Draw game over text (no overlay background) 460 if (gameState === 'gameover') { 461 ctx.fillStyle = '#000000'; 462 + ctx.font = `bold ${Math.max(14, Math.floor(20 * (scale / 2.5)))}px monospace`; 463 ctx.textAlign = 'center'; 464 + ctx.fillText('GAME OVER', canvasWidth / 2, canvasHeight / 2 - 40); 465 } 466 467 animationId = requestAnimationFrame(gameLoop); ··· 477 initGroundTiles(); 478 } 479 480 + let resizeObserver: ResizeObserver | undefined = $state(); 481 + 482 onMount(async () => { 483 ctx = canvas.getContext('2d'); 484 await loadSprites(); 485 resizeCanvas(); 486 487 + resizeObserver = new ResizeObserver(() => { 488 resizeCanvas(); 489 }); 490 resizeObserver.observe(canvas.parentElement!); 491 492 gameLoop(); 493 }); 494 495 onDestroy(() => { 496 + resizeObserver?.disconnect(); 497 + 498 if (animationId) { 499 cancelAnimationFrame(animationId); 500 } ··· 504 <svelte:window onkeydown={handleKeyDown} onkeyup={handleKeyUp} /> 505 506 <div class="relative h-full w-full overflow-hidden"> 507 + <canvas bind:this={canvas} class="h-full w-full invert dark:invert-0" ontouchstart={handleTouch} 508 + ></canvas> 509 510 {#if gameState === 'idle' || gameState === 'gameover'} 511 <button 512 onclick={startGame} 513 + class="bg-base-50/80 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform cursor-pointer rounded-lg border-2 border-black px-6 py-3 font-mono font-bold text-black transition-colors duration-200 hover:bg-black hover:text-white" 514 > 515 {gameState === 'gameover' ? 'PLAY AGAIN' : 'START'} 516 </button>
src/lib/cards/DinoGameCard/SidebarItemDinoGameCard.svelte src/lib/cards/GameCards/DinoGameCard/SidebarItemDinoGameCard.svelte
src/lib/cards/DinoGameCard/index.ts src/lib/cards/GameCards/DinoGameCard/index.ts
+1 -1
src/lib/cards/index.ts
··· 3 import { BigSocialCardDefinition } from './BigSocialCard'; 4 import { BlueskyMediaCardDefinition } from './BlueskyMediaCard'; 5 import { BlueskyPostCardDefinition } from './BlueskyPostCard'; 6 - import { DinoGameCardDefinition } from './DinoGameCard'; 7 import { EmbedCardDefinition } from './EmbedCard'; 8 import { ImageCardDefinition } from './ImageCard'; 9 import { LinkCardDefinition } from './LinkCard';
··· 3 import { BigSocialCardDefinition } from './BigSocialCard'; 4 import { BlueskyMediaCardDefinition } from './BlueskyMediaCard'; 5 import { BlueskyPostCardDefinition } from './BlueskyPostCard'; 6 + import { DinoGameCardDefinition } from './GameCards/DinoGameCard'; 7 import { EmbedCardDefinition } from './EmbedCard'; 8 import { ImageCardDefinition } from './ImageCard'; 9 import { LinkCardDefinition } from './LinkCard';