This commit is contained in:
pj committed 2026-06-22 13:03:11 -04:00
1 parent b4097cc47f
commit 61bd7cf6e5
6 files changed
+94 -121

No files matched your search

+4 -1
View File
@@ -1,10 +1,12 @@
--- ---
import { site, platforms } from "../data/site"; import { site, platforms } from "../data/site";
import { getRelease } from "../data/release";
interface Props { interface Props {
large?: boolean; large?: boolean;
} }
const { large = false } = Astro.props; const { large = false } = Astro.props;
const release = await getRelease();
--- ---
<div class="dl"> <div class="dl">
@@ -26,7 +28,7 @@ const { large = false } = Astro.props;
platforms.map((p) => ( platforms.map((p) => (
<a <a
class="dl__chip" class="dl__chip"
href={site.releasesUrl} href={release.urls[p.id]}
data-download-os={p.id} data-download-os={p.id}
rel="noopener" rel="noopener"
download download
@@ -57,6 +59,7 @@ const { large = false } = Astro.props;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex: none;
width: 44px; width: 44px;
height: 44px; height: 44px;
background: #ffffff; background: #ffffff;
+28 -42
View File
@@ -1,14 +1,13 @@
--- ---
import { site } from "../data/site"; import { site, platforms } from "../data/site";
import { getRelease } from "../data/release";
const { repo, releasesUrl } = site; const { repo, releasesUrl } = site;
const { urls, version } = await getRelease();
const matches = Object.fromEntries(platforms.map((p) => [p.id, p.match]));
--- ---
<script define:vars={{ repo, releasesUrl }}> <script define:vars={{ repo, releasesUrl, urls, version, matches }}>
const SUFFIXES = {
mac: [".dmg"],
windows: [".msi", ".exe"],
linux: [".AppImage", ".deb", ".rpm"],
};
const LABELS = { mac: "macOS", windows: "Windows", linux: "Linux" }; const LABELS = { mac: "macOS", windows: "Windows", linux: "Linux" };
function detectOS() { function detectOS() {
@@ -21,32 +20,31 @@ const { repo, releasesUrl } = site;
} }
function pickAsset(assets, os) { function pickAsset(assets, os) {
for (const sfx of SUFFIXES[os] || []) { for (const sfx of matches[os] || []) {
const hit = assets.find((a) => a.name.toLowerCase().endsWith(sfx.toLowerCase())); const hit = assets.find((a) => a.name.toLowerCase().endsWith(sfx.toLowerCase()));
if (hit) return hit.browser_download_url; if (hit) return hit.browser_download_url;
} }
return null; return null;
} }
const os = detectOS(); function apply(map, ver) {
const os = detectOS();
document.querySelectorAll("[data-download]").forEach((el) => {
const label = el.querySelector("[data-dl-label]");
if (label) label.textContent = "Download for " + LABELS[os];
if (map[os]) el.href = map[os];
});
document.querySelectorAll("[data-download-os]").forEach((el) => {
const url = map[el.dataset.downloadOs];
if (url) el.href = url;
});
document.querySelectorAll(".dl__os [data-download-os]").forEach((el) => {
if (el.dataset.downloadOs === os) el.remove();
});
if (ver) document.querySelectorAll("[data-version]").forEach((el) => (el.textContent = ver));
}
document.querySelectorAll("[data-download]").forEach((el) => { apply(urls, version);
el.dataset.detectedOs = os;
const label = el.querySelector("[data-dl-label]");
if (label) label.textContent = "Download for " + LABELS[os];
});
document.querySelectorAll(".dl__alt [data-download-os]").forEach((el) => {
if (el.dataset.downloadOs === os) el.remove();
});
document.querySelectorAll("[data-osrow]").forEach((row) => {
const primary = row.querySelector(`[data-download-os="${os}"]`);
if (primary) {
primary.classList.add("is-detected");
row.prepend(primary);
}
});
(async () => { (async () => {
try { try {
@@ -56,23 +54,11 @@ const { repo, releasesUrl } = site;
if (!res.ok) return; if (!res.ok) return;
const data = await res.json(); const data = await res.json();
const assets = data.assets || []; const assets = data.assets || [];
const version = data.tag_name || ""; const fresh = {};
for (const os of Object.keys(matches)) fresh[os] = pickAsset(assets, os) || urls[os] || releasesUrl;
document.querySelectorAll("[data-download]").forEach((el) => { apply(fresh, data.tag_name || version);
const url = pickAsset(assets, el.dataset.detectedOs || os);
if (url) el.href = url;
});
document.querySelectorAll("[data-download-os]").forEach((el) => {
const url = pickAsset(assets, el.dataset.downloadOs);
if (url) el.href = url;
});
if (version) {
document.querySelectorAll("[data-version]").forEach((el) => {
el.textContent = version;
});
}
} catch (e) { } catch (e) {
/* keep the releases-page fallback links */ /* keep the build-time links */
} }
})(); })();
</script> </script>
+6 -3
View File
@@ -1,14 +1,17 @@
--- ---
import { site } from "../data/site"; import { site } from "../data/site";
import { getRelease } from "../data/release";
import Logo from "./Logo.astro"; import Logo from "./Logo.astro";
const release = await getRelease();
const cols = [ const cols = [
{ {
title: "Get Margin", title: "Get Margin",
links: [ links: [
{ label: "Download for macOS", href: site.releasesUrl, os: "mac" }, { label: "Download for macOS", href: release.urls.mac, os: "mac" },
{ label: "Download for Windows", href: site.releasesUrl, os: "windows" }, { label: "Download for Windows", href: release.urls.windows, os: "windows" },
{ label: "Download for Linux", href: site.releasesUrl, os: "linux" }, { label: "Download for Linux", href: release.urls.linux, os: "linux" },
], ],
}, },
{ {
+2 -73
View File
@@ -1,7 +1,7 @@
--- ---
import AppWindow from "./AppWindow.astro"; import AppWindow from "./AppWindow.astro";
import DownloadButtons from "./DownloadButtons.astro";
import OfferBadge from "./OfferBadge.astro"; import OfferBadge from "./OfferBadge.astro";
import { site, platforms } from "../data/site";
--- ---
<section class="hero"> <section class="hero">
@@ -21,25 +21,7 @@ import { site, platforms } from "../data/site";
</div> </div>
<div class="container hero__get" id="download"> <div class="container hero__get" id="download">
<div class="hero__buttons" data-osrow> <DownloadButtons large />
{
platforms.map((p) => (
<a
class="osbtn"
href={site.releasesUrl}
data-download-os={p.id}
rel="noopener"
download
>
<img class="osbtn__icon" src={`/logos/${p.logo}`} alt="" />
<span>Download for {p.label}</span>
</a>
))
}
</div>
<p class="hero__meta">
<span data-version>v0.1.2</span> · Works offline · No account
</p>
<div class="hero__offer"> <div class="hero__offer">
<OfferBadge /> <OfferBadge />
</div> </div>
@@ -75,62 +57,9 @@ import { site, platforms } from "../data/site";
margin-top: clamp(24px, 3vw, 32px); margin-top: clamp(24px, 3vw, 32px);
scroll-margin-top: 96px; scroll-margin-top: 96px;
} }
.hero__buttons {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
}
.osbtn {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 0.82em 1.35em;
background: #ffffff;
border: 1px solid var(--line-strong);
border-radius: 999px;
color: #23201b;
font-family: var(--font-ui);
font-size: 1rem;
font-weight: 600;
box-shadow: var(--shadow-page);
transition:
transform 0.18s var(--ease),
box-shadow 0.18s var(--ease),
border-color 0.18s var(--ease);
}
.osbtn:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-pop);
}
.osbtn.is-detected {
border-color: var(--ink);
}
.osbtn__icon {
width: 18px;
height: 18px;
flex: none;
object-fit: contain;
}
.hero__meta {
margin: 16px 0 0;
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
font-size: 0.8rem;
color: var(--ink-faint);
text-align: center;
}
.hero__offer { .hero__offer {
display: flex; display: flex;
justify-content: center; justify-content: center;
margin-top: 22px; margin-top: 22px;
} }
@media (max-width: 480px) {
.hero__buttons {
flex-direction: column;
align-items: stretch;
}
.osbtn {
justify-content: center;
}
}
</style> </style>
+52
View File
@@ -0,0 +1,52 @@
// Resolves the latest GitHub release to a direct download URL per OS, at build
// time. The picked file follows each platform's `match` order in site.ts.
// Falls back to the releases page if the release can't be read.
import { site, platforms } from "./site";
type Asset = { name: string; browser_download_url: string };
export type Release = {
version: string;
urls: Record<string, string>;
};
function pickAsset(assets: Asset[], match: string[]): string | null {
for (const sfx of match) {
const hit = assets.find((a) => a.name.toLowerCase().endsWith(sfx.toLowerCase()));
if (hit) return hit.browser_download_url;
}
return null;
}
function fallback(): Release {
return {
version: "",
urls: Object.fromEntries(platforms.map((p) => [p.id, site.releasesUrl])),
};
}
async function fetchRelease(): Promise<Release> {
try {
const res = await fetch(`https://api.github.com/repos/${site.repo}/releases/latest`, {
headers: { Accept: "application/vnd.github+json" },
signal: AbortSignal.timeout(8000),
});
if (!res.ok) return fallback();
const data = await res.json();
const assets: Asset[] = data.assets || [];
const urls = Object.fromEntries(
platforms.map((p) => [p.id, pickAsset(assets, p.match) ?? site.releasesUrl]),
);
return { version: data.tag_name ?? "", urls };
} catch {
return fallback();
}
}
let cached: Promise<Release> | null = null;
export function getRelease(): Promise<Release> {
cached ??= fetchRelease();
return cached;
}
+2 -2
View File
@@ -39,8 +39,8 @@ export type Platform = {
export const platforms: Platform[] = [ export const platforms: Platform[] = [
{ id: "mac", label: "macOS", logo: "macos.svg", match: [".dmg"] }, { id: "mac", label: "macOS", logo: "macos.svg", match: [".dmg"] },
{ id: "windows", label: "Windows", logo: "windows.svg", match: [".msi", ".exe"] }, { id: "windows", label: "Windows", logo: "windows.svg", match: [".exe", ".msi"] },
{ id: "linux", label: "Linux", logo: "linux.svg", match: [".AppImage", ".deb", ".rpm"] }, { id: "linux", label: "Linux", logo: "linux.svg", match: [".deb", ".AppImage", ".rpm"] },
]; ];
// Full-color store logos, served from /public/logos. Shown on white tiles so // Full-color store logos, served from /public/logos. Shown on white tiles so