Initial commit: Dostalgia — DOS game hub

- Go backend (single binary) with embedded Svelte frontend
- JSON-per-game storage (no database)
- .jsdos bundle creation on upload
- Dark phosphor-green theme (#33FF33)
- js-dos v8 emulator integration
- Sockdrive support for games >80MB
- IGDB placeholder for future artwork scraping
- Docker deployment ready
This commit is contained in:
David Alvarez
2026-05-23 11:18:41 +02:00
parent c3c154ec65
commit 1efd10f81f
24 changed files with 3314 additions and 2 deletions
+136
View File
@@ -0,0 +1,136 @@
<script>
import { getGame, bundleUrl } from "../lib/api.js";
import { push } from "../lib/router.js";
let { id } = $props();
let game = $state(null);
let loading = $state(true);
let error = $state("");
let emulatorReady = $state(false);
let dosContainer;
async function load() {
loading = true;
try {
game = await getGame(id);
} catch (e) {
error = "Game not found";
}
loading = false;
}
function startEmulator() {
if (!game || !game.ready) return;
// Load js-dos from CDN dynamically
const css = document.createElement("link");
css.rel = "stylesheet";
css.href = "https://v8.js-dos.com/latest/js-dos.css";
document.head.appendChild(css);
const script = document.createElement("script");
script.src = "https://v8.js-dos.com/latest/js-dos.js";
script.onload = () => {
const url = bundleUrl(game);
if (window.Dos && dosContainer && url) {
window.Dos(dosContainer, { url });
emulatorReady = true;
}
};
document.head.appendChild(script);
emulatorReady = true;
}
$effect(() => { load(); });
// Cleanup on unmount
$effect(() => {
return () => {
// js-dos handles its own cleanup
};
});
</script>
{#if loading}
<div class="loading">Loading...</div>
{:else if error}
<div class="error">{error}</div>
<a href="#/" class="back-link">← Back to Library</a>
{:else if game}
<div class="play-page">
<div class="play-header">
<a href={`#/game/${game.id}`} class="back-link">{game.title}</a>
{#if !emulatorReady}
<button class="btn btn-primary" onclick={startEmulator}>
▶ Launch Emulator
</button>
{/if}
</div>
{#if emulatorReady}
<div class="emulator-wrapper">
<div bind:this={dosContainer} class="dos-container"></div>
</div>
{:else}
<div class="launch-screen">
<div class="launch-icon">🕹️</div>
<h2>{game.title}</h2>
<p class="launch-info">
{game.year ? `${game.year} · ` : ""}{game.genre || ""}
</p>
<p class="launch-hint">Click "Launch Emulator" above to start playing</p>
<p class="save-hint">💾 Game saves are stored automatically in your browser</p>
</div>
{/if}
</div>
{/if}
<style>
.play-page { padding: 16px 0; }
.loading { text-align: center; padding: 60px; color: var(--text-dim); }
.error { background: #331111; border: 1px solid #cc3333; color: #ff6666; padding: 12px; border-radius: var(--radius-sm); margin-bottom: 16px; }
.back-link { display: inline-block; color: var(--text-dim); }
.back-link:hover { color: var(--phosphor); }
.play-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.emulator-wrapper {
background: #000;
border-radius: var(--radius);
overflow: hidden;
border: 1px solid var(--border);
}
.dos-container {
width: 100%;
aspect-ratio: 4/3;
max-height: 80vh;
}
.dos-container :global(canvas) {
width: 100% !important;
height: 100% !important;
}
.launch-screen {
text-align: center;
padding: 80px 20px;
border: 1px dashed var(--border);
border-radius: var(--radius);
}
.launch-icon { font-size: 4rem; margin-bottom: 16px; }
.launch-screen h2 { color: var(--phosphor); font-family: var(--font-mono); margin-bottom: 8px; }
.launch-info { color: var(--text-dim); margin-bottom: 24px; }
.launch-hint { color: var(--text-dim); margin-bottom: 8px; }
.save-hint {
color: var(--phosphor-dim);
font-size: 0.85rem;
margin-top: 16px;
background: var(--phosphor-burn);
display: inline-block;
padding: 6px 16px;
border-radius: 12px;
}
</style>