Skip to content

app/about.ts

88 lines · 5.7 KB · TypeScript
Raw
import { BUILD } from './build-info.ts'
import { pwaScript } from './pwa.ts'
import { baseCss, COLORS, esc, headTags, type PageCtx } from './ui.ts'

const aboutScript = String.raw`
(() => {
  const $ = (id) => document.getElementById(id);
  const built = $('builtAt'), iso = built.getAttribute('datetime');
  function age(ms) { const s = Math.max(0, (Date.now() - ms) / 1000);
    const u = [[86400 * 365, 'year'], [86400 * 30, 'month'], [86400, 'day'], [3600, 'hour'], [60, 'minute']];
    for (const [n, w] of u) if (s >= n) { const v = Math.floor(s / n); return v + ' ' + w + (v === 1 ? '' : 's') + ' ago'; }
    return 'just now'; }
  const d = new Date(iso);
  if (!isNaN(d)) built.textContent = d.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }) + ' (' + age(d.getTime()) + ')';
  const btn = $('checkBtn'), out = $('checkResult');
  btn.addEventListener('click', async () => {
    btn.disabled = true; btn.textContent = 'CHECKING…'; out.textContent = '';
    const r = window.__pwa ? await window.__pwa.check() : { ok: false, reason: 'unsupported' };
    btn.disabled = false; btn.textContent = 'CHECK FOR UPDATES';
    out.textContent = !r.ok ? (r.reason === 'offline' ? 'OFFLINE. TRY AGAIN WHEN CONNECTED.' : 'UPDATES NEED A BROWSER WITH SERVICE WORKERS.')
      : r.waiting ? 'NEW VERSION READY. TAP UPDATE BELOW.' : 'UP TO DATE.';
  });
  if (window.__pwa) window.__pwa.onChange((s) => { $('swState').textContent = s.registered ? 'READY FOR OFFLINE PLAY' : 'NOT INSTALLED YET'; });
})();
`

export function renderAbout(ctx: PageCtx) {
	const commit = BUILD.sourceCommit
	return `<!doctype html>
<html lang="en" data-app-base="${esc(ctx.appBase)}" data-build="${esc(BUILD.buildId)}"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>About · Asteroids</title>
${headTags(ctx, 'About · Asteroids')}
<style>
${baseCss}
body{min-height:100%;background:radial-gradient(ellipse at 50% 0%,#0d1630,${COLORS.bg} 70%) fixed}
main{max-width:640px;margin:0 auto;padding:max(24px,env(safe-area-inset-top)) 18px 120px}
h1{font-size:clamp(28px,7vw,44px);letter-spacing:.3em;margin:8px 0 4px;font-weight:700;text-shadow:0 0 8px ${COLORS.glow}}
h2{font-size:13px;letter-spacing:.24em;margin:28px 0 10px;color:${COLORS.glow};font-weight:700}
p,li,dd{opacity:.85;font-size:13px}ul{padding-left:18px;margin:0}li{margin:4px 0}
dl{display:grid;grid-template-columns:max-content 1fr;gap:6px 14px;margin:0;font-size:13px}dt{opacity:.6;letter-spacing:.12em}dd{margin:0;word-break:break-all}
kbd{display:inline-block;min-width:2.2ch;padding:1px 6px;border:1px solid #e8f1ff55;border-radius:4px;font:inherit;font-size:12px;text-align:center}
.row{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.sub{opacity:.6;letter-spacing:.2em;font-size:12px;margin:0}
#checkResult{min-height:1.5em;letter-spacing:.12em;font-size:12px;margin:8px 0 0}
a{color:${COLORS.ink}}
@media (max-width:480px){main{padding-left:max(14px,env(safe-area-inset-left));padding-right:max(14px,env(safe-area-inset-right))}h1{letter-spacing:.2em}dl{grid-template-columns:1fr;gap:2px}dt{margin-top:8px}.btn{font-size:12px;padding:9px 12px}}
</style></head><body>
<main>
<div class="row" style="justify-content:space-between"><a class="btn" href="${esc(ctx.appBase)}/">◀ PLAY</a><button id="installBtn" class="btn hot" type="button" hidden>INSTALL</button></div>
<h1>ASTEROIDS</h1>
<p class="sub">VECTOR ARCADE · KEYBOARD OR TOUCH</p>

<h2>VERSION</h2>
<dl>
<dt>BUILD</dt><dd id="buildId">${esc(BUILD.buildId)}</dd>
<dt>COMMIT</dt><dd>${commit ? `<span id="commit" title="${esc(commit)}">${esc(commit.slice(0, 7))}</span> <span style="opacity:.6">(the commit this build was made from)</span>` : 'unknown'}</dd>
<dt>BUILT</dt><dd><time id="builtAt" datetime="${esc(BUILD.builtAt)}">${esc(BUILD.builtAt)}</time></dd>
<dt>OFFLINE</dt><dd id="swState">CHECKING…</dd>
</dl>

<h2>UPDATES</h2>
<p>Asteroids saves itself on your device so it loads instantly and plays offline. When a new version is published, it downloads in the background and an <b>UPDATE AVAILABLE</b> message appears. Tap <b>UPDATE</b> to switch now, or keep playing and it takes over next time you open the game. It checks when you open it, when you come back to the tab, and every 30 minutes.</p>
<div class="row"><button id="checkBtn" class="btn" type="button">CHECK FOR UPDATES</button></div>
<p id="checkResult" role="status"></p>

<h2>CONTROLS</h2>
<ul>
<li><kbd>←</kbd> <kbd>→</kbd> or <kbd>A</kbd> <kbd>D</kbd> rotate</li>
<li><kbd>↑</kbd> or <kbd>W</kbd> thrust</li>
<li><kbd>Space</kbd> fire · <kbd>Space</kbd> or <kbd>Enter</kbd> start / restart</li>
<li><kbd>S</kbd> sound effects on/off · <kbd>M</kbd> music on/off</li>
<li>Touch: put your left thumb anywhere on the left side and drag. The ship turns toward the stick and thrusts harder the further you push. Hold <b>FIRE</b> on the right while steering. Tap to start.</li>
<li>Extra life every 10,000 points. Your high score and sound settings stay on this device.</li>
</ul>

<h2>CREDITS</h2>
<ul>
<li>Inspired by Atari's 1979 arcade classic <i>Asteroids</i> (Lyle Rains and Ed Logg). This is an unofficial fan remake.</li>
<li>Made by Kent C. Dodds with Kody. Vector graphics drawn on canvas; every sound and the chiptune are synthesized live with the Web Audio API.</li>
</ul>
<div class="row" style="margin-top:28px"><a class="btn" href="${esc(ctx.appBase)}/">◀ BACK TO THE GAME</a><a class="btn" href="https://kody.codes/@kentcdodds/asteroids" target="_blank" rel="noopener">KODY PACKAGE ↗</a></div>
</main>
<div id="toast" role="status" hidden>UPDATE AVAILABLE <button class="btn hot" type="button" data-update>UPDATE</button><button class="btn" type="button" data-later>LATER</button></div>
<script>${pwaScript}</script>
<script>${aboutScript}</script>
</body></html>`
}