tangled
alpha
login
or
join now
flo-bit.dev
/
blento
your personal website on atproto - mirror
blento.app
20
fork
atom
overview
issues
pulls
pipelines
small fixes
Florian
3 weeks ago
e9a0ea5f
6a64ae29
+13
-38
4 changed files
expand all
collapse all
unified
split
src
lib
cards
GameCards
DinoGameCard
DinoGameCard.svelte
SidebarItemDinoGameCard.svelte
index.ts
index.ts
+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
0
0
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(() => {
0
0
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>
0
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> = {};
0
19
20
// Tile size (original is 16x16)
21
const TILE_SIZE = 16;
0
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 {
0
0
0
0
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
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
102
return offscreen;
103
}
104
···
106
return new Promise<void>((resolve) => {
107
const img = new Image();
108
img.onload = () => {
0
0
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();
0
0
0
0
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';