diff --git a/website/src/components/DownloadButton.astro b/website/src/components/DownloadButton.astro index d255ecf..348912b 100644 --- a/website/src/components/DownloadButton.astro +++ b/website/src/components/DownloadButton.astro @@ -108,13 +108,44 @@ const classes = [ fallback: 'your platform', }; - document.querySelectorAll('.dl-btn').forEach((btn) => { + document.querySelectorAll('.dl-btn').forEach(async (btn) => { const plat = detect(); - const url = btn.dataset[`url${plat.charAt(0).toUpperCase()}${plat.slice(1)}` as 'urlWindows' | 'urlMac' | 'urlLinux' | 'urlFallback']; - if (url) btn.href = url; - const labelEl = btn.querySelector('.dl-label'); const prefix = btn.dataset.labelPrefix ?? 'Download for'; + const labelEl = btn.querySelector('.dl-label'); + const versionEl = btn.querySelector('.dl-version'); + if (labelEl) labelEl.textContent = `${prefix} ${labels[plat]}`; + + // If the button is currently pointing to a fallback (or if we want to ensure freshness), + // we can fetch the latest release directly on the client. + try { + const res = await fetch('https://api.github.com/repos/Soumyadipgithub/SyncSpeak/releases/latest'); + if (res.ok) { + const rel = await res.json(); + const findUrl = (patterns: RegExp[]) => { + for (const re of patterns) { + const hit = rel.assets.find((a: any) => re.test(a.name)); + if (hit) return hit.browser_download_url; + } + return rel.html_url; + }; + + const freshUrls = { + windows: findUrl([/setup\.exe$/i, /\.exe$/i, /\.msi$/i]), + mac: findUrl([/x64\.dmg$/i, /aarch64\.dmg$/i, /\.dmg$/i]), + linux: findUrl([/\.AppImage$/i, /\.deb$/i, /\.rpm$/i]), + fallback: rel.html_url + }; + + const newUrl = freshUrls[plat]; + if (newUrl) btn.href = newUrl; + if (versionEl) versionEl.textContent = rel.tag_name; + } + } catch (e) { + // Fall back to the build-time data-urls already in the HTML + const url = btn.dataset[`url${plat.charAt(0).toUpperCase()}${plat.slice(1)}` as 'urlWindows' | 'urlMac' | 'urlLinux' | 'urlFallback']; + if (url) btn.href = url; + } }); diff --git a/website/src/pages/download.astro b/website/src/pages/download.astro index e554fbd..232dbde 100644 --- a/website/src/pages/download.astro +++ b/website/src/pages/download.astro @@ -6,69 +6,6 @@ import Icon from '../components/Icon.astro'; const title = 'Download Sync Speak'; const description = 'Download the latest Sync Speak build for Windows, macOS, or Linux. Free, open-source, no account required.'; - -type Asset = { name: string; size: number; browser_download_url: string }; -type Release = { - tag_name: string; - name: string; - html_url: string; - published_at: string; - body: string; - assets: Asset[]; -}; - -let release: Release | null = null; -try { - const res = await fetch( - 'https://api.github.com/repos/Soumyadipgithub/SyncSpeak/releases/latest', - { headers: { Accept: 'application/vnd.github+json' } } - ); - if (res.ok) release = (await res.json()) as Release; -} catch { - release = null; -} - -const fmt = (bytes: number) => { - if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`; - if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`; - return `${bytes} B`; -}; - -// Human-friendly asset metadata — keyed by filename pattern. -// Order matters: first match wins within each platform group. -type AssetMeta = { label: string; desc: string; icon: 'window' | 'laptop' | 'terminal' }; - -const assetMap: { re: RegExp; meta: AssetMeta; platform: string }[] = [ - // Windows - { re: /setup\.exe$/i, meta: { label: 'Windows Installer', desc: '.exe — recommended for most users', icon: 'window' }, platform: 'Windows' }, - { re: /\.msi$/i, meta: { label: 'Windows MSI', desc: '.msi — for IT / group policy deployment', icon: 'window' }, platform: 'Windows' }, - // macOS - { re: /aarch64\.dmg$/i, meta: { label: 'macOS (Apple Silicon)', desc: '.dmg — for M1, M2, M3, M4 Macs', icon: 'laptop' }, platform: 'macOS' }, - { re: /x64\.dmg$/i, meta: { label: 'macOS (Intel)', desc: '.dmg — for older Intel-based Macs', icon: 'laptop' }, platform: 'macOS' }, - // Linux - { re: /\.AppImage$/i, meta: { label: 'Linux AppImage', desc: '.AppImage — runs on any distro', icon: 'terminal' }, platform: 'Linux' }, - { re: /\.deb$/i, meta: { label: 'Linux Debian/Ubuntu', desc: '.deb — for apt-based distros', icon: 'terminal' }, platform: 'Linux' }, -]; - -// Build display list: match each release asset to its metadata, skip source archives -const displayAssets = release - ? release.assets - .map((a) => { - const match = assetMap.find((m) => m.re.test(a.name)); - if (!match) return null; // skip source code zips, unknown files - return { ...a, ...match.meta, platform: match.platform }; - }) - .filter(Boolean) as (Asset & AssetMeta & { platform: string })[] - : []; - -// Group by platform for visual sections -const platforms = ['Windows', 'macOS', 'Linux'] as const; -const grouped = platforms.map((p) => ({ - name: p, - assets: displayAssets.filter((a) => a.platform === p), -})); - -const published = release ? new Date(release.published_at).toLocaleDateString() : null; --- @@ -82,13 +19,7 @@ const published = release ? new Date(release.published_at).toLocaleDateString()
- { - release && ( -

- Latest: {release.tag_name} · released {published} -

- ) - } +

@@ -96,41 +27,135 @@ const published = release ? new Date(release.published_at).toLocaleDateString()

Not sure which to pick? Click the big button above — it auto-detects your system.

- { - release && displayAssets.length > 0 ? ( -
- {grouped.filter((g) => g.assets.length > 0).map((g) => ( -
-

{g.name}

-
    - {g.assets.map((a) => ( -
  • - -
    - {a.label} - {a.desc} -
    - {fmt(a.size)} - - Download - -
  • - ))} -
-
- ))} -
- ) : ( -
-

- No releases published yet. Track the first release on - GitHub. -

-
- ) - } + +
+
+

Fetching latest builds from GitHub...

+
+
+ + + + + +

Trusted & Open Source

@@ -204,19 +229,37 @@ npm run dev } .all-assets { margin-bottom: var(--space-2xl); } - .all-assets h2 { margin-bottom: var(--space-s); } - - .platform-groups { + /* Custom responsive spacing as requested */ + :global(.platform-groups) { display: flex; flex-direction: column; - gap: var(--space-xl); + gap: clamp(8px, -4vw + 32px, 50px) !important; /* Mobile */ + margin-top: var(--space-xl); } - .platform-heading { - font-size: 1rem; - font-weight: 600; - color: var(--vibrant-primary); + + @media (min-width: 641px) { + :global(.platform-groups) { + gap: clamp(23px, -1vw + 32px, 50px) !important; /* Desktop */ + } + } + + :global(.platform-heading) { + font-size: 0.75rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.15em; + color: var(--accent-blue); margin-bottom: var(--space-m); - padding-left: var(--space-xs); + display: flex; + align-items: center; + gap: var(--space-m); + opacity: 0.8; + } + .platform-heading::after { + content: ''; + flex: 1; + height: 1px; + background: linear-gradient(90deg, var(--border-blue), transparent); } .assets {