README, docs and site: one story (the whole job, no avoidable rejections, the desktop app, your AI, install)

README is a short front page (about 120 lines) with one real visual per section; every former section moved
unchanged into docs/*.md with a docs/README.md index (journey, rejections, getting-started, commands, growth,
studios, store-art, listing-languages, api, skills, mcp, safety, faq, project). The site tells the same story on one
page: the teaser in the hero, real CLI output as text, the desktop app as a section with light/dark captures of eight
screens and the per-system downloads (desktop/latest.json); /desktop/ redirects to /#download. New capture tools:
tools/site-desktop-shots.ts (fixture-mode desktop frames) and tools/site-term-shots.ts (real CLI output as text and
WebP). tests/docs-links.test.ts checks README, docs and site links resolve.
This commit is contained in:
CRTkafa 2026-10-13 03:29:59 +08:00
parent deb218943c
commit 710c3a53d0
63 changed files with 2742 additions and 1782 deletions

View file

@ -1,4 +1,5 @@
// storebound.dev: copy buttons, AI tool tabs, the hero demo (real CLI output) and the How it works stepper. No network, no storage.
// storebound.dev: copy buttons, tabs (AI tools, desktop screens), the teaser player, the demo (real CLI output), the
// How it works stepper and the desktop downloads (reads desktop/latest.json, same origin). No storage, no third parties.
(() => {
const copyText = async text => {
try { await navigator.clipboard.writeText(text); return true; } catch {}
@ -268,4 +269,88 @@
wide.addEventListener('change', layout);
layout();
}
// Desktop downloads: desktop/latest.json (same origin, published with the binaries by tools/desktop-release.ts) fills
// the per-system buttons and the checksum table. Everything from it is set as text, never as HTML; only
// storebound.dev/desktop/dl/ links are used. Before the first release the buttons stay off and say so.
const dl = document.getElementById('download');
if (dl) {
const LABEL = {
'macos-universal-dmg': ['macOS 11 or later', 'Apple silicon and Intel, .dmg'],
'windows-x86_64-nsis': ['Windows 10 / 11', 'x64 installer, installs for your user only'],
'linux-x86_64-appimage': ['Linux', 'x64 AppImage (Ubuntu 22.04+, Debian 12+)'],
'linux-x86_64-deb': ['Linux (Debian, Ubuntu)', 'x64 .deb package'],
};
const ORDER = Object.keys(LABEL);
const el = (tag, attrs, text) => { const e = document.createElement(tag); for (const k in attrs || {}) e.setAttribute(k, attrs[k]); if (text != null) e.textContent = text; return e; };
const mb = n => (n / 1e6).toFixed(1) + ' MB';
const ours = u => { try { const x = new URL(u, location.href); return x.protocol === 'https:' && x.host === 'storebound.dev' && x.pathname.startsWith('/desktop/dl/'); } catch { return false; } };
const system = () => {
const p = ((navigator.userAgentData && navigator.userAgentData.platform) || navigator.platform || '') + ' ' + navigator.userAgent;
if (/android|iphone|ipad|ipod/i.test(p)) return 'mobile';
if (/mac/i.test(p)) return 'macos-universal-dmg';
if (/win/i.test(p)) return 'windows-x86_64-nsis';
if (/linux|x11/i.test(p)) return 'linux-x86_64-appimage';
return null;
};
const status = document.getElementById('dl-status');
const notice = document.getElementById('dl-notice');
const buttons = [...dl.querySelectorAll('[data-dl]')];
const off = text => {
status.textContent = text;
for (const b of buttons) b.querySelector('small').textContent += ' · coming soon';
};
fetch('desktop/latest.json', { cache: 'no-cache' }).then(r => {
if (r.status === 404) throw new Error('none');
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.json();
}).then(m => {
const d = m.downloads || {};
const keys = ORDER.filter(k => d[k] && ours(d[k].url));
if (!keys.length) throw new Error('none');
const here = system();
status.textContent = 'Version ' + m.version + (m.pub_date ? ' · ' + m.pub_date.slice(0, 10) : '') + (here === 'mobile' ? ' · the desktop app runs on macOS, Windows and Linux' : '');
for (const b of buttons) {
const k = b.dataset.dl, x = d[k];
if (!x || !keys.includes(k)) { b.querySelector('small').textContent += ' · not in this release'; continue; }
b.href = x.url;
b.classList.remove('is-off');
b.removeAttribute('aria-disabled');
b.querySelector('small').textContent += ' · ' + mb(x.size) + (k === here ? ' · your system' : '');
if (k === here) { b.classList.add('is-here', 'btn-primary'); b.classList.remove('btn-ghost'); }
}
const unsigned = keys.filter(k => !d[k].codeSigned && (k === 'macos-universal-dmg' || k === 'windows-x86_64-nsis'));
if (unsigned.length) {
const n = el('div', { class: 'dl-notice', role: 'note' });
n.appendChild(el('b', null, 'Unsigned preview. '));
n.appendChild(document.createTextNode('This early build is not yet signed by Apple or Microsoft, so your system asks once before opening it. Check the SHA-256 under "Checksums and signatures" first.'));
const ol = el('ol');
if (unsigned.includes('macos-universal-dmg')) ol.appendChild(el('li', null, 'macOS: drag storebound to Applications and open it; when macOS refuses, open System Settings > Privacy & Security and choose Open Anyway (once).'));
if (unsigned.includes('windows-x86_64-nsis')) ol.appendChild(el('li', null, 'Windows: when SmartScreen shows "Windows protected your PC", choose More info > Run anyway.'));
n.appendChild(ol);
notice.appendChild(n);
}
const rows = document.getElementById('dl-rows');
for (const k of keys) {
const x = d[k];
const tr = el('tr');
const sys = el('td');
sys.appendChild(el('b', null, LABEL[k][0]));
sys.appendChild(el('div', { class: 'small' }, LABEL[k][1] + ((k === 'macos-universal-dmg' || k === 'windows-x86_64-nsis') && !x.codeSigned ? ' · unsigned preview' : '')));
const file = el('td');
file.appendChild(el('a', { class: 'fname', href: x.url }, x.file));
const sub = el('div', { class: 'small' }, mb(x.size) + ' · ');
sub.appendChild(el('a', { href: x.url + '.sig' }, 'signature'));
file.appendChild(sub);
const sha = el('td');
sha.appendChild(el('code', { class: 'sha' }, x.sha256));
tr.append(sys, file, sha);
rows.appendChild(tr);
}
document.getElementById('dl-table').hidden = false;
}).catch(e => {
off(e && e.message === 'none'
? 'The first desktop release is not published yet. Until then the storebound CLI does everything the app shows.'
: 'Could not read the release list (' + (e && e.message) + '). desktop/latest.json has every file and its SHA-256.');
});
}
})();