diff --git a/.github/workflows/_build.yml b/.github/workflows/_build.yml
index 10320f12..0b811936 100644
--- a/.github/workflows/_build.yml
+++ b/.github/workflows/_build.yml
@@ -135,6 +135,7 @@ jobs:
release/Lightcode-*.AppImage
release/Lightcode-*.deb
release/latest*.yml
+ release/nightly*.yml
release/*.blockmap
if-no-files-found: error
retention-days: 7
diff --git a/src/renderer/views/SettingsOverlay/parts/AboutSettings.tsx b/src/renderer/views/SettingsOverlay/parts/AboutSettings.tsx
index 42105871..0d6c46d8 100644
--- a/src/renderer/views/SettingsOverlay/parts/AboutSettings.tsx
+++ b/src/renderer/views/SettingsOverlay/parts/AboutSettings.tsx
@@ -5,7 +5,8 @@ import { PixelLoader } from "@/renderer/components/common";
import { useUpdateStore } from "@/renderer/state/updateStore";
import { productNameFor } from "@/shared/channel";
import { formatBytes } from "@/shared/formatBytes";
-import appIconUrl from "../../../../../build/icon.png";
+import appIconStableUrl from "../../../../../build/icon.png";
+import appIconNightlyUrl from "../../../../../build/icon-nightly.png";
const GITHUB_REPO = "https://github.com/nicepkg/lightcode";
const WEBSITE_URL = "https://www.lightcodeapp.com/";
@@ -88,6 +89,7 @@ function UpdateButton() {
export function AboutSettings() {
const bridge = readBridge();
const productName = productNameFor(bridge.channel);
+ const appIconUrl = bridge.channel === "nightly" ? appIconNightlyUrl : appIconStableUrl;
return (
diff --git a/website/src/app/download/download-content.tsx b/website/src/app/download/download-content.tsx
index 65eb9e78..df201327 100644
--- a/website/src/app/download/download-content.tsx
+++ b/website/src/app/download/download-content.tsx
@@ -1,6 +1,6 @@
"use client";
-import { Download, ArrowLeft, Monitor, Apple, Terminal } from "lucide-react";
+import { Download, ArrowLeft, Monitor, Apple, Terminal, Moon } from "lucide-react";
import { motion } from "framer-motion";
import Link from "next/link";
import { downloadUrlFor, type ReleaseInfo } from "@/lib/releases";
@@ -46,6 +46,13 @@ export function DownloadContent({ release }: { release: ReleaseInfo }) {
Back to home
+
+
+
Nightly builds →
+
{/* Content */}
diff --git a/website/src/app/home-content.tsx b/website/src/app/home-content.tsx
index e22f0a9d..8626fab9 100644
--- a/website/src/app/home-content.tsx
+++ b/website/src/app/home-content.tsx
@@ -324,20 +324,18 @@ export function HomeContent({ release }: { release: ReleaseInfo }) {
Download for {platform.label}
-
- View on GitHub
-
Other platforms
+
+ Nightly builds
+
diff --git a/website/src/app/nightly/nightly-content.tsx b/website/src/app/nightly/nightly-content.tsx
new file mode 100644
index 00000000..3b5a053c
--- /dev/null
+++ b/website/src/app/nightly/nightly-content.tsx
@@ -0,0 +1,225 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { Download, ArrowLeft, Monitor, Apple, Terminal, Moon, AlertTriangle } from "lucide-react";
+import { motion } from "framer-motion";
+import Link from "next/link";
+import { downloadUrlFor, type ReleaseInfo } from "@/lib/releases";
+
+const PLATFORMS = [
+ {
+ os: "macOS",
+ icon: Apple,
+ variants: [
+ { label: "arm", slug: "mac-arm64", ext: ".dmg" },
+ { label: "Intel", slug: "mac-x64", ext: ".dmg" },
+ ],
+ },
+ {
+ os: "Windows",
+ icon: Monitor,
+ variants: [
+ { label: "x64", slug: "win-x64", ext: ".exe" },
+ { label: "ARM64", slug: "win-arm64", ext: ".exe" },
+ ],
+ },
+ {
+ os: "Linux",
+ icon: Terminal,
+ variants: [{ label: "x64 (AppImage)", slug: "linux-x64", ext: ".AppImage" }],
+ },
+];
+
+function formatRelative(iso: string): string {
+ const then = new Date(iso).getTime();
+ if (Number.isNaN(then)) return "";
+ const diffMs = Date.now() - then;
+ const minutes = Math.round(diffMs / 60_000);
+ if (minutes < 1) return "just now";
+ if (minutes < 60) return `${minutes}m ago`;
+ const hours = Math.round(minutes / 60);
+ if (hours < 24) return `${hours}h ago`;
+ const days = Math.round(hours / 24);
+ if (days < 30) return `${days}d ago`;
+ const months = Math.round(days / 30);
+ return `${months}mo ago`;
+}
+
+function formatBuildTime(iso: string): string {
+ const date = new Date(iso);
+ if (Number.isNaN(date.getTime())) return "";
+ return date.toLocaleString(undefined, {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ timeZoneName: "short",
+ });
+}
+
+export function NightlyContent({ release }: { release: ReleaseInfo }) {
+ const hasBuild = release.version !== null;
+ const publishedAt = release.publishedAt ?? null;
+ const [relativeTime, setRelativeTime] = useState