docs(launcher): add Call to Play specification and design reference

Extend the launcher design specification and prototype reference with the
"Call to Play" multiplayer coordination feature and clean up top-bar chrome.

Key additions and changes:
- Spec & Roadmap: Document Call to Play mechanics in SPEC.md, including Play
  Now / Scheduled call flavors, 15-minute check-in windows, top-bar button
  with active count badge, quick-bar ticker stack above grid, overlay modal,
  and per-call group chat. Update design/README.md.
- Components & Logic: Add calltoplay.jsx and ctp-chat.jsx components for call
  creation, status progression, and chat. Extend data.jsx with a mock peer
  roster (PEERS) and helper functions for peer install count tracking.
- Top-bar Cleanup: Move game-folder configuration from top bar into Settings ->
  Library, freeing top-bar space for the Call to Play action button.
- Styling & Layout: Add CTP quick-bar, ticker, badge, card, and chat styles to
  styles.css, and integrate CTP components into launcher.jsx, components.jsx,
  and SoftLAN Launcher.html.

Test Plan:
- `git diff --cached --check` -- passed (no trailing whitespace or conflict markers)
- `cargo check --workspace` -- passed cleanly
- Manual review of staged diff across 9 design/launcher files -- confirmed clean staging
This commit is contained in:
2026-07-21 20:00:07 +02:00
parent 8d54e6e954
commit 8f151e38b4
9 changed files with 1831 additions and 10 deletions
@@ -20,6 +20,12 @@ const Icon = {
check: (p) => <svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="m3 8 3.5 3.5L13 5"/></svg>,
chevron:(p) => <svg viewBox="0 0 16 16" width="11" height="11" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="m4 6 4 4 4-4"/></svg>,
trash: (p) => <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 4.5h10"/><path d="M5.5 4.5V3a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1.5"/><path d="M4.5 4.5 5 13a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1l.5-8.5"/></svg>,
flag: (p) => <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3.5 1.5v13"/><path d="M3.5 2.25c3-1.3 4.5 1.3 7.5 0v6.7c-3 1.3-4.5-1.3-7.5 0"/></svg>,
clock: (p) => <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="8" cy="8" r="6"/><path d="M8 4.5V8l2.5 1.5"/></svg>,
chat: (p) => <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M2 3.5A1.5 1.5 0 0 1 3.5 2h9A1.5 1.5 0 0 1 14 3.5v6a1.5 1.5 0 0 1-1.5 1.5H6.5L3 14v-3h.5A1.5 1.5 0 0 1 2 9.5z"/></svg>,
send: (p) => <svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor" {...p}><path d="M1.5 2 15 8 1.5 14l2.3-6z"/></svg>,
caretUp: (p) => <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="m4 10 4-4 4 4"/></svg>,
caretDown: (p) => <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="m4 6 4 4 4-4"/></svg>,
};
// ────────────────────────────────────────────────────────────────────
@@ -217,6 +223,10 @@ function GameCard({ game, accent, aspect, onOpen }) {
<span>{fmtSize(game.size)}</span>
<span className="card-dot">·</span>
<span>{game.tags[0]}</span>
<span className="card-dot">·</span>
<span className="card-peers" title={`${installedPeersFor(game).length} of ${PEERS.length} peers on the LAN have this installed`}>
<Icon.users/>{installedPeersFor(game).length}/{PEERS.length}
</span>
</div>
<ActionButton state={game.state} accent={accent} full game={game}/>
</div>
@@ -293,6 +303,7 @@ const SORTS = [
{ key: 'size', label: 'Size (largest)' },
{ key: 'recent', label: 'Recently Played' },
{ key: 'state', label: 'Status' },
{ key: 'peers', label: 'Peers with it (most)' },
];
function SortMenu({ value, onChange, accent }) {