Add desktop games, robot pet and TCG app
This commit is contained in:
1 parent
812503251b
commit
c664f573b8
55 files changed
+5090
-15
No files matched your search
@@ -0,0 +1,266 @@
|
||||
<script lang="ts">
|
||||
import { pixelRuns } from '#lib/pet/pixelArt';
|
||||
import { petHeight, petWidth, robotGridSize, type Point } from '#lib/pet/world';
|
||||
import type { PetActivity, PetExpression } from '#lib/pet/behavior';
|
||||
|
||||
let { expression, activity }: { expression: PetExpression; activity: PetActivity | null } = $props();
|
||||
|
||||
const bodyRows = [
|
||||
'............',
|
||||
'.....XX.....',
|
||||
'.XXXXXXXXXX.',
|
||||
'.X........X.',
|
||||
'.X........X.',
|
||||
'.X........X.',
|
||||
'.XXXXXXXXXX.',
|
||||
'...XXXXXX...',
|
||||
'...XXXXXX...',
|
||||
'...XXXXXX...',
|
||||
'...X....X...',
|
||||
'..XX....XX..'
|
||||
];
|
||||
|
||||
const bodyRuns = pixelRuns(bodyRows);
|
||||
|
||||
const leftArmDown: Point[] = [{ x: 0, y: 8 }, { x: 1, y: 8 }];
|
||||
const rightArmDown: Point[] = [{ x: 10, y: 8 }, { x: 11, y: 8 }];
|
||||
const leftArmUp: Point[] = [{ x: 1, y: 7 }, { x: 0, y: 6 }];
|
||||
const rightArmUp: Point[] = [{ x: 10, y: 7 }, { x: 11, y: 6 }, { x: 11, y: 5 }];
|
||||
const rightArmOut: Point[] = [{ x: 10, y: 7 }, { x: 11, y: 7 }, { x: 11, y: 6 }];
|
||||
|
||||
const armPoses: Partial<Record<PetActivity, Point[][]>> = {
|
||||
wave: [
|
||||
[...leftArmDown, ...rightArmUp],
|
||||
[...leftArmDown, ...rightArmOut]
|
||||
],
|
||||
dance: [
|
||||
[...leftArmUp, ...rightArmDown],
|
||||
[...leftArmDown, ...rightArmUp]
|
||||
]
|
||||
};
|
||||
|
||||
const happyEyePixels: Point[] = [
|
||||
{ x: 2, y: 4 },
|
||||
{ x: 3, y: 3 },
|
||||
{ x: 4, y: 4 },
|
||||
{ x: 7, y: 4 },
|
||||
{ x: 8, y: 3 },
|
||||
{ x: 9, y: 4 }
|
||||
];
|
||||
|
||||
const codeFrames = [
|
||||
[
|
||||
{ x: 3, y: 3, width: 4 },
|
||||
{ x: 3, y: 4, width: 2 },
|
||||
{ x: 4, y: 5, width: 4 }
|
||||
],
|
||||
[
|
||||
{ x: 3, y: 3, width: 2 },
|
||||
{ x: 4, y: 4, width: 4 },
|
||||
{ x: 3, y: 5, width: 3 }
|
||||
]
|
||||
];
|
||||
|
||||
const armFrames = $derived((activity && armPoses[activity]) || [[...leftArmDown, ...rightArmDown]]);
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class="robot activity-{activity ?? 'none'}"
|
||||
viewBox="0 0 {robotGridSize} {robotGridSize}"
|
||||
width={petWidth}
|
||||
height={petHeight}
|
||||
shape-rendering="crispEdges"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect class="antenna-light" class:powered-down={expression === 'sleeping'} x="5" y="0" width="2" height="1" />
|
||||
|
||||
{#each bodyRuns as run (`${run.x}-${run.y}`)}
|
||||
<rect class="frame" x={run.x} y={run.y} width={run.width} height="1" />
|
||||
{/each}
|
||||
|
||||
{#each armFrames as armPixels, frameIndex (frameIndex)}
|
||||
<g class="frame arm-frame" class:alternating={armFrames.length > 1} class:second-frame={frameIndex === 1}>
|
||||
{#each armPixels as pixel (`${pixel.x}-${pixel.y}`)}
|
||||
<rect x={pixel.x} y={pixel.y} width="1" height="1" />
|
||||
{/each}
|
||||
</g>
|
||||
{/each}
|
||||
|
||||
<rect class="screen" x="2" y="3" width="8" height="3" />
|
||||
<rect class="chest-light" x="5" y="8" width="2" height="1" />
|
||||
|
||||
{#if activity === 'compute'}
|
||||
{#each codeFrames as lines, frameIndex (frameIndex)}
|
||||
<g class="code alternating" class:second-frame={frameIndex === 1}>
|
||||
{#each lines as codeLine (`${codeLine.x}-${codeLine.y}`)}
|
||||
<rect x={codeLine.x} y={codeLine.y} width={codeLine.width} height="1" />
|
||||
{/each}
|
||||
</g>
|
||||
{/each}
|
||||
{:else if activity === 'charge'}
|
||||
<rect class="battery-track" x="3" y="4" width="6" height="1" />
|
||||
<rect class="battery-fill" x="3" y="4" width="6" height="1" />
|
||||
{:else if expression === 'awake'}
|
||||
<g class="eyes" class:blinking={activity !== 'scan'} class:scanning={activity === 'scan'}>
|
||||
<rect x="3" y="3" width="2" height="2" />
|
||||
<rect x="7" y="3" width="2" height="2" />
|
||||
</g>
|
||||
{:else if expression === 'sleeping'}
|
||||
<g class="eyes powered-down">
|
||||
<rect x="3" y="4" width="2" height="1" />
|
||||
<rect x="7" y="4" width="2" height="1" />
|
||||
</g>
|
||||
{:else}
|
||||
<g class="eyes">
|
||||
{#each happyEyePixels as pixel (`${pixel.x}-${pixel.y}`)}
|
||||
<rect x={pixel.x} y={pixel.y} width="1" height="1" />
|
||||
{/each}
|
||||
</g>
|
||||
{/if}
|
||||
</svg>
|
||||
|
||||
<style>
|
||||
.robot {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.frame {
|
||||
fill: var(--accent);
|
||||
}
|
||||
|
||||
.screen {
|
||||
fill: var(--dark-background, var(--background));
|
||||
}
|
||||
|
||||
.eyes {
|
||||
fill: var(--cyan);
|
||||
}
|
||||
|
||||
.code {
|
||||
fill: var(--green);
|
||||
}
|
||||
|
||||
.battery-track {
|
||||
fill: var(--muted);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.battery-fill {
|
||||
fill: var(--green);
|
||||
transform-box: fill-box;
|
||||
transform-origin: left center;
|
||||
animation: battery-charge 1.8s steps(6, jump-start) infinite;
|
||||
}
|
||||
|
||||
.chest-light {
|
||||
fill: var(--yellow);
|
||||
}
|
||||
|
||||
.activity-charge .chest-light {
|
||||
fill: var(--green);
|
||||
animation: light-flash 0.5s steps(1) infinite;
|
||||
}
|
||||
|
||||
.antenna-light {
|
||||
fill: var(--red);
|
||||
animation: light-flash 1.6s steps(1) infinite;
|
||||
}
|
||||
|
||||
.activity-scan .antenna-light {
|
||||
fill: var(--green);
|
||||
animation-duration: 0.4s;
|
||||
}
|
||||
|
||||
.powered-down {
|
||||
opacity: 0.35;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.blinking {
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
animation: blink 4s infinite;
|
||||
}
|
||||
|
||||
.scanning {
|
||||
animation: scan-eyes 1.4s steps(1) infinite;
|
||||
}
|
||||
|
||||
.alternating {
|
||||
animation: show-first-frame 0.5s steps(1) infinite;
|
||||
}
|
||||
|
||||
.alternating.second-frame {
|
||||
animation-name: show-second-frame;
|
||||
}
|
||||
|
||||
@keyframes show-first-frame {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes show-second-frame {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes light-flash {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scan-eyes {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: translateX(-1px);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translateX(1px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes battery-charge {
|
||||
from {
|
||||
transform: scaleX(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%,
|
||||
94%,
|
||||
100% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
|
||||
97% {
|
||||
transform: scaleY(0.1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user