site: first-party, cookieless visit stats (self-hosted Umami at storebound.dev/s/, Do Not Track respected) with events for copy buttons, desktop downloads per OS, the teaser (play, 50 %, complete) and outbound docs / git / npm links; a privacy note in the footer; docs/site-analytics.md and tools/vds/ configs to rebuild the server side; scrub allows a page's public stats website id
This commit is contained in:
parent
83c14d3a8e
commit
34c2fa22e5
7 changed files with 129 additions and 3 deletions
|
|
@ -9,6 +9,7 @@
|
|||
<meta http-equiv="refresh" content="0; url=../#download">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<link rel="icon" href="../favicon.ico?v=2" sizes="16x16 32x32 48x48">
|
||||
<script defer src="https://storebound.dev/s/p.js" data-website-id="91ce17cb-8f26-451f-92be-752c805238ee" data-host-url="https://storebound.dev/s" data-domains="storebound.dev" data-do-not-track="true"></script>
|
||||
<script>location.replace('../#download')</script>
|
||||
<style>body { font: 16px/1.6 ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 40px 16px; background: #f8fafc; color: #0f172a; } a { color: #4f46e5; } @media (prefers-color-scheme: dark) { body { background: #0b1120; color: #f1f5f9; } a { color: #818cf8; } }</style>
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@
|
|||
<meta name="twitter:image" content="https://storebound.dev/og.png?v=2">
|
||||
<link rel="stylesheet" href="style.css?v=20261013c">
|
||||
<script>document.documentElement.classList.add('js')</script>
|
||||
<script src="main.js?v=20261013b" defer></script>
|
||||
<script defer src="https://storebound.dev/s/p.js" data-website-id="91ce17cb-8f26-451f-92be-752c805238ee" data-host-url="https://storebound.dev/s" data-domains="storebound.dev" data-do-not-track="true"></script>
|
||||
<script src="main.js?v=20261014a" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip" href="#main">Skip to content</a>
|
||||
|
|
@ -887,7 +888,7 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99
|
|||
<img class="mark" src="img/logo-mark.png?v=2" srcset="img/logo-mark.png?v=2 1x, img/logo-mark@2x.png?v=2 2x" width="28" height="28" alt="storebound logo">
|
||||
<span>storebound</span>
|
||||
</a>
|
||||
<p class="fine">MIT · made by CRTkafa · not affiliated with Google or Apple.<br>App Store is a trademark of Apple Inc.; Google Play is a trademark of Google LLC.</p>
|
||||
<p class="fine">MIT · made by CRTkafa · not affiliated with Google or Apple.<br>App Store is a trademark of Apple Inc.; Google Play is a trademark of Google LLC.<br>Privacy: this site counts visits with self-hosted Umami. No cookies, no personal data, nothing shared with third parties, and Do Not Track is respected.</p>
|
||||
</div>
|
||||
<nav aria-label="Footer">
|
||||
<a href="https://git.crt.fyi/CRT/storebound" target="_blank" rel="noopener">git repo<span class="ext" aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a>
|
||||
|
|
|
|||
24
site/main.js
24
site/main.js
|
|
@ -1,5 +1,28 @@
|
|||
// 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.
|
||||
// Stats: self-hosted Umami at /s/ (cookieless, Do Not Track respected); ev() is a no-op when the script is blocked or off.
|
||||
(() => {
|
||||
const ev = (name, data) => { try { window.umami?.track(name, data); } catch {} };
|
||||
document.addEventListener('click', e => {
|
||||
const a = e.target.closest?.('a[href]');
|
||||
if (!a) return;
|
||||
if (a.dataset.dl) { ev('desktop-download', { os: a.dataset.dl.split('-')[0], file: a.dataset.dl }); return; }
|
||||
let u; try { u = new URL(a.href, location.href); } catch { return; }
|
||||
if (u.host === location.host) return;
|
||||
const docs = u.host === 'git.crt.fyi' && /\/(docs\/|README|CHANGELOG|SECURITY)/.test(u.pathname);
|
||||
const kind = docs ? 'docs' : u.host === 'git.crt.fyi' ? 'git' : u.host.endsWith('npmjs.com') ? 'npm' : 'other';
|
||||
ev('outbound', { kind, host: u.host, path: u.pathname.slice(0, 120) });
|
||||
}, true);
|
||||
const vid = document.querySelector('#teaser video');
|
||||
if (vid) {
|
||||
const seen = new Set();
|
||||
const once = (k, d) => { if (!seen.has(k)) { seen.add(k); ev(k, d); } };
|
||||
vid.addEventListener('play', () => once('teaser-play'));
|
||||
vid.addEventListener('timeupdate', () => { if (vid.duration && vid.currentTime / vid.duration >= 0.5) once('teaser-50'); });
|
||||
vid.addEventListener('ended', () => once('teaser-complete'));
|
||||
}
|
||||
window.__sbEv = ev;
|
||||
})();
|
||||
(() => {
|
||||
const copyText = async text => {
|
||||
try { await navigator.clipboard.writeText(text); return true; } catch {}
|
||||
|
|
@ -15,6 +38,7 @@
|
|||
// Drop "# comment" tails so the copied commands run as is.
|
||||
const text = btn.dataset.copy ?? (target ? target.innerText.split('\n').map(l => l.replace(/\s+#.*$/, '')).join('\n').trim() : '');
|
||||
const ok = await copyText(text);
|
||||
window.__sbEv?.('copy', { what: (btn.dataset.copy ?? btn.dataset.copyTarget ?? '').slice(0, 80) });
|
||||
btn.textContent = ok ? 'Copied' : 'Press Ctrl+C';
|
||||
btn.classList.toggle('done', ok);
|
||||
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('done'); }, 1600);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue