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:
parent
deb218943c
commit
710c3a53d0
63 changed files with 2742 additions and 1782 deletions
87
site/main.js
87
site/main.js
|
|
@ -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.');
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue