diff --git a/design/README.md b/design/README.md index 184ba2f..6d75ebf 100644 --- a/design/README.md +++ b/design/README.md @@ -251,20 +251,22 @@ grid-template-areas: ``` - **Primary row** (`.dl-lg-primary`, top-left) — pulse dot + the uppercase live label `DOWNLOADING` in `color-mix(in srgb, var(--accent) 80%, white)`, 13px / 600, `letter-spacing: 0.02em`. This is the only place the word "Downloading" appears in the component. -- **Secondary row** (`.dl-lg-secondary`, bottom-left) — the live stats. 12px, three groups separated by `·` (0.45 opacity): +- **Secondary row** (`.dl-lg-secondary`, bottom-left) — the live stats. 12px, four groups separated by `·` (0.45 opacity): 1. `11.4 GB / 35 GB` (`var(--t-1)` strong + `var(--t-2)` rest) 2. `47.6 MB/s` (`var(--t-1)`) - 3. `8 min left` (`var(--t-2)`) + 3. `[users-icon] from 5 peers` — `.dl-peers`, inline-flex with 5px gap, icon at 0.7 opacity, count bold + tabular-nums in `var(--t-1)`, rest in `var(--t-2)`. Singular/plural switches on `peers === 1`. Hidden entirely when `game.peers` is falsy. Communicates that this is a LAN swarm transfer, not a single-source pull. + 4. `8 min left` (`var(--t-2)`) - **pct column** — large percentage, 20px / 700, `letter-spacing: -0.01em`, `var(--t-1)`. `%` glyph at 12px / 600 / 0.55 opacity. - **cancel column** — 28×28 square, `1px solid var(--bd-2)`, `border-radius: 6px`, X icon. Hover: bg `rgba(239,68,68,0.12)`, border `rgba(239,68,68,0.40)`, text `#fca5a5`. Cancelling reverts the game to its prior state (`local` if any data was kept, `none` otherwise) — dev decides the underlying behavior. **Graceful degradation in narrow modals:** ```css -@container (max-width: 380px) { .dl-lg-secondary .dl-eta, .dl-lg-secondary .dl-sep-eta { display: none; } } +@container (max-width: 380px) { .dl-lg-secondary .dl-eta, .dl-lg-secondary .dl-sep-eta { display: none; } } +@container (max-width: 300px) { .dl-lg-secondary .dl-peers, .dl-lg-secondary .dl-sep-peers { display: none; } } ``` -The ETA drops first if the modal narrows; bytes + speed stay (they're the actionable numbers). The pct/cancel column never collapses. +ETA drops first, then peers; bytes + speed always stay (they're the actionable numbers). The pct/cancel column never collapses. ### Number formatting @@ -287,10 +289,11 @@ type Game = { state: 'installed' | 'local' | 'downloading' | 'none'; progress?: number; // 0–1, only when state === 'downloading' speed?: number; // current throughput in MB/s + peers?: number; // number of LAN peers currently seeding }; ``` -In the real app, `progress` and `speed` come from the download worker (Tauri command emitting events). The mock's `useLiveDownload(game)` hook (in `components.jsx`) is just a placeholder — 600ms `setInterval` advancing `progress` proportional to `speed`, with `speed` smoothed via a low-pass filter and small random drift so the number doesn't look fake. Replace with a `useEffect` that subscribes to your real progress events; the rendering layer needs nothing else. +In the real app, `progress`, `speed`, and `peers` come from the download worker (Tauri command emitting events). The mock's `useLiveDownload(game)` hook (in `components.jsx`) is just a placeholder — 600ms `setInterval` advancing `progress` proportional to `speed`, with `speed` smoothed via a low-pass filter and small random drift so the number doesn't look fake. `peers` is read straight off the game object (static in the mock); in production, push updates as peers join/leave the swarm — the `.dl-peers` chip re-renders silently. Replace the hook with a `useEffect` that subscribes to your real progress events; the rendering layer needs nothing else. Filter changes: - `Local` filter includes `installed` + `local` + `downloading` (in-flight downloads belong on the Local tab — you're managing them). diff --git a/design/design_reference/components.jsx b/design/design_reference/components.jsx index 64e6cb8..647426b 100644 --- a/design/design_reference/components.jsx +++ b/design/design_reference/components.jsx @@ -159,6 +159,15 @@ function DownloadProgress({ game, accent, size = 'md', full = false }) { · {fmtSpeed(speed)} + {game.peers > 0 && ( + + · + + + from {game.peers} {game.peers === 1 ? 'peer' : 'peers'} + + + )} · {fmtEta(etaSec)} left diff --git a/design/design_reference/data.jsx b/design/design_reference/data.jsx index 15032ee..9d95a5d 100644 --- a/design/design_reference/data.jsx +++ b/design/design_reference/data.jsx @@ -18,7 +18,7 @@ const GAMES = [ { id: 'avp', title: 'Aliens vs. Predator', size: 35.0, version: '2019.10.01', desc: "Three campaigns, three nightmares. Be the alien stalking the dark, the predator hunting both, or the marine just trying to make it home with a working flashlight.", - state: 'downloading', progress: 0.32, speed: 49.4, players: '2–16', tags: ['FPS', 'Horror', 'Multiplayer'], + state: 'downloading', progress: 0.32, speed: 49.4, peers: 5, players: '2–16', tags: ['FPS', 'Horror', 'Multiplayer'], cover: { c1: '#064e3b', c2: '#020617', accent: '#34d399', mood: 'dark' }, }, { @@ -120,7 +120,7 @@ const GAMES = [ { id: 'quake3', title: 'Quake III Arena', size: 0.5, version: '2010.08.15', desc: "Arena FPS in its most distilled form. Rocket jumps, rail-gun duels, and a netcode that's still the benchmark.", - state: 'downloading', progress: 0.71, speed: 12.8, players: '2–16', tags: ['FPS', 'Arena', 'LAN'], + state: 'downloading', progress: 0.71, speed: 12.8, peers: 3, players: '2–16', tags: ['FPS', 'Arena', 'LAN'], cover: { c1: '#7f1d1d', c2: '#0a0a0a', accent: '#fbbf24', mood: 'dark' }, }, { diff --git a/design/design_reference/styles.css b/design/design_reference/styles.css index 6c98a32..059d97b 100644 --- a/design/design_reference/styles.css +++ b/design/design_reference/styles.css @@ -784,6 +784,12 @@ } .dl-lg-secondary .dl-bytes .dl-of { color: var(--t-2); font-weight: 500; } .dl-lg-secondary .dl-speed { color: var(--t-1); font-weight: 600; white-space: nowrap; } +.dl-lg-secondary .dl-peers { + display: inline-flex; align-items: center; gap: 5px; + color: var(--t-2); white-space: nowrap; +} +.dl-lg-secondary .dl-peers strong { color: var(--t-1); font-weight: 600; font-variant-numeric: tabular-nums; } +.dl-lg-secondary .dl-peers svg { opacity: 0.7; } .dl-lg-secondary .dl-eta { white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis; } .dl-sep { opacity: 0.45; } @@ -792,6 +798,11 @@ .dl-lg-secondary .dl-eta, .dl-lg-secondary .dl-sep-eta { display: none; } } +/* Even narrower: drop peers too, keep size + speed */ +@container (max-width: 300px) { + .dl-lg-secondary .dl-peers, + .dl-lg-secondary .dl-sep-peers { display: none; } +} .dl-lg-pct { grid-area: pct;