SOURCE / PINNED RELEASE
Made of little things.
Pickle Rustlers
- Release
- 039e51f55e34…
- Author-recorded commit
- 40608586a8a1…
- License
- LICENSE
- Author’s source reference
- nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-bad44e7ab35
Archive hash verified: f38e99afa0992052…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import { STORAGE_DOMAIN, storage } from '@napplet/sdk';
import { runtimeHasDomain } from './domain-availability.js';
import { Game, type Progress } from './game/game.js';
import { setSoundEnabled } from './game/sound.js';
import { startSettings } from './napplet-settings.js';
import { createScoreShare } from './share/share.js';
import './styles.css';
// Colors are app-owned: the desert, the pickles, the onions and the wanted-poster HUD
// keep their palette in every host theme (see docs/brief.md). Matching the host is
// an app choice, not a NAP requirement, so no theme handler is installed.
const PROGRESS_KEY = 'progress-v1';
async function loadProgress(): Promise<Progress> {
const empty = { best: 0, wins: 0 };
if (!runtimeHasDomain(STORAGE_DOMAIN)) return empty;
const raw = await storage.getItem(PROGRESS_KEY);
if (!raw) return empty;
try {
const parsed = JSON.parse(raw) as Partial<Progress>;
const count = (v: unknown) => (typeof v === 'number' && Number.isFinite(v) && v > 0 ? Math.floor(v) : 0);
return { best: count(parsed.best), wins: count(parsed.wins) };
} catch {
return empty;
}
}
async function saveProgress(progress: Progress): Promise<void> {
// Without storage, progress lasts only for this session.
if (!runtimeHasDomain(STORAGE_DOMAIN)) return;
await storage.setItem(PROGRESS_KEY, JSON.stringify(progress));
}
const canvas = document.querySelector<HTMLCanvasElement>('#scene');
if (!canvas) throw new Error('Missing #scene canvas');
let game: Game | null = null;
try {
// Score-card posting needs identity, upload and outbox; otherwise it stays hidden.
game = new Game({ canvas, loadProgress, saveProgress, share: createScoreShare() });
} catch (error) {
// WebGL can be unavailable (old hardware, disabled GPU). Say so plainly.
document.getElementById('noGl')?.removeAttribute('hidden');
document.getElementById('posterWrap')?.setAttribute('hidden', '');
console.error(error);
}
window.addEventListener('pagehide', (event) => {
if (!event.persisted) game?.dispose();
});
// Optional settings (sound and screen shake). Without the config domain the defaults stay on.
if (runtimeHasDomain('config')) {
startSettings((values) => {
setSoundEnabled(values.sound !== false);
if (game) game.shakeEnabled = values.screenShake !== false;
});
}
