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