Skip to content

Commit a89093c

Browse files
committed
Refine website download UX and macOS warning dialog
1 parent 6a0b923 commit a89093c

2 files changed

Lines changed: 447 additions & 20 deletions

File tree

website/src/components/NavHero.astro

Lines changed: 273 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
---
22
// Stitch baseline: dark hero screen codegen, adapted for Astro + Fontsource + truthful content
33
const GITHUB = 'https://github.com/InterestingSoftware/SproutGit';
4+
const RELEASES_API = 'https://api.github.com/repos/InterestingSoftware/SproutGit/releases/latest';
5+
const RELEASES_PAGE = `${GITHUB}/releases/latest`;
46
---
57

68
<!-- Nav -->
79
<header class="fixed top-0 left-0 right-0 z-50 transition-all duration-300" id="site-nav">
810
<nav class="flex justify-between items-center w-full px-12 py-5 max-w-screen-2xl mx-auto">
9-
<a href="/" class="flex items-center gap-2.5 no-underline text-[var(--sg-text)]">
11+
<a href="/" class="flex items-center gap-2.5 no-underline text-(--sg-text)">
1012
<img src="/logo.svg" alt="SproutGit logo" width="28" height="28" class="w-7 h-7"/>
1113
<span class="font-bold text-lg tracking-tight" style="letter-spacing:-0.025em;">SproutGit</span>
1214
</a>
1315

1416
<div class="hidden md:flex items-center gap-10">
15-
<a href="#why" class="text-sm font-medium text-[var(--sg-text-dim)] hover:text-[var(--sg-text)] transition-colors">Why worktrees?</a>
16-
<a href="#features" class="text-sm font-medium text-[var(--sg-text-dim)] hover:text-[var(--sg-text)] transition-colors">Features</a>
17+
<a href="#why" class="text-sm font-medium text-(--sg-text-dim) hover:text-(--sg-text) transition-colors">Why worktrees?</a>
18+
<a href="#features" class="text-sm font-medium text-(--sg-text-dim) hover:text-(--sg-text) transition-colors">Features</a>
1719
<a href={GITHUB} target="_blank" rel="noopener noreferrer"
18-
class="flex items-center gap-2 text-sm font-medium px-4 py-2 rounded-lg border border-[var(--sg-border)] text-[var(--sg-text-dim)] hover:border-[var(--sg-primary)] hover:text-[var(--sg-primary)] transition-all">
20+
class="flex items-center gap-2 text-sm font-medium px-4 py-2 rounded-lg border border-(--sg-border) text-(--sg-text-dim) hover:border-(--sg-primary) hover:text-(--sg-primary) transition-all">
1921
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
2022
<path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.603-3.369-1.342-3.369-1.342-.454-1.155-1.11-1.462-1.11-1.462-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0 1 12 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.163 22 16.418 22 12c0-5.523-4.477-10-10-10z"/>
2123
</svg>
@@ -54,7 +56,7 @@ const GITHUB = 'https://github.com/InterestingSoftware/SproutGit';
5456
</div>
5557

5658
<!-- Headline — Stitch layout with gradient text -->
57-
<h1 data-animate class="font-bold leading-[0.92] tracking-tighter text-[var(--sg-text)]"
59+
<h1 data-animate class="font-bold leading-[0.92] tracking-tighter text-(--sg-text)"
5860
style="font-size: clamp(3rem, 7vw, 5.5rem); letter-spacing: -0.045em;">
5961
Git built for<br>
6062
<span style="background: linear-gradient(135deg, var(--sg-primary) 0%, var(--sg-mint) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">
@@ -68,22 +70,86 @@ const GITHUB = 'https://github.com/InterestingSoftware/SproutGit';
6870
</p>
6971

7072
<!-- CTAs -->
71-
<div data-animate class="flex flex-wrap gap-4 mt-2">
72-
<a href={GITHUB} target="_blank" rel="noopener noreferrer"
73-
class="flex items-center gap-3 px-8 py-4 rounded-xl text-lg font-bold text-white transition-all hover:-translate-y-0.5"
74-
style="background: var(--sg-primary); box-shadow: 0 0 0 0 color-mix(in srgb, var(--sg-primary) 30%, transparent);">
75-
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
76-
<path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.603-3.369-1.342-3.369-1.342-.454-1.155-1.11-1.462-1.11-1.462-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0 1 12 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.163 22 16.418 22 12c0-5.523-4.477-10-10-10z"/>
77-
</svg>
78-
View on GitHub
79-
</a>
80-
<a href="#features"
81-
class="flex items-center gap-2 px-8 py-4 rounded-xl text-lg font-bold transition-all"
82-
style="color: var(--sg-primary); border: 1.5px solid color-mix(in srgb, var(--sg-primary) 30%, transparent);">
83-
See features ↓
84-
</a>
73+
<div data-animate class="hero-downloads mt-2" data-download-root data-api={RELEASES_API} data-fallback={RELEASES_PAGE}>
74+
<div class="flex flex-wrap gap-4">
75+
<a href={RELEASES_PAGE}
76+
class="flex items-center gap-3 px-8 py-4 rounded-xl text-lg font-bold text-white transition-all hover:-translate-y-0.5"
77+
style="background: var(--sg-primary); box-shadow: 0 0 0 0 color-mix(in srgb, var(--sg-primary) 30%, transparent);"
78+
data-primary-download>
79+
Download preview
80+
</a>
81+
<a href="#features"
82+
class="flex items-center gap-2 px-8 py-4 rounded-xl text-lg font-bold transition-all"
83+
style="color: var(--sg-primary); border: 1.5px solid color-mix(in srgb, var(--sg-primary) 30%, transparent);">
84+
See features ↓
85+
</a>
86+
<div class="hero-download-menu-wrap">
87+
<button type="button"
88+
class="download-platforms-trigger flex items-center gap-2 px-6 py-4 rounded-xl text-base font-bold transition-all"
89+
style="color: var(--sg-text); border: 1.5px solid color-mix(in srgb, var(--sg-border) 85%, transparent); background: color-mix(in srgb, var(--sg-surface) 72%, transparent);"
90+
aria-expanded="false"
91+
aria-controls="platform-downloads-menu"
92+
data-platforms-trigger>
93+
Other platforms
94+
<span aria-hidden="true">▾</span>
95+
</button>
96+
<div id="platform-downloads-menu" class="download-platforms-menu" hidden data-platforms-menu>
97+
<a href={RELEASES_PAGE} target="_blank" rel="noopener noreferrer" data-platform-link="macos">macOS</a>
98+
<a href={RELEASES_PAGE} target="_blank" rel="noopener noreferrer" data-platform-link="windows">Windows</a>
99+
<a href={RELEASES_PAGE} target="_blank" rel="noopener noreferrer" data-platform-link="linux">Linux</a>
100+
</div>
101+
</div>
102+
</div>
103+
104+
<div class="download-links-row">
105+
<span class="download-links-label" data-download-status>Loading latest release…</span>
106+
<a href={GITHUB} target="_blank" rel="noopener noreferrer">GitHub</a>
107+
<a href={RELEASES_PAGE} target="_blank" rel="noopener noreferrer">All releases</a>
108+
<a href={RELEASES_PAGE} target="_blank" rel="noopener noreferrer" data-inline-platform-link="macos">macOS</a>
109+
<a href={RELEASES_PAGE} target="_blank" rel="noopener noreferrer" data-inline-platform-link="windows">Windows</a>
110+
<a href={RELEASES_PAGE} target="_blank" rel="noopener noreferrer" data-inline-platform-link="linux">Linux</a>
111+
</div>
85112
</div>
86113

114+
<dialog class="download-warning-dialog" data-mac-dialog>
115+
<div class="download-warning-panel">
116+
<div class="download-warning-header">
117+
<div>
118+
<h2>Unsigned macOS preview build</h2>
119+
</div>
120+
<button type="button" class="download-warning-close" aria-label="Close download warning" data-close-mac-dialog>✕</button>
121+
</div>
122+
123+
<p>
124+
While we are in early development, SproutGit for macOS is not signed or notarized yet.
125+
Because of that, macOS may show warnings such as "can’t be opened" or "is damaged and can’t be opened," even when downloaded from our official release page.
126+
</p>
127+
<p>
128+
By launch, we plan to ship signed builds so this warning goes away.
129+
For now, if you downloaded SproutGit from our official site or GitHub releases, follow the steps below to allow this app.
130+
</p>
131+
132+
<ol>
133+
<li>Open the downloaded `.dmg` and drag SproutGit into Applications.</li>
134+
<li>In Finder, open Applications and Control-click SproutGit.</li>
135+
<li>Choose Open, then confirm Open again in the system dialog.</li>
136+
<li>If it still says "is damaged", remove quarantine in Terminal: <code>xattr -dr com.apple.quarantine /Applications/SproutGit.app</code></li>
137+
<li>Then try opening SproutGit again (or run: <code>open /Applications/SproutGit.app</code>).</li>
138+
</ol>
139+
140+
<div class="download-warning-actions">
141+
<button type="button" class="download-warning-secondary" data-close-mac-dialog>Cancel</button>
142+
<a href={RELEASES_PAGE}
143+
target="_blank"
144+
rel="noopener noreferrer"
145+
class="download-warning-primary"
146+
data-mac-download-link>
147+
Continue to download
148+
</a>
149+
</div>
150+
</div>
151+
</dialog>
152+
87153
<!-- Meta strip -->
88154
<div data-animate class="flex items-center gap-8 mt-6 opacity-60">
89155
<div class="flex flex-col">
@@ -99,7 +165,7 @@ const GITHUB = 'https://github.com/InterestingSoftware/SproutGit';
99165
</div>
100166

101167
<!-- Right: Graph panel — adapted from Stitch SVG + floating cards -->
102-
<div class="lg:col-span-5 relative flex justify-center items-center h-[560px]" data-animate data-graph>
168+
<div class="lg:col-span-5 relative flex justify-center items-center h-140" data-animate data-graph>
103169

104170
<!-- Panel -->
105171
<div class="absolute inset-0 rounded-3xl overflow-hidden border flex items-center justify-center p-6"
@@ -192,6 +258,193 @@ const GITHUB = 'https://github.com/InterestingSoftware/SproutGit';
192258
</section>
193259
</main>
194260

261+
<script is:inline>
262+
(() => {
263+
const root = document.querySelector('[data-download-root]');
264+
if (!root) return;
265+
266+
const apiUrl = root.getAttribute('data-api') || 'https://api.github.com/repos/InterestingSoftware/SproutGit/releases/latest';
267+
const fallbackUrl = root.getAttribute('data-fallback') || 'https://github.com/InterestingSoftware/SproutGit/releases/latest';
268+
const primaryLink = root.querySelector('[data-primary-download]');
269+
const trigger = root.querySelector('[data-platforms-trigger]');
270+
const menu = root.querySelector('[data-platforms-menu]');
271+
const status = root.querySelector('[data-download-status]');
272+
const dialog = document.querySelector('[data-mac-dialog]');
273+
const macDownloadLink = document.querySelector('[data-mac-download-link]');
274+
const closeButtons = document.querySelectorAll('[data-close-mac-dialog]');
275+
276+
const menuLinks = {
277+
macos: root.querySelector('[data-platform-link="macos"]'),
278+
windows: root.querySelector('[data-platform-link="windows"]'),
279+
linux: root.querySelector('[data-platform-link="linux"]'),
280+
};
281+
282+
const inlineLinks = {
283+
macos: root.querySelector('[data-inline-platform-link="macos"]'),
284+
windows: root.querySelector('[data-inline-platform-link="windows"]'),
285+
linux: root.querySelector('[data-inline-platform-link="linux"]'),
286+
};
287+
288+
const detectOS = () => {
289+
const rawUserAgentData = 'userAgentData' in navigator ? navigator['userAgentData'] : undefined;
290+
/** @type {{ platform?: string } | undefined} */
291+
const userAgentData = rawUserAgentData && typeof rawUserAgentData === 'object' ? rawUserAgentData : undefined;
292+
const platform = (userAgentData && typeof userAgentData.platform === 'string'
293+
? userAgentData.platform
294+
: navigator.platform || navigator.userAgent || '').toLowerCase();
295+
296+
if (platform.includes('mac')) return 'macos';
297+
if (platform.includes('win')) return 'windows';
298+
if (platform.includes('linux') || platform.includes('x11')) return 'linux';
299+
return 'unknown';
300+
};
301+
302+
const assetMatchers = {
303+
macos: (/** @type {{ name: string }} */ asset) => asset.name.endsWith('.dmg') && /aarch64|arm64/i.test(asset.name),
304+
windows: (/** @type {{ name: string }} */ asset) => asset.name.endsWith('.msi'),
305+
linux: (/** @type {{ name: string }} */ asset) => asset.name.endsWith('.AppImage'),
306+
};
307+
308+
const platformLabels = {
309+
macos: 'macOS',
310+
windows: 'Windows',
311+
linux: 'Linux',
312+
unknown: 'your OS',
313+
};
314+
315+
const links = {
316+
macos: fallbackUrl,
317+
windows: fallbackUrl,
318+
linux: fallbackUrl,
319+
};
320+
321+
const recommended = detectOS();
322+
323+
const setStatus = (/** @type {string} */ message) => {
324+
if (status) status.textContent = message;
325+
};
326+
327+
const setPrimaryLink = (/** @type {string} */ href, /** @type {string} */ label) => {
328+
if (!(primaryLink instanceof HTMLAnchorElement)) return;
329+
primaryLink.href = href;
330+
primaryLink.textContent = label;
331+
};
332+
333+
const syncLinks = () => {
334+
if (menuLinks.macos instanceof HTMLAnchorElement) menuLinks.macos.href = links.macos;
335+
if (menuLinks.windows instanceof HTMLAnchorElement) menuLinks.windows.href = links.windows;
336+
if (menuLinks.linux instanceof HTMLAnchorElement) menuLinks.linux.href = links.linux;
337+
338+
if (inlineLinks.macos instanceof HTMLAnchorElement) inlineLinks.macos.href = links.macos;
339+
if (inlineLinks.windows instanceof HTMLAnchorElement) inlineLinks.windows.href = links.windows;
340+
if (inlineLinks.linux instanceof HTMLAnchorElement) inlineLinks.linux.href = links.linux;
341+
342+
if (macDownloadLink instanceof HTMLAnchorElement) {
343+
macDownloadLink.href = links.macos;
344+
}
345+
};
346+
347+
const closeMenu = () => {
348+
if (!(trigger instanceof HTMLButtonElement) || !(menu instanceof HTMLElement)) return;
349+
menu.hidden = true;
350+
trigger.setAttribute('aria-expanded', 'false');
351+
};
352+
353+
if (trigger instanceof HTMLButtonElement && menu instanceof HTMLElement) {
354+
trigger.addEventListener('click', () => {
355+
const isHidden = menu.hidden;
356+
menu.hidden = !isHidden;
357+
trigger.setAttribute('aria-expanded', isHidden ? 'true' : 'false');
358+
});
359+
360+
document.addEventListener('click', (event) => {
361+
if (!(event.target instanceof Node)) {
362+
closeMenu();
363+
return;
364+
}
365+
366+
if (!root.contains(event.target)) closeMenu();
367+
});
368+
369+
document.addEventListener('keydown', (event) => {
370+
if (event.key === 'Escape') closeMenu();
371+
});
372+
}
373+
374+
closeButtons.forEach((button) => {
375+
button.addEventListener('click', () => {
376+
if (dialog instanceof HTMLDialogElement) {
377+
dialog.close();
378+
}
379+
});
380+
});
381+
382+
if (dialog instanceof HTMLDialogElement) {
383+
dialog.addEventListener('click', (event) => {
384+
const rect = dialog.getBoundingClientRect();
385+
const inside =
386+
rect.top <= event.clientY &&
387+
event.clientY <= rect.top + rect.height &&
388+
rect.left <= event.clientX &&
389+
event.clientX <= rect.left + rect.width;
390+
391+
if (!inside) {
392+
dialog.close();
393+
}
394+
});
395+
}
396+
397+
if (primaryLink instanceof HTMLAnchorElement) {
398+
primaryLink.addEventListener('click', (event) => {
399+
if (recommended !== 'macos') return;
400+
if (!(dialog instanceof HTMLDialogElement)) return;
401+
event.preventDefault();
402+
dialog.showModal();
403+
});
404+
}
405+
406+
syncLinks();
407+
setPrimaryLink(fallbackUrl, recommended === 'unknown' ? 'Download preview' : `Download for ${platformLabels[recommended]}`);
408+
setStatus('Loading latest release…');
409+
410+
fetch(apiUrl, {
411+
headers: {
412+
Accept: 'application/vnd.github+json',
413+
},
414+
})
415+
.then((response) => {
416+
if (!response.ok) {
417+
throw new Error(`GitHub release lookup failed with status ${response.status}`);
418+
}
419+
return response.json();
420+
})
421+
.then((release) => {
422+
const assets = Array.isArray(release.assets) ? release.assets : [];
423+
links.macos = assets.find(assetMatchers.macos)?.browser_download_url || fallbackUrl;
424+
links.windows = assets.find(assetMatchers.windows)?.browser_download_url || fallbackUrl;
425+
links.linux = assets.find(assetMatchers.linux)?.browser_download_url || fallbackUrl;
426+
syncLinks();
427+
428+
const primaryHref = recommended === 'macos'
429+
? links.macos
430+
: recommended === 'windows'
431+
? links.windows
432+
: recommended === 'linux'
433+
? links.linux
434+
: fallbackUrl;
435+
const primaryLabel = recommended === 'unknown' ? 'Download preview' : `Download for ${platformLabels[recommended]}`;
436+
437+
setPrimaryLink(primaryHref, primaryLabel);
438+
setStatus(`Latest release ${release.tag_name || 'available'}.`);
439+
})
440+
.catch(() => {
441+
syncLinks();
442+
setPrimaryLink(fallbackUrl, recommended === 'unknown' ? 'Download preview' : `Download for ${platformLabels[recommended]}`);
443+
setStatus('Using GitHub release links.');
444+
});
445+
})();
446+
</script>
447+
195448
<style>
196449
@media (max-width: 768px) {
197450
.lg\:col-span-5 { display: none; }

0 commit comments

Comments
 (0)