Files
Space-Game/archive/legacy-static/js/pages/techstack.js
francy51 316a44661b Restructure into pnpm monorepo with game shell, docs, and SpacetimeDB backend
- Restructure flat static prototype into pnpm workspace monorepo
- apps/game: playable shell with R3F 3D scene, HUD, SpacetimeDB connection
- apps/docs: design docs and prototypes
- apps/site: landing page
- packages/ui: shared Button and Panel primitives
- services/spacetimedb: backend module (9 tables, 11 reducers)
- Archive legacy static files to archive/legacy-static/
- Game loop: connect, undock, target, approach, dock, mine, sell
- Add pnpm-workspace.yaml, tsconfig.base.json, spacetime.json
2026-05-31 17:56:56 -04:00

175 lines
8.5 KiB
JavaScript

window.GDD = window.GDD || {};
function TechStackPage() {
const [activeTab, setActiveTab] = React.useState('frontend');
const tabs = [
{ id: 'frontend', label: 'Frontend' },
{ id: 'rendering', label: '3D Rendering' },
{ id: 'state', label: 'State' },
{ id: 'backend', label: 'Backend' },
{ id: 'styling', label: 'Styling' },
{ id: 'auth', label: 'Auth' },
];
const decisions = {
frontend: {
choice: 'Vite + React + TypeScript',
reason: 'Fast client-side app setup, no server-rendering complexity, good for a real-time game UI.',
whyNot: "Next.js is not necessary for the MVP. The prototype is a client-side real-time game, not a content site. Next.js can be introduced later for marketing pages, account pages, SSR, or API routes outside SpacetimeDB.",
},
rendering: {
choice: 'React Three Fiber',
reason: 'Declarative React renderer for Three.js; good for a browser prototype and React integration.',
whyNot: "A full engine like Unity or Bevy would slow iteration on panels, tables, forms, chat, and market UX. The gameplay is UI-heavy and economy/social-system-heavy.",
},
state: {
choice: 'Zustand',
reason: 'Simple local state for panels, selection, active tabs, camera preferences, and modal state.',
whyNot: "Redux would add ceremony without benefit at prototype scale. Context API re-renders would hurt performance with frequent state updates.",
},
backend: {
choice: 'SpacetimeDB',
reason: 'Real-time backend/database with server-side reducers and live client subscriptions. Authoritative state, persistence, multiplayer all in one.',
whyNot: "A custom Node.js + PostgreSQL backend would require building real-time sync, subscriptions, and authoritative game logic from scratch.",
},
styling: {
choice: 'Tailwind CSS',
reason: 'Fast UI iteration for panels, tables, HUDs, and dense game interfaces.',
whyNot: "CSS-in-JS adds runtime overhead. Vanilla CSS at this scale would slow panel iteration.",
},
auth: {
choice: 'SpacetimeDB Identity (MVP)',
reason: 'Keep early identity/session handling simple. Add external auth only after core loop works.',
whyNot: "Full OAuth/JWT would add complexity before we know the right identity model for the game.",
},
};
const d = decisions[activeTab];
return (
<div className="content-inner">
<h1 style={{ marginBottom: '8px' }}>Technical Direction</h1>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.95rem', maxWidth: '680px' }}>
Each layer is chosen for iteration speed during the prototype phase. Architecture is designed
so any layer can be replaced as the game evolves.
</p>
{/* Decision tabs */}
<div style={{ display: 'flex', gap: 'var(--sp-2)', marginBottom: 'var(--sp-6)', flexWrap: 'wrap' }}>
{tabs.map(t => (
<button
key={t.id}
className={`btn btn-sm${activeTab === t.id ? ' btn-primary' : ''}`}
onClick={() => setActiveTab(t.id)}
>
{t.label}
</button>
))}
</div>
{/* Active decision */}
<div className="card" style={{ marginBottom: 'var(--sp-6)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--sp-3)', marginBottom: 'var(--sp-4)' }}>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: 'var(--accent)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
{activeTab}
</span>
<span style={{ width: '1px', height: '16px', background: 'var(--border)' }} />
<span style={{ fontFamily: 'var(--font-display)', fontSize: '1.3rem', fontWeight: 600, color: 'var(--fg-bright)' }}>
{d.choice}
</span>
</div>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', marginBottom: 'var(--sp-4)' }}>
<strong style={{ color: 'var(--fg)' }}>Why:</strong> {d.reason}
</p>
<div style={{ background: 'var(--surface-raised)', borderRadius: 'var(--radius-md)', padding: 'var(--sp-4)', fontSize: '0.85rem', color: 'var(--fg-dim)' }}>
<strong style={{ color: 'var(--muted)' }}>Why not the alternatives:</strong> {d.whyNot}
</div>
</div>
{/* Full decision table */}
<div className="section-header">
<span className="section-num">TECH-ALL</span>
<h2 style={{ margin: 0 }}>Decision Matrix</h2>
</div>
<table className="data-table">
<thead>
<tr><th>Layer</th><th>Choice</th><th>Reason</th></tr>
</thead>
<tbody>
<tr>
<td style={{ color: 'var(--cyan)' }}>Frontend</td>
<td>Vite + React + TypeScript</td>
<td style={{ color: 'var(--fg-dim)' }}>Fast client-side app, no SSR complexity.</td>
</tr>
<tr>
<td style={{ color: 'var(--purple)' }}>3D Rendering</td>
<td>React Three Fiber</td>
<td style={{ color: 'var(--fg-dim)' }}>Declarative React renderer for Three.js.</td>
</tr>
<tr>
<td style={{ color: 'var(--green)' }}>UI State</td>
<td>Zustand</td>
<td style={{ color: 'var(--fg-dim)' }}>Simple local state for panels and UI.</td>
</tr>
<tr>
<td style={{ color: 'var(--accent)' }}>Backend</td>
<td>SpacetimeDB</td>
<td style={{ color: 'var(--fg-dim)' }}>Real-time backend with reducers and subscriptions.</td>
</tr>
<tr>
<td style={{ color: 'var(--fg-dim)' }}>Styling</td>
<td>Tailwind CSS</td>
<td style={{ color: 'var(--fg-dim)' }}>Fast iteration for panels, tables, HUDs.</td>
</tr>
<tr>
<td style={{ color: 'var(--muted)' }}>Auth</td>
<td>SpacetimeDB identity</td>
<td style={{ color: 'var(--fg-dim)' }}>Simple identity first; add auth later.</td>
</tr>
</tbody>
</table>
{/* File structure */}
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
<span className="section-num">TECH-FS</span>
<h2 style={{ margin: 0 }}>Starter File Structure</h2>
</div>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)', fontSize: '0.82rem' }}>
<strong>Note:</strong> The file structure below is the <em>production target</em> for a Vite + React + TypeScript project.
The current prototype uses a simpler layout: <code>js/pages/</code>, <code>js/components/</code>, <code>js/demos/</code>, <code>js/lib/</code>, and <code>css/</code>
a flat structure loaded via Babel standalone without a build step. The prototype structure will migrate to the layout below when moving to Vite.
</div>
<div className="code-block">
<code>
<span className="cm">{'//'} Starter project layout</span><br/>
<span className="kw">/client/src/app</span> <span className="cm">{' //'} App shell and providers</span><br/>
<span className="kw">/client/src/network</span> <span className="cm">{' //'} SpacetimeDB client, subscriptions</span><br/>
<span className="kw">/client/src/game</span> <span className="cm">{' //'} Renderer-independent types, view models</span><br/>
<span className="kw">/client/src/store</span> <span className="cm">{' //'} Zustand stores</span><br/>
<span className="kw">/client/src/renderers/r3f</span> <span className="cm">{'//'} R3F scene, meshes, camera</span><br/>
<span className="kw">/client/src/ui</span> <span className="cm">{' //'} HUD, inventory, market, chat</span><br/>
<span className="kw">/server-spacetime/src</span> <span className="cm">{' //'} SpacetimeDB module, reducers</span><br/>
</code>
</div>
<h3>Packages</h3>
<table className="data-table">
<thead><tr><th>Package</th><th>Purpose</th></tr></thead>
<tbody>
<tr><td>vite, react, react-dom, typescript</td><td>Core frontend.</td></tr>
<tr><td>three, @react-three/fiber, @react-three/drei</td><td>3D scene and helper controls.</td></tr>
<tr><td>zustand</td><td>Local UI/game view state.</td></tr>
<tr><td>tailwindcss</td><td>Panel and HUD styling.</td></tr>
<tr><td>spacetimedb TS client</td><td>Backend connection, reducers, subscriptions.</td></tr>
</tbody>
</table>
</div>
);
}
window.GDD.TechStackPage = TechStackPage;