authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-06 15:29:20-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-06 15:29:20-07:00
log075a86e3b0152ea8f867ddd2c424739f5da08238
tree3fdea486e5b8ddfd3bb7d564f36220e2dbcb85f1
parent001f06d543beb92cc66bb7e8d54d233a20d802b4
signature Signed by PGP key B5690EEEBB952194

feat(marketing): add a nightly channel to the download page (#10408)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

5 files changed, 496 insertions(+), 151 deletions(-)

apps/marketing/public/nightly-sky.svg created+44
...@@ -0,0 +1,44 @@
1<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 540 112" preserveAspectRatio="xMidYMid slice">
2 <defs>
3 <linearGradient id="night-sky" x1="28" y1="0" x2="510" y2="112" gradientUnits="userSpaceOnUse">
4 <stop stop-color="#07152F"/>
5 <stop offset="0.52" stop-color="#151443"/>
6 <stop offset="1" stop-color="#32155B"/>
7 </linearGradient>
8 <radialGradient id="night-glow" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(420 18) rotate(157) scale(260 126)">
9 <stop stop-color="#5165D8" stop-opacity="0.42"/>
10 <stop offset="0.55" stop-color="#283075" stop-opacity="0.15"/>
11 <stop offset="1" stop-color="#111635" stop-opacity="0"/>
12 </radialGradient>
13 <linearGradient id="night-cloud" x1="18" y1="62" x2="260" y2="112" gradientUnits="userSpaceOnUse">
14 <stop stop-color="#4EA4FF" stop-opacity="0.5"/>
15 <stop offset="0.52" stop-color="#696FEA" stop-opacity="0.62"/>
16 <stop offset="1" stop-color="#A85BEA" stop-opacity="0.5"/>
17 </linearGradient>
18 <filter id="night-soft" x="-24" y="35" width="588" height="101" filterUnits="userSpaceOnUse">
19 <feGaussianBlur stdDeviation="4"/>
20 </filter>
21 </defs>
22
23
24 <rect width="540" height="112" fill="url(#night-sky)"/>
25 <rect width="540" height="112" fill="url(#night-glow)"/>
26
27 <g fill="#E4EAFF">
28 <circle cx="20" cy="18" r="0.8" opacity="0.82"/>
29 <circle cx="76" cy="27" r="0.55" opacity="0.55"/>
30 <circle cx="133" cy="13" r="0.65" opacity="0.7"/>
31 <circle cx="198" cy="28" r="0.5" opacity="0.5"/>
32 <circle cx="258" cy="15" r="0.75" opacity="0.8"/>
33 <circle cx="326" cy="29" r="0.5" opacity="0.55"/>
34 <circle cx="384" cy="12" r="0.65" opacity="0.72"/>
35 <circle cx="455" cy="26" r="0.55" opacity="0.58"/>
36 <circle cx="517" cy="15" r="0.75" opacity="0.78"/>
37 </g>
38
39 <g filter="url(#night-soft)">
40 <path d="M-12 112V101C-12 87 0 76 14 76C18 63 30 54 44 54C58 54 70 62 74 75C79 70 86 67 94 67C110 67 123 79 124 95C132 96 138 101 141 112H-12Z" fill="url(#night-cloud)"/>
41 <path d="M374 112C375 98 387 87 401 87C405 72 418 62 434 62C450 62 463 72 467 87C478 87 488 94 492 104C506 103 518 106 526 112H374Z" fill="url(#night-cloud)" opacity="0.82"/>
42 </g>
43 <path d="M0 111.5H540" stroke="#050A19" stroke-opacity="0.3"/>
44</svg>
apps/marketing/src/assets/icon-nightly.webp created
Binary files /dev/null and b/apps/marketing/src/assets/icon-nightly.webp differ
apps/marketing/src/layouts/Layout.astro+14
...@@ -80,6 +80,7 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site);...@@ -80,6 +80,7 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
80 <span class="nav-brand-name">T3 Code</span>80 <span class="nav-brand-name">T3 Code</span>
81 </a>81 </a>
82 <div class="nav-right">82 <div class="nav-right">
83 <a class="nav-link" href="/download">Download</a>
83 <a84 <a
84 class="nav-stars"85 class="nav-stars"
85 href={GITHUB_REPOSITORY_URL}86 href={GITHUB_REPOSITORY_URL}
...@@ -385,6 +386,19 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site);...@@ -385,6 +386,19 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
385 gap: 8px;386 gap: 8px;
386 }387 }
387388
389 .nav-link {
390 padding: 0 10px;
391 color: var(--fg-muted);
392 font-size: 13px;
393 letter-spacing: -0.01em;
394 white-space: nowrap;
395 transition: color 0.18s ease;
396 }
397
398 .nav-link:hover {
399 color: var(--fg);
400 }
401
388 .nav-stars {402 .nav-stars {
389 display: inline-flex;403 display: inline-flex;
390 align-items: center;404 align-items: center;
apps/marketing/src/lib/releases.ts+31-6
...@@ -1,9 +1,15 @@...@@ -1,9 +1,15 @@
1const REPO = "pingdotgg/t3code";1const REPO = "pingdotgg/t3code";
22
3export const RELEASES_URL = `https://github.com/${REPO}/releases`;3export const RELEASES_URL = `https://github.com/${REPO}/releases`;
4export const NIGHTLY_RELEASES_URL = `${RELEASES_URL}?q=nightly&expanded=true`;
45
5const API_URL = `https://api.github.com/repos/${REPO}/releases/latest`;6const LATEST_API_URL = `https://api.github.com/repos/${REPO}/releases/latest`;
6const CACHE_KEY = "t3code-latest-release";7// The `latest` endpoint skips prereleases, so nightly needs the list. GitHub
8// returns it newest first and nightlies land several times a day, so the first
9// nightly tag in a small page is the current build.
10const LIST_API_URL = `https://api.github.com/repos/${REPO}/releases?per_page=10`;
11
12export type ReleaseChannel = "stable" | "nightly";
713
8export interface ReleaseAsset {14export interface ReleaseAsset {
9 name: string;15 name: string;
...@@ -13,17 +19,36 @@ export interface ReleaseAsset {...@@ -13,17 +19,36 @@ export interface ReleaseAsset {
13export interface Release {19export interface Release {
14 tag_name: string;20 tag_name: string;
15 html_url: string;21 html_url: string;
22 published_at: string;
16 assets: ReleaseAsset[];23 assets: ReleaseAsset[];
17}24}
1825
19export async function fetchLatestRelease(): Promise<Release> {26function cacheKey(channel: ReleaseChannel) {
20 const cached = sessionStorage.getItem(CACHE_KEY);27 return `t3code-${channel}-release`;
28}
29
30async function fetchStable(): Promise<Release> {
31 return fetch(LATEST_API_URL).then((r) => r.json());
32}
33
34async function fetchNightly(): Promise<Release> {
35 const list: Release[] = await fetch(LIST_API_URL).then((r) => r.json());
36 const nightly = Array.isArray(list)
37 ? list.find((release) => release.tag_name?.includes("-nightly."))
38 : undefined;
39 if (!nightly) throw new Error("No nightly release in the latest page");
40 return nightly;
41}
42
43export async function fetchLatestRelease(channel: ReleaseChannel = "stable"): Promise<Release> {
44 const key = cacheKey(channel);
45 const cached = sessionStorage.getItem(key);
21 if (cached) return JSON.parse(cached);46 if (cached) return JSON.parse(cached);
2247
23 const data = await fetch(API_URL).then((r) => r.json());48 const data = channel === "nightly" ? await fetchNightly() : await fetchStable();
2449
25 if (data?.assets) {50 if (data?.assets) {
26 sessionStorage.setItem(CACHE_KEY, JSON.stringify(data));51 sessionStorage.setItem(key, JSON.stringify(data));
27 }52 }
2853
29 return data;54 return data;
apps/marketing/src/pages/download.astro+407-145
...@@ -1,134 +1,215 @@...@@ -1,134 +1,215 @@
1---1---
2import type { ComponentProps } from "astro/types";
3import { Image } from "astro:assets";
4import iconStable from "../assets/icon.webp";
5import iconNightly from "../assets/icon-nightly.webp";
2import Layout from "../layouts/Layout.astro";6import Layout from "../layouts/Layout.astro";
3import { RELEASES_URL } from "../lib/releases";7import { RELEASES_URL } from "../lib/releases";
4import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";8import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";
9
10const imageProps = {
11 alt: "",
12 densities: [1, 2, 3],
13 format: "webp",
14 quality: 90,
15 decoding: "async",
16} satisfies Partial<ComponentProps<typeof Image>>;
5---17---
618
7<Layout title="Download — T3 Code" description="Download T3 Code for macOS, Windows, or Linux.">19<Layout title="Download — T3 Code" description="Download T3 Code for macOS, Windows, or Linux.">
8 <div class="download-page">20 <div class="download-page" id="download-page" data-channel="stable">
9 <h1 class="heading">Download T3 Code</h1>21 <!-- The sky lives behind the header and fades to the page. It only shows on the nightly channel. -->
10 <p class="subheading">22 <header class="download-head">
11 <span id="version-label">Loading latest release&hellip;</span>23 <div class="sky" aria-hidden="true"></div>
12 <a id="changelog-link" class="changelog-link" href="#" target="_blank" rel="noopener noreferrer" style="display:none">View changelog<span aria-hidden="true"> &#8599;</span></a>24 <div class="head-inner" data-rise>
13 </p>25 <span class="head-icon">
1426 <Image class="head-icon--stable" src={iconStable} width={64} height={64} loading="eager" {...imageProps} />
15 <div class="platforms" id="platforms">27 <Image class="head-icon--nightly" src={iconNightly} width={64} height={64} loading="eager" {...imageProps} />
16 <!-- macOS -->28 </span>
17 <section class="platform-section">29 <h1 class="heading">Download T3 Code</h1>
18 <div class="platform-header">
19 <svg class="platform-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 814 1000" aria-hidden="true"><path fill="currentColor" d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/></svg>
20 <h2 class="platform-name">macOS</h2>
21 </div>
22 <div class="download-cards">
23 <a class="download-card" data-asset="arm64.dmg" href={RELEASES_URL}>
24 <span class="card-arch">Apple Silicon (arm64)</span>
25 <span class="card-format">.dmg</span>
26 </a>
27 </div>30 </div>
28 <p class="intel-note">31 <div class="channel-toggle" role="tablist" aria-label="Release channel" data-rise style="--d:90ms">
29 On an Intel Mac? <a data-asset="x64.dmg" href={RELEASES_URL}>Download the x64 build</a>.32 <span class="channel-thumb" aria-hidden="true"></span>
30 </p>33 <button type="button" role="tab" data-channel="stable" aria-selected="true">Stable</button>
31 </section>34 <button type="button" role="tab" data-channel="nightly" aria-selected="false">Nightly</button>
32
33 <!-- Windows -->
34 <section class="platform-section">
35 <div class="platform-header">
36 <svg class="platform-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 88 88" aria-hidden="true"><path fill="currentColor" d="m0 12.402 35.687-4.86.016 34.423-35.67.203zm35.67 33.529.028 34.453L.028 75.48.026 45.7zm4.326-39.025L87.314 0v41.527l-47.318.376zm47.329 39.349-.011 41.34-47.318-6.678-.066-34.739z"/></svg>
37 <h2 class="platform-name">Windows</h2>
38 </div>
39 <div class="download-cards">
40 <a class="download-card" data-asset="x64.exe" href={RELEASES_URL}>
41 <span class="card-arch">Windows 10, 11</span>
42 <span class="card-format">.exe</span>
43 </a>
44 </div>
45 </section>
46
47 <!-- Linux -->
48 <section class="platform-section">
49 <div class="platform-header">
50 <svg class="platform-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M12.504 0c-.155 0-.315.008-.48.021-4.226.333-3.105 4.807-3.17 6.298-.076 1.092-.3 1.953-1.05 3.02-.885 1.051-2.127 2.75-2.716 4.521-.278.832-.41 1.684-.287 2.489a.424.424 0 00-.11.135c-.26.268-.45.6-.663.839-.199.199-.485.267-.797.4-.313.136-.658.269-.864.68-.09.189-.136.394-.132.602 0 .199.027.4.055.536.058.399.116.728.04.97-.249.68-.28 1.145-.106 1.484.174.334.535.47.94.601.81.2 1.91.135 2.774.6.926.466 1.866.67 2.616.47.526-.116.97-.464 1.208-.946.587-.003 1.23-.269 2.26-.334.699-.058 1.574.267 2.577.2.025.134.063.198.114.333l.003.003c.391.778 1.113 1.132 1.884 1.071.771-.06 1.592-.536 2.257-1.306.631-.765 1.683-1.084 2.378-1.503.348-.199.629-.469.649-.853.023-.4-.2-.811-.714-1.376v-.097l-.003-.003c-.17-.2-.25-.535-.338-.926-.085-.401-.182-.786-.492-1.046h-.003c-.059-.054-.123-.067-.188-.135a.357.357 0 00-.19-.064c.431-1.278.264-2.55-.173-3.694-.533-1.41-1.465-2.638-2.175-3.483-.796-1.005-1.576-1.957-1.56-3.368.026-2.152.236-6.133-3.544-6.139zm.529 3.405h.013c.213 0 .396.062.584.198.19.135.33.332.438.533.105.259.158.459.166.724 0-.02.006-.04.006-.06v.105a.086.086 0 01-.004-.021l-.004-.024a1.807 1.807 0 01-.15.706.953.953 0 01-.213.335.71.71 0 00-.088-.042c-.104-.045-.198-.064-.284-.133a1.312 1.312 0 00-.22-.066c.05-.06.146-.133.183-.198.053-.128.082-.264.088-.402v-.02a1.21 1.21 0 00-.061-.4c-.045-.134-.101-.2-.183-.333-.084-.066-.167-.132-.267-.132h-.016c-.093 0-.176.03-.262.132a.8.8 0 00-.205.334 1.18 1.18 0 00-.09.4v.019c.002.089.008.179.02.267-.193-.067-.438-.135-.607-.202a1.635 1.635 0 01-.018-.2v-.02a1.772 1.772 0 01.15-.768c.082-.22.232-.406.43-.533a.985.985 0 01.594-.2zm-2.962.059h.036c.142 0 .27.048.399.135.146.129.264.288.344.465.09.199.14.4.153.667v.004c.007.134.006.2-.002.266v.08c-.03.007-.056.018-.083.024-.152.055-.274.135-.393.2.012-.09.013-.18.003-.267v-.015c-.012-.133-.04-.2-.082-.333a.613.613 0 00-.166-.267.248.248 0 00-.183-.064h-.021c-.071.006-.13.04-.186.132a.552.552 0 00-.12.27.944.944 0 00-.023.33v.015c.012.135.037.2.08.334.046.134.098.2.166.268.01.009.02.018.034.024-.07.057-.117.07-.176.136a.304.304 0 01-.131.068 2.62 2.62 0 01-.275-.402 1.772 1.772 0 01-.155-.667 1.759 1.759 0 01.08-.668 1.43 1.43 0 01.283-.535c.128-.133.26-.2.418-.2zm1.37 1.706c.332 0 .733.065 1.216.399.293.2.523.269 1.052.468h.003c.255.136.405.266.478.399v-.131a.571.571 0 01.016.47c-.123.31-.516.643-1.063.842v.002c-.268.135-.501.333-.775.465-.276.135-.588.292-1.012.267a1.139 1.139 0 01-.448-.067 3.566 3.566 0 01-.322-.198c-.195-.135-.363-.332-.612-.465v-.005h-.005c-.4-.246-.616-.512-.686-.71-.07-.268-.005-.47.193-.6.224-.135.38-.271.483-.336.104-.074.143-.102.176-.131h.002v-.003c.169-.202.436-.47.839-.601.139-.036.294-.065.466-.065zm2.8 2.142c.358 1.417 1.196 3.475 1.735 4.473.286.534.855 1.659 1.102 3.024.156-.005.33.018.513.064.646-1.671-.546-3.467-1.089-3.966-.22-.2-.232-.335-.123-.335.59.534 1.365 1.572 1.646 2.757.13.535.16 1.104.021 1.67.067.028.135.06.205.067 1.032.534 1.413.938 1.23 1.537v-.043c-.06-.003-.12 0-.18 0h-.016c.151-.467-.182-.825-1.065-1.224-.915-.4-1.646-.336-1.77.465-.008.043-.013.066-.018.135-.068.023-.139.053-.209.064-.43.268-.662.669-.793 1.187-.13.533-.17 1.156-.205 1.869v.003c-.02.334-.17.838-.319 1.35-1.5 1.072-3.58 1.538-5.348.334a2.645 2.645 0 00-.402-.533 1.45 1.45 0 00-.275-.333c.182 0 .338-.03.465-.067a.615.615 0 00.314-.334c.108-.267 0-.697-.345-1.163-.345-.467-.931-.995-1.788-1.521-.63-.4-.986-.87-1.15-1.396-.165-.534-.143-1.085-.015-1.645.245-1.07.873-2.11 1.274-2.763.107-.065.037.135-.408.974-.396.751-1.14 2.497-.122 3.854a8.123 8.123 0 01.647-2.876c.564-1.278 1.743-3.504 1.836-5.268.048.036.217.135.289.202.218.133.38.333.59.465.21.201.477.335.876.335.039.003.075.006.11.006.412 0 .73-.134.997-.268.29-.134.52-.334.74-.4h.005c.467-.135.835-.402 1.044-.7zm2.185 8.958c.037.6.343 1.245.882 1.377.588.134 1.434-.333 1.791-.765l.211-.01c.315-.007.577.01.847.268l.003.003c.208.199.305.53.391.876.085.4.154.78.409 1.066.486.527.645.906.636 1.14l.003-.007v.018l-.003-.012c-.015.262-.185.396-.498.595-.63.401-1.746.712-2.457 1.57-.618.737-1.37 1.14-2.036 1.191-.664.053-1.237-.2-1.574-.898l-.005-.003c-.21-.4-.12-1.025.056-1.69.176-.668.428-1.344.463-1.897.037-.714.076-1.335.195-1.814.12-.465.308-.797.641-.984l.045-.022zm-10.814.049h.01c.053 0 .105.005.157.014.376.055.706.333 1.023.752l.91 1.664.003.003c.243.533.754 1.064 1.189 1.637.434.598.77 1.131.729 1.57v.006c-.057.744-.48 1.148-1.125 1.294-.645.135-1.52.002-2.395-.464-.968-.536-2.118-.469-2.857-.602-.369-.066-.61-.2-.723-.4-.11-.2-.113-.602.123-1.23v-.004l.002-.003c.117-.334.03-.752-.027-1.118-.055-.401-.083-.71.043-.94.16-.334.396-.4.69-.533.294-.135.64-.202.915-.47h.002v-.002c.256-.268.445-.601.668-.838.19-.201.38-.336.663-.336zm7.159-9.074c-.435.201-.945.535-1.488.535-.542 0-.97-.267-1.28-.466-.154-.134-.28-.268-.373-.335-.164-.134-.144-.333-.074-.333.109.016.129.134.199.2.096.066.215.2.36.333.292.2.68.467 1.167.467.485 0 1.053-.267 1.398-.466.195-.135.445-.334.648-.467.156-.136.149-.267.279-.267.128.016.034.134-.147.332a8.097 8.097 0 01-.69.468zm-1.082-1.583V5.64c-.006-.02.013-.042.029-.05.074-.043.18-.027.26.004.063 0 .16.067.15.135-.006.049-.085.066-.135.066-.055 0-.092-.043-.141-.068-.052-.018-.146-.008-.163-.065zm-.551 0c-.02.058-.113.049-.166.066-.047.025-.086.068-.14.068-.05 0-.13-.02-.136-.068-.01-.066.088-.133.15-.133.08-.031.184-.047.259-.005.019.009.036.03.03.05v.02h.003z"/></svg>
51 <h2 class="platform-name">Linux</h2>
52 </div>35 </div>
53 <div class="download-cards">36 <p class="head-meta" data-rise style="--d:180ms">
54 <a class="download-card" data-asset="x86_64.AppImage" href={RELEASES_URL}>37 <a id="changelog-link" class="changelog-link" href={RELEASES_URL} target="_blank" rel="noopener noreferrer">
55 <span class="card-arch">x86_64</span>38 <span id="version-label">&nbsp;</span><span aria-hidden="true"> &#8599;</span>
56 <span class="card-format">AppImage</span>
57 </a>39 </a>
58 </div>40 </p>
59 </section>41 </header>
6042
61 <!-- Mobile -->43 <div class="platforms">
62 <section class="platform-section">44 <!-- macOS -->
63 <div class="platform-header">45 <section class="platform-section" data-rise style="--d:240ms">
64 <svg class="platform-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="7" y="2" width="10" height="20" rx="2.5"/><path d="M11 18.5h2"/></svg>46 <div class="platform-header">
65 <h2 class="platform-name">Mobile</h2>47 <svg class="platform-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 814 1000" aria-hidden="true"><path fill="currentColor" d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/></svg>
66 </div>48 <h2 class="platform-name">macOS</h2>
67 <div class="download-cards">49 </div>
68 <a class="download-card" href={IOS_APP_STORE_URL} target="_blank" rel="noopener noreferrer">50 <div class="download-cards">
69 <span class="card-arch">iPhone &amp; iPad</span>51 <a class="download-card" data-asset="arm64.dmg" href={RELEASES_URL}>
70 <span class="card-format">App Store</span>52 <span class="card-icon">
71 </a>53 <Image class="card-icon--stable" src={iconStable} width={28} height={28} loading="lazy" {...imageProps} />
72 <a class="download-card" href={ANDROID_PLAY_STORE_URL} target="_blank" rel="noopener noreferrer">54 <Image class="card-icon--nightly" src={iconNightly} width={28} height={28} loading="lazy" {...imageProps} />
73 <span class="card-arch">Android</span>55 </span>
74 <span class="card-format">Google Play</span>56 <span class="card-text">
75 </a>57 <span class="card-arch">Apple Silicon (arm64)</span>
76 </div>58 <span class="card-format">.dmg</span>
77 </section>59 </span>
78 </div>60 </a>
7961 </div>
80 <p class="releases-link">62 <p class="intel-note">
81 Looking for older versions? Check the{" "}63 On an Intel Mac? <a data-asset="x64.dmg" href={RELEASES_URL}>Download the x64 build</a>.
82 <a href="https://github.com/pingdotgg/t3code/releases" target="_blank" rel="noopener noreferrer">64 </p>
83 GitHub releases page<span aria-hidden="true"> &#8599;</span>65 </section>
84 </a>66
85 </p>67 <!-- Windows -->
68 <section class="platform-section" data-rise style="--d:300ms">
69 <div class="platform-header">
70 <svg class="platform-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 88 88" aria-hidden="true"><path fill="currentColor" d="m0 12.402 35.687-4.86.016 34.423-35.67.203zm35.67 33.529.028 34.453L.028 75.48.026 45.7zm4.326-39.025L87.314 0v41.527l-47.318.376zm47.329 39.349-.011 41.34-47.318-6.678-.066-34.739z"/></svg>
71 <h2 class="platform-name">Windows</h2>
72 </div>
73 <div class="download-cards">
74 <a class="download-card" data-asset="x64.exe" href={RELEASES_URL}>
75 <span class="card-icon">
76 <Image class="card-icon--stable" src={iconStable} width={28} height={28} loading="lazy" {...imageProps} />
77 <Image class="card-icon--nightly" src={iconNightly} width={28} height={28} loading="lazy" {...imageProps} />
78 </span>
79 <span class="card-text">
80 <span class="card-arch">Windows 10, 11</span>
81 <span class="card-format">.exe</span>
82 </span>
83 </a>
84 </div>
85 </section>
86
87 <!-- Linux -->
88 <section class="platform-section" data-rise style="--d:360ms">
89 <div class="platform-header">
90 <svg class="platform-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M12.504 0c-.155 0-.315.008-.48.021-4.226.333-3.105 4.807-3.17 6.298-.076 1.092-.3 1.953-1.05 3.02-.885 1.051-2.127 2.75-2.716 4.521-.278.832-.41 1.684-.287 2.489a.424.424 0 00-.11.135c-.26.268-.45.6-.663.839-.199.199-.485.267-.797.4-.313.136-.658.269-.864.68-.09.189-.136.394-.132.602 0 .199.027.4.055.536.058.399.116.728.04.97-.249.68-.28 1.145-.106 1.484.174.334.535.47.94.601.81.2 1.91.135 2.774.6.926.466 1.866.67 2.616.47.526-.116.97-.464 1.208-.946.587-.003 1.23-.269 2.26-.334.699-.058 1.574.267 2.577.2.025.134.063.198.114.333l.003.003c.391.778 1.113 1.132 1.884 1.071.771-.06 1.592-.536 2.257-1.306.631-.765 1.683-1.084 2.378-1.503.348-.199.629-.469.649-.853.023-.4-.2-.811-.714-1.376v-.097l-.003-.003c-.17-.2-.25-.535-.338-.926-.085-.401-.182-.786-.492-1.046h-.003c-.059-.054-.123-.067-.188-.135a.357.357 0 00-.19-.064c.431-1.278.264-2.55-.173-3.694-.533-1.41-1.465-2.638-2.175-3.483-.796-1.005-1.576-1.957-1.56-3.368.026-2.152.236-6.133-3.544-6.139zm.529 3.405h.013c.213 0 .396.062.584.198.19.135.33.332.438.533.105.259.158.459.166.724 0-.02.006-.04.006-.06v.105a.086.086 0 01-.004-.021l-.004-.024a1.807 1.807 0 01-.15.706.953.953 0 01-.213.335.71.71 0 00-.088-.042c-.104-.045-.198-.064-.284-.133a1.312 1.312 0 00-.22-.066c.05-.06.146-.133.183-.198.053-.128.082-.264.088-.402v-.02a1.21 1.21 0 00-.061-.4c-.045-.134-.101-.2-.183-.333-.084-.066-.167-.132-.267-.132h-.016c-.093 0-.176.03-.262.132a.8.8 0 00-.205.334 1.18 1.18 0 00-.09.4v.019c.002.089.008.179.02.267-.193-.067-.438-.135-.607-.202a1.635 1.635 0 01-.018-.2v-.02a1.772 1.772 0 01.15-.768c.082-.22.232-.406.43-.533a.985.985 0 01.594-.2zm-2.962.059h.036c.142 0 .27.048.399.135.146.129.264.288.344.465.09.199.14.4.153.667v.004c.007.134.006.2-.002.266v.08c-.03.007-.056.018-.083.024-.152.055-.274.135-.393.2.012-.09.013-.18.003-.267v-.015c-.012-.133-.04-.2-.082-.333a.613.613 0 00-.166-.267.248.248 0 00-.183-.064h-.021c-.071.006-.13.04-.186.132a.552.552 0 00-.12.27.944.944 0 00-.023.33v.015c.012.135.037.2.08.334.046.134.098.2.166.268.01.009.02.018.034.024-.07.057-.117.07-.176.136a.304.304 0 01-.131.068 2.62 2.62 0 01-.275-.402 1.772 1.772 0 01-.155-.667 1.759 1.759 0 01.08-.668 1.43 1.43 0 01.283-.535c.128-.133.26-.2.418-.2zm1.37 1.706c.332 0 .733.065 1.216.399.293.2.523.269 1.052.468h.003c.255.136.405.266.478.399v-.131a.571.571 0 01.016.47c-.123.31-.516.643-1.063.842v.002c-.268.135-.501.333-.775.465-.276.135-.588.292-1.012.267a1.139 1.139 0 01-.448-.067 3.566 3.566 0 01-.322-.198c-.195-.135-.363-.332-.612-.465v-.005h-.005c-.4-.246-.616-.512-.686-.71-.07-.268-.005-.47.193-.6.224-.135.38-.271.483-.336.104-.074.143-.102.176-.131h.002v-.003c.169-.202.436-.47.839-.601.139-.036.294-.065.466-.065zm2.8 2.142c.358 1.417 1.196 3.475 1.735 4.473.286.534.855 1.659 1.102 3.024.156-.005.33.018.513.064.646-1.671-.546-3.467-1.089-3.966-.22-.2-.232-.335-.123-.335.59.534 1.365 1.572 1.646 2.757.13.535.16 1.104.021 1.67.067.028.135.06.205.067 1.032.534 1.413.938 1.23 1.537v-.043c-.06-.003-.12 0-.18 0h-.016c.151-.467-.182-.825-1.065-1.224-.915-.4-1.646-.336-1.77.465-.008.043-.013.066-.018.135-.068.023-.139.053-.209.064-.43.268-.662.669-.793 1.187-.13.533-.17 1.156-.205 1.869v.003c-.02.334-.17.838-.319 1.35-1.5 1.072-3.58 1.538-5.348.334a2.645 2.645 0 00-.402-.533 1.45 1.45 0 00-.275-.333c.182 0 .338-.03.465-.067a.615.615 0 00.314-.334c.108-.267 0-.697-.345-1.163-.345-.467-.931-.995-1.788-1.521-.63-.4-.986-.87-1.15-1.396-.165-.534-.143-1.085-.015-1.645.245-1.07.873-2.11 1.274-2.763.107-.065.037.135-.408.974-.396.751-1.14 2.497-.122 3.854a8.123 8.123 0 01.647-2.876c.564-1.278 1.743-3.504 1.836-5.268.048.036.217.135.289.202.218.133.38.333.59.465.21.201.477.335.876.335.039.003.075.006.11.006.412 0 .73-.134.997-.268.29-.134.52-.334.74-.4h.005c.467-.135.835-.402 1.044-.7zm2.185 8.958c.037.6.343 1.245.882 1.377.588.134 1.434-.333 1.791-.765l.211-.01c.315-.007.577.01.847.268l.003.003c.208.199.305.53.391.876.085.4.154.78.409 1.066.486.527.645.906.636 1.14l.003-.007v.018l-.003-.012c-.015.262-.185.396-.498.595-.63.401-1.746.712-2.457 1.57-.618.737-1.37 1.14-2.036 1.191-.664.053-1.237-.2-1.574-.898l-.005-.003c-.21-.4-.12-1.025.056-1.69.176-.668.428-1.344.463-1.897.037-.714.076-1.335.195-1.814.12-.465.308-.797.641-.984l.045-.022zm-10.814.049h.01c.053 0 .105.005.157.014.376.055.706.333 1.023.752l.91 1.664.003.003c.243.533.754 1.064 1.189 1.637.434.598.77 1.131.729 1.57v.006c-.057.744-.48 1.148-1.125 1.294-.645.135-1.52.002-2.395-.464-.968-.536-2.118-.469-2.857-.602-.369-.066-.61-.2-.723-.4-.11-.2-.113-.602.123-1.23v-.004l.002-.003c.117-.334.03-.752-.027-1.118-.055-.401-.083-.71.043-.94.16-.334.396-.4.69-.533.294-.135.64-.202.915-.47h.002v-.002c.256-.268.445-.601.668-.838.19-.201.38-.336.663-.336zm7.159-9.074c-.435.201-.945.535-1.488.535-.542 0-.97-.267-1.28-.466-.154-.134-.28-.268-.373-.335-.164-.134-.144-.333-.074-.333.109.016.129.134.199.2.096.066.215.2.36.333.292.2.68.467 1.167.467.485 0 1.053-.267 1.398-.466.195-.135.445-.334.648-.467.156-.136.149-.267.279-.267.128.016.034.134-.147.332a8.097 8.097 0 01-.69.468zm-1.082-1.583V5.64c-.006-.02.013-.042.029-.05.074-.043.18-.027.26.004.063 0 .16.067.15.135-.006.049-.085.066-.135.066-.055 0-.092-.043-.141-.068-.052-.018-.146-.008-.163-.065zm-.551 0c-.02.058-.113.049-.166.066-.047.025-.086.068-.14.068-.05 0-.13-.02-.136-.068-.01-.066.088-.133.15-.133.08-.031.184-.047.259-.005.019.009.036.03.03.05v.02h.003z"/></svg>
91 <h2 class="platform-name">Linux</h2>
92 </div>
93 <div class="download-cards">
94 <a class="download-card" data-asset="x86_64.AppImage" href={RELEASES_URL}>
95 <span class="card-icon">
96 <Image class="card-icon--stable" src={iconStable} width={28} height={28} loading="lazy" {...imageProps} />
97 <Image class="card-icon--nightly" src={iconNightly} width={28} height={28} loading="lazy" {...imageProps} />
98 </span>
99 <span class="card-text">
100 <span class="card-arch">x86_64</span>
101 <span class="card-format">AppImage</span>
102 </span>
103 </a>
104 </div>
105 </section>
106
107 <!-- Mobile. Stable only, there is no nightly mobile build. -->
108 <section class="platform-section platform-section--mobile" data-rise style="--d:420ms">
109 <div class="platform-header">
110 <svg class="platform-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="7" y="2" width="10" height="20" rx="2.5"/><path d="M11 18.5h2"/></svg>
111 <h2 class="platform-name">Mobile</h2>
112 </div>
113 <div class="download-cards">
114 <a class="download-card" href={IOS_APP_STORE_URL} target="_blank" rel="noopener noreferrer">
115 <span class="card-text">
116 <span class="card-arch">iPhone &amp; iPad</span>
117 <span class="card-format">App Store</span>
118 </span>
119 </a>
120 <a class="download-card" href={ANDROID_PLAY_STORE_URL} target="_blank" rel="noopener noreferrer">
121 <span class="card-text">
122 <span class="card-arch">Android</span>
123 <span class="card-format">Google Play</span>
124 </span>
125 </a>
126 </div>
127 </section>
128
129 <section class="platform-section platform-section--cli" data-rise style="--d:420ms">
130 <div class="platform-header">
131 <svg class="platform-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 7l5 5-5 5M12 17h7"/></svg>
132 <h2 class="platform-name">Terminal</h2>
133 </div>
134 <code class="cli-line">npx t3@nightly</code>
135 </section>
136 </div>
137
138 <p class="releases-link" data-rise style="--d:480ms">
139 All builds are on the{" "}
140 <a id="releases-page-link" href={RELEASES_URL} target="_blank" rel="noopener noreferrer">
141 GitHub releases page<span aria-hidden="true"> &#8599;</span>
142 </a>
143 </p>
86 </div>144 </div>
87</Layout>145</Layout>
88146
89<script>147<script>
90 import { fetchLatestRelease, RELEASES_URL } from "../lib/releases";148 import { fetchLatestRelease, RELEASES_URL, NIGHTLY_RELEASES_URL, type ReleaseChannel } from "../lib/releases";
91149
92 async function init() {150 const page = document.getElementById("download-page")!;
93 const versionLabel = document.getElementById("version-label");151 const versionLabel = document.getElementById("version-label")!;
94 // Only release-asset links; mobile store cards have no data-asset and keep their href.152 const changelogLink = document.getElementById("changelog-link") as HTMLAnchorElement;
95 const cards = document.querySelectorAll<HTMLAnchorElement>("a[data-asset]");153 const releasesPageLink = document.getElementById("releases-page-link") as HTMLAnchorElement;
96154 const tabs = page.querySelectorAll<HTMLButtonElement>(".channel-toggle button");
97 try {155 // Only release-asset links; mobile store cards have no data-asset and keep their href.
98 const release = await fetchLatestRelease();156 const cards = page.querySelectorAll<HTMLAnchorElement>("a[data-asset]");
157
158 function readChannel(): ReleaseChannel {
159 return new URLSearchParams(location.search).get("channel") === "nightly" ? "nightly" : "stable";
160 }
99161
100 if (versionLabel && release.tag_name) {162 function relativeAge(iso: string): string {
101 versionLabel.textContent = `Latest (${release.tag_name})`;163 const minutes = Math.max(1, Math.round((Date.now() - Date.parse(iso)) / 60_000));
102 }164 if (minutes < 60) return `${minutes} minutes ago`;
165 const hours = Math.round(minutes / 60);
166 if (hours < 48) return `${hours} hours ago`;
167 return `${Math.round(hours / 24)} days ago`;
168 }
103169
104 const changelogLink = document.getElementById("changelog-link") as HTMLAnchorElement | null;170 async function load(channel: ReleaseChannel) {
105 if (changelogLink && release.html_url) {171 const fallback = channel === "nightly" ? NIGHTLY_RELEASES_URL : RELEASES_URL;
106 changelogLink.href = release.html_url;172 page.dataset.channel = channel;
107 changelogLink.style.display = "";173 tabs.forEach((tab) => tab.setAttribute("aria-selected", String(tab.dataset.channel === channel)));
108 }174 releasesPageLink.href = fallback;
175 changelogLink.href = fallback;
176 versionLabel.textContent = "\u00a0";
177 cards.forEach((card) => {
178 card.href = fallback;
179 });
109180
181 try {
182 const release = await fetchLatestRelease(channel);
183 // A toggle click while the fetch was in flight wins; drop the stale result.
184 if (page.dataset.channel !== channel) return;
185
186 // Stable shows its version. Nightly shows how fresh the build is; the tag is noise.
187 versionLabel.textContent =
188 channel === "nightly"
189 ? `Built ${relativeAge(release.published_at)}`
190 : release.tag_name;
191 if (release.html_url) changelogLink.href = release.html_url;
110 cards.forEach((card) => {192 cards.forEach((card) => {
111 const suffix = card.dataset.asset;193 const match = (release.assets ?? []).find((a) => a.name.endsWith(`-${card.dataset.asset}`));
112 if (!suffix) return;194 card.href = match?.browser_download_url ?? fallback;
113
114 const match = (release.assets ?? []).find((a) => a.name.endsWith(`-${suffix}`));
115 if (match) {
116 card.href = match.browser_download_url;
117 } else {
118 card.href = RELEASES_URL;
119 }
120 });195 });
121 } catch {196 } catch {
122 if (versionLabel) {197 if (page.dataset.channel === channel) versionLabel.textContent = "Releases";
123 versionLabel.textContent = "Could not load release info.";
124 }
125 cards.forEach((card) => {
126 card.href = RELEASES_URL;
127 });
128 }198 }
129 }199 }
130200
131 init();201 tabs.forEach((tab) => {
202 tab.addEventListener("click", () => {
203 const channel = tab.dataset.channel as ReleaseChannel;
204 const url = new URL(location.href);
205 if (channel === "nightly") url.searchParams.set("channel", "nightly");
206 else url.searchParams.delete("channel");
207 history.replaceState(null, "", url);
208 load(channel);
209 });
210 });
211
212 load(readChannel());
132</script>213</script>
133214
134<style>215<style>
...@@ -137,38 +218,158 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";...@@ -137,38 +218,158 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";
137 display: flex;218 display: flex;
138 flex-direction: column;219 flex-direction: column;
139 align-items: center;220 align-items: center;
140 padding: 8vh 1.5rem 4rem;221 padding: 0 1.5rem 4rem;
222 }
223
224 /* ── Header ── */
225
226 .download-head {
227 position: relative;
228 width: 100%;
229 display: flex;
230 flex-direction: column;
231 align-items: center;
232 padding: 9vh 1.5rem 2.5rem;
233 margin-bottom: 2rem;
141 }234 }
142235
143 /* ── Heading ── */236 /* Sky: the nightly DMG backdrop, masked so it dissolves into the page instead of ending at a line. */
237 .sky {
238 position: absolute;
239 top: -80px;
240 bottom: 0;
241 /* Break out of the page padding so the sky spans the full viewport width. */
242 left: 50%;
243 width: 100vw;
244 margin-left: -50vw;
245 z-index: -1;
246 background: url("/nightly-sky.svg") center / cover no-repeat;
247 -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 45%, transparent 100%);
248 mask-image: linear-gradient(to bottom, #000 0%, #000 45%, transparent 100%);
249 opacity: 0;
250 transform: scale(1.04);
251 transition: opacity 0.9s ease, transform 1.4s cubic-bezier(0.2, 0.7, 0.2, 1);
252 pointer-events: none;
253 }
254
255 .download-page[data-channel="nightly"] .sky {
256 opacity: 1;
257 transform: none;
258 }
259
260 .head-inner {
261 display: flex;
262 align-items: center;
263 gap: 1rem;
264 }
265
266 .head-icon {
267 position: relative;
268 width: 64px;
269 height: 64px;
270 flex-shrink: 0;
271 }
272
273 /* Both channel icons stay mounted and cross-fade, so the title never moves. */
274 .head-icon img,
275 .card-icon img {
276 position: absolute;
277 inset: 0;
278 width: 100%;
279 height: 100%;
280 transition: opacity 0.45s ease;
281 }
282
283 .head-icon--nightly,
284 .card-icon--nightly {
285 opacity: 0;
286 }
287
288 .download-page[data-channel="nightly"] .head-icon--stable,
289 .download-page[data-channel="nightly"] .card-icon--stable {
290 opacity: 0;
291 }
292
293 .download-page[data-channel="nightly"] .head-icon--nightly,
294 .download-page[data-channel="nightly"] .card-icon--nightly {
295 opacity: 1;
296 }
144297
145 .heading {298 .heading {
146 font-weight: 500;299 font-weight: 500;
147 font-size: clamp(1.75rem, 4vw, 2.75rem);300 font-size: clamp(1.75rem, 4vw, 2.75rem);
148 letter-spacing: -0.035em;301 letter-spacing: -0.035em;
149 line-height: 1.15;302 line-height: 1.15;
150 text-align: center;
151 margin-bottom: 0.5rem;
152 opacity: 0;
153 animation: fade-in 1s ease-out forwards;
154 }303 }
155304
156 .subheading {305 /* ── Channel toggle ── */
157 font-size: 0.9rem;306
158 color: var(--fg-dim);307 .channel-toggle {
159 margin-bottom: 3.5rem;308 position: relative;
160 opacity: 0;309 display: inline-flex;
161 animation: fade-in 1s ease-out 0.1s forwards;310 margin-top: 1.5rem;
162 display: flex;311 padding: 3px;
163 align-items: center;312 border: 1px solid var(--border-strong);
164 gap: 0.75rem;313 border-radius: 999px;
314 background: rgba(255, 255, 255, 0.03);
315 transition: border-color 0.45s ease, background 0.45s ease;
165 }316 }
166317
167 .changelog-link {318 .download-page[data-channel="nightly"] .channel-toggle {
319 border-color: rgba(255, 255, 255, 0.22);
320 background: rgba(7, 21, 47, 0.5);
321 }
322
323 /* The thumb slides between the two tabs; both tabs are the same width. */
324 .channel-thumb {
325 position: absolute;
326 top: 3px;
327 bottom: 3px;
328 left: 3px;
329 width: calc(50% - 3px);
330 border-radius: 999px;
331 background: rgba(255, 255, 255, 0.12);
332 transition: transform 0.35s cubic-bezier(0.2, 0.7, 0.2, 1), background 0.45s ease;
333 }
334
335 .download-page[data-channel="nightly"] .channel-thumb {
336 transform: translateX(100%);
337 background: rgba(255, 255, 255, 0.16);
338 }
339
340 .channel-toggle button {
341 position: relative;
342 width: 5.5rem;
343 padding: 7px 0;
344 border: 0;
345 border-radius: 999px;
346 background: transparent;
168 color: var(--fg-muted);347 color: var(--fg-muted);
348 font: inherit;
349 font-size: 13px;
350 cursor: pointer;
351 transition: color 0.25s ease;
352 }
353
354 .channel-toggle button:hover,
355 .channel-toggle button[aria-selected="true"] {
356 color: var(--fg);
357 }
358
359 .channel-toggle button[aria-selected="true"] {
360 font-weight: 500;
361 }
362
363 .head-meta {
364 margin-top: 1rem;
169 font-size: 0.8rem;365 font-size: 0.8rem;
366 min-height: 1.2em;
367 }
368
369 .changelog-link {
370 color: var(--fg-dim);
170 text-decoration: underline;371 text-decoration: underline;
171 text-decoration-color: rgba(161, 161, 170, 0.4);372 text-decoration-color: transparent;
172 text-underline-offset: 3px;373 text-underline-offset: 3px;
173 transition: color 0.3s ease, text-decoration-color 0.3s ease;374 transition: color 0.3s ease, text-decoration-color 0.3s ease;
174 }375 }
...@@ -178,6 +379,10 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";...@@ -178,6 +379,10 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";
178 text-decoration-color: var(--fg);379 text-decoration-color: var(--fg);
179 }380 }
180381
382 .download-page[data-channel="nightly"] .changelog-link {
383 color: #b9bdff;
384 }
385
181 /* ── Platforms ── */386 /* ── Platforms ── */
182387
183 .platforms {388 .platforms {
...@@ -186,8 +391,6 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";...@@ -186,8 +391,6 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";
186 display: flex;391 display: flex;
187 flex-direction: column;392 flex-direction: column;
188 gap: 2.5rem;393 gap: 2.5rem;
189 opacity: 0;
190 animation: fade-in 1s ease-out 0.2s forwards;
191 }394 }
192395
193 .platform-section {396 .platform-section {
...@@ -196,6 +399,18 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";...@@ -196,6 +399,18 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";
196 gap: 0.75rem;399 gap: 0.75rem;
197 }400 }
198401
402 .platform-section--cli {
403 display: none;
404 }
405
406 .download-page[data-channel="nightly"] .platform-section--mobile {
407 display: none;
408 }
409
410 .download-page[data-channel="nightly"] .platform-section--cli {
411 display: flex;
412 }
413
199 .platform-header {414 .platform-header {
200 display: flex;415 display: flex;
201 align-items: center;416 align-items: center;
...@@ -217,6 +432,17 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";...@@ -217,6 +432,17 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";
217 letter-spacing: -0.01em;432 letter-spacing: -0.01em;
218 }433 }
219434
435 .cli-line {
436 align-self: flex-start;
437 font-family: var(--font-mono);
438 font-size: 0.85rem;
439 padding: 0.6rem 0.9rem;
440 border-radius: 10px;
441 border: 1px solid rgba(105, 111, 234, 0.35);
442 background: rgba(21, 20, 67, 0.3);
443 color: var(--fg);
444 }
445
220 /* ── Download cards ── */446 /* ── Download cards ── */
221447
222 .download-cards {448 .download-cards {
...@@ -227,13 +453,13 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";...@@ -227,13 +453,13 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";
227453
228 .download-card {454 .download-card {
229 display: flex;455 display: flex;
230 flex-direction: column;456 align-items: center;
231 gap: 0.2rem;457 gap: 0.75rem;
232 padding: 0.85rem 1rem;458 padding: 0.85rem 1rem;
233 border-radius: 10px;459 border-radius: 10px;
234 border: 1px solid var(--border);460 border: 1px solid var(--border);
235 background: rgba(255, 255, 255, 0.02);461 background: rgba(255, 255, 255, 0.02);
236 transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;462 transition: background 0.45s ease, border-color 0.45s ease, transform 0.2s ease;
237 }463 }
238464
239 .download-card:hover {465 .download-card:hover {
...@@ -246,6 +472,28 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";...@@ -246,6 +472,28 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";
246 transform: translateY(0);472 transform: translateY(0);
247 }473 }
248474
475 .download-page[data-channel="nightly"] .download-card[data-asset] {
476 border-color: rgba(105, 111, 234, 0.4);
477 background: linear-gradient(135deg, rgba(81, 101, 216, 0.1), rgba(168, 91, 234, 0.06));
478 }
479
480 .download-page[data-channel="nightly"] .download-card[data-asset]:hover {
481 border-color: rgba(105, 111, 234, 0.7);
482 }
483
484 .card-icon {
485 position: relative;
486 width: 28px;
487 height: 28px;
488 flex-shrink: 0;
489 }
490
491 .card-text {
492 display: flex;
493 flex-direction: column;
494 gap: 0.2rem;
495 }
496
249 .card-arch {497 .card-arch {
250 font-weight: 600;498 font-weight: 600;
251 font-size: 0.95rem;499 font-size: 0.95rem;
...@@ -254,6 +502,11 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";...@@ -254,6 +502,11 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";
254 .card-format {502 .card-format {
255 font-size: 0.8rem;503 font-size: 0.8rem;
256 color: var(--fg-dim);504 color: var(--fg-dim);
505 transition: color 0.45s ease;
506 }
507
508 .download-page[data-channel="nightly"] .card-format {
509 color: #b6b0ff;
257 }510 }
258511
259 .intel-note {512 .intel-note {
...@@ -261,7 +514,8 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";...@@ -261,7 +514,8 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";
261 color: var(--fg-dim);514 color: var(--fg-dim);
262 }515 }
263516
264 .intel-note a {517 .intel-note a,
518 .releases-link a {
265 color: var(--fg-muted);519 color: var(--fg-muted);
266 text-decoration: underline;520 text-decoration: underline;
267 text-decoration-color: rgba(161, 161, 170, 0.4);521 text-decoration-color: rgba(161, 161, 170, 0.4);
...@@ -269,7 +523,8 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";...@@ -269,7 +523,8 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";
269 transition: color 0.3s ease, text-decoration-color 0.3s ease;523 transition: color 0.3s ease, text-decoration-color 0.3s ease;
270 }524 }
271525
272 .intel-note a:hover {526 .intel-note a:hover,
527 .releases-link a:hover {
273 color: var(--fg);528 color: var(--fg);
274 text-decoration-color: var(--fg);529 text-decoration-color: var(--fg);
275 }530 }
...@@ -280,28 +535,35 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";...@@ -280,28 +535,35 @@ import { ANDROID_PLAY_STORE_URL, IOS_APP_STORE_URL } from "../lib/site";
280 margin-top: 3rem;535 margin-top: 3rem;
281 font-size: 0.825rem;536 font-size: 0.825rem;
282 color: var(--fg-dim);537 color: var(--fg-dim);
283 opacity: 0;
284 animation: fade-in 1s ease-out 0.35s forwards;
285 }538 }
286539
287 .releases-link a {540 /* ── Motion ── */
288 color: var(--fg-muted);
289 text-decoration: underline;
290 text-decoration-color: rgba(161, 161, 170, 0.4);
291 text-underline-offset: 3px;
292 transition: color 0.3s ease, text-decoration-color 0.3s ease;
293 }
294541
295 .releases-link a:hover {542 @media (prefers-reduced-motion: reduce) {
296 color: var(--fg);543 .sky,
297 text-decoration-color: var(--fg);544 .head-icon img,
545 .card-icon img,
546 .channel-thumb,
547 .channel-toggle,
548 .download-card {
549 transition: none;
550 }
298 }551 }
299552
300 /* ── Mobile ── */553 /* ── Mobile ── */
301554
302 @media (max-width: 640px) {555 @media (max-width: 640px) {
303 .download-page {556 .download-page {
304 padding: 5vh 1.25rem 4rem;557 padding: 0 1.25rem 4rem;
558 }
559
560 .download-head {
561 padding: 6vh 1.25rem 2rem;
562 }
563
564 .head-inner {
565 flex-direction: column;
566 text-align: center;
305 }567 }
306568
307 .platforms {569 .platforms {