diff --git a/README.md b/README.md index 22c974a..0d0980d 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,9 @@ bun run preview # serve dist/ ### Direct downloads Every download button links straight to the file (`platforms.linux.url` in `src/config.ts`), with no -intermediate page. Only visitors on Windows, macOS or a phone are sent to `/download/`, because the -Linux AppImage is no use to them yet. Point `url` at any direct file URL. By default it is the GitHub -Releases asset, which is a plain download link. Don't host the 157 MB file with the site itself on +intermediate page. Windows visitors get the .exe on the same button; macOS and phone visitors are sent to `/download/`, because the +Linux AppImage is no use to them. Point `url` at any direct file URL. By default it is the release on git.joji.li, which is a plain download link. +Don't host the 157 MB file with the site itself on GitHub Pages (100 MB per-file limit) or Cloudflare Pages (25 MB); use Releases, R2/S3 or a CDN. ### Releasing a new version @@ -83,4 +83,4 @@ Add `Sitemap: /sitemap-index.xml` to `public/robots.txt` once the URL - `public/og.png` (1200x630) is a static file; replace it if the tagline changes. - The `typescript` dependency is pinned to 6.x because `astro check` does not support TypeScript 7 yet. -- Builds are unsigned and Windows/macOS are "coming soon" by design: see `platforms.*.available` in `config.ts`. +- Builds are unsigned and macOS is "coming soon" by design: see `platforms.*.available` in `config.ts`. diff --git a/scripts/capture/capture.cjs b/scripts/capture/capture.cjs index 6c8ea01..f935a61 100644 --- a/scripts/capture/capture.cjs +++ b/scripts/capture/capture.cjs @@ -1,72 +1,118 @@ 'use strict'; -// Runs the real Ink app against a seeded demo vault and saves 16:10 screenshots. -// electron scripts/capture/capture.cjs --ozone-platform=headless --force-device-scale-factor=1.5 -// Env: INK_APP (path to the Ink repo, read only), OUT (output dir), THEMES (comma list), SCENES (comma list) -const { app, BrowserWindow } = require('electron'); +// Runs the packaged Ink app (the released AppImage, extracted) against a seeded demo vault and saves 16:10 +// screenshots, driving it over the Chrome DevTools protocol. Nothing is read from the app's source. +// node scripts/capture/capture.cjs (env: INK_BIN, OUT, THEME, SCENES; run.sh sets these up) +const { spawn } = require('node:child_process'); const path = require('node:path'); const os = require('node:os'); const fs = require('node:fs'); -const INK = process.env.INK_APP || path.resolve(__dirname, '../../../../App/Ink'); +const BIN = process.env.INK_BIN; const OUT = process.env.OUT || path.resolve(__dirname, '../../src/assets/screens'); -const THEMES = (process.env.THEMES || 'dark,light').split(','); const ONLY = process.env.SCENES ? process.env.SCENES.split(',') : null; +const theme = process.env.THEME || 'dark'; +const palette = process.env.PALETTE || 'default'; +const accent = process.env.ACCENT || 'blush'; +const SUFFIX = process.env.SUFFIX || theme; const { seedVault } = require('./seed-vault.cjs'); const scenes = require('./scenes.cjs'); +if (!BIN) throw new Error('Set INK_BIN to the extracted AppImage\'s `ink` binary (run.sh does this).'); -const W = 1600, H = 1000; +const W = 1600, H = 1000, SCALE = 1.5, PORT = 9300 + (process.pid % 500); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); -const theme = process.env.THEME || THEMES[0]; const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ink-shots-')); const vault = path.join(tmp, 'Notes'); fs.mkdirSync(vault); const seeded = seedVault(vault); - -// No network: a link card gets a fixed title and description instead of fetching. -const linkpreview = require(path.join(INK, 'electron/linkpreview.cjs')); -linkpreview.hooks.lookup = async () => [{ address: '93.184.216.34' }]; -linkpreview.hooks.fetch = async () => new Response( - 'JSON Canvas' + - '', { status: 200, headers: { 'content-type': 'text/html' } }); - -app.setPath('userData', tmp); fs.writeFileSync(path.join(tmp, 'settings.json'), JSON.stringify({ - vault, lastPage: seeded.lastPage, theme, bounds: { width: W, height: H }, + vault, lastPage: seeded.lastPage, theme, palette, accent, bounds: { width: W, height: H }, fontSize: 16, outline: false, sidebarCollapsed: false, tagsOpen: true, vaults: { [vault]: seeded.vaultSettings }, })); -app.commandLine.appendSwitch('lang', 'en-US'); -require(path.join(INK, 'electron/main.cjs')); -app.whenReady().then(async () => { - let win; - for (let i = 0; i < 100 && !(win = BrowserWindow.getAllWindows()[0]); i++) await sleep(100); - win.setContentSize(W, H); - const wc = win.webContents; - if (wc.isLoading()) await new Promise((r) => wc.once('did-finish-load', r)); - const js = (code) => wc.executeJavaScript(`(async () => { ${code} })()`); - const key = async (keyCode, modifiers = []) => { wc.sendInputEvent({ type: 'keyDown', keyCode, modifiers }); wc.sendInputEvent({ type: 'keyUp', keyCode, modifiers }); await sleep(250); }; - const type = async (t, d = 45) => { for (const ch of t) { wc.sendInputEvent({ type: 'char', keyCode: ch }); await sleep(d); } await sleep(200); }; - const click = async (x, y) => { wc.sendInputEvent({ type: 'mouseMove', x, y }); wc.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount: 1 }); wc.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount: 1 }); await sleep(250); }; +const KEYS = { Escape: ['Escape', 27], Return: ['Enter', 13], Enter: ['Enter', 13], Tab: ['Tab', 9], Backspace: ['Backspace', 8], Down: ['ArrowDown', 40], Up: ['ArrowUp', 38] }; +const MODS = { alt: 1, control: 2, meta: 4, shift: 8 }; + +async function connect() { + for (let i = 0; i < 150; i++) { + try { + const list = await (await fetch(`http://127.0.0.1:${PORT}/json`)).json(); + const page = list.find((t) => t.type === 'page' && !t.url.startsWith('devtools')); + if (page) { + const ws = new WebSocket(page.webSocketDebuggerUrl); + await new Promise((res, rej) => { ws.onopen = res; ws.onerror = rej; }); + return ws; + } + } catch {} + await sleep(200); + } + throw new Error('Ink did not start'); +} + +(async () => { + const proc = spawn(BIN, [ + `--user-data-dir=${tmp}`, `--remote-debugging-port=${PORT}`, '--ozone-platform=headless', '--no-sandbox', '--lang=en-US', + ], { stdio: 'ignore' }); + const ws = await connect(); + let id = 0; + const pending = new Map(); + ws.onmessage = (m) => { + const d = JSON.parse(m.data); + if (d.id && pending.has(d.id)) { const { res, rej } = pending.get(d.id); pending.delete(d.id); d.error ? rej(new Error(d.error.message)) : res(d.result); } + }; + const send = (method, params = {}) => new Promise((res, rej) => { pending.set(++id, { res, rej }); ws.send(JSON.stringify({ id, method, params })); }); + + await send('Emulation.setDeviceMetricsOverride', { width: W, height: H, deviceScaleFactor: SCALE, mobile: false }); + await send('Runtime.enable'); + + const js = async (code) => { + const r = await send('Runtime.evaluate', { expression: `(async () => { ${code} })()`, awaitPromise: true, returnByValue: true }); + if (r.exceptionDetails) throw new Error(r.exceptionDetails.exception?.description || r.exceptionDetails.text); + return r.result.value; + }; + const key = async (name, modifiers = []) => { + const m = modifiers.reduce((a, k) => a | MODS[k], 0); + const [k, vk] = KEYS[name] || [name, name.length === 1 ? name.toUpperCase().charCodeAt(0) : 0]; + const text = !m || m === 8 ? (name.length === 1 ? name : undefined) : undefined; + const code = name.length === 1 && /[a-z]/i.test(name) ? `Key${name.toUpperCase()}` : /^\d$/.test(name) ? `Digit${name}` : k; + await send('Input.dispatchKeyEvent', { type: 'rawKeyDown', key: k, code, windowsVirtualKeyCode: vk, modifiers: m }); + await send('Input.dispatchKeyEvent', { type: 'keyUp', key: k, code, windowsVirtualKeyCode: vk, modifiers: m }); + await sleep(250); + }; + const type = async (t, d = 45) => { + for (const ch of t) { + await send('Input.dispatchKeyEvent', { type: 'keyDown', key: ch, text: ch, unmodifiedText: ch, windowsVirtualKeyCode: ch.toUpperCase().charCodeAt(0) }); + await send('Input.dispatchKeyEvent', { type: 'keyUp', key: ch }); + await sleep(d); + } + await sleep(200); + }; + const click = async (x, y) => { + await send('Input.dispatchMouseEvent', { type: 'mouseMoved', x, y }); + await send('Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button: 'left', clickCount: 1 }); + await send('Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button: 'left', clickCount: 1 }); + await sleep(250); + }; const shot = async (name) => { await sleep(500); - const img = await wc.capturePage(); - const file = path.join(OUT, `${name}-${theme}.png`); + const r = await send('Page.captureScreenshot', { format: 'png' }); + const file = path.join(OUT, `${name}-${SUFFIX}.png`); fs.mkdirSync(OUT, { recursive: true }); - fs.writeFileSync(file, img.toPNG()); - const s = img.getSize(); - console.log(`saved ${path.basename(file)} ${s.width}x${s.height}`); + fs.writeFileSync(file, Buffer.from(r.data, 'base64')); + console.log(`saved ${path.basename(file)}`); }; - const ctx = { win, wc, js, key, type, click, shot, sleep, vault, W, H }; + const ctx = { js, key, type, click, shot, sleep, vault, W, H, send }; try { - await sleep(2200); + await sleep(3000); for (const [name, fn] of Object.entries(scenes)) { if (ONLY && !ONLY.includes(name)) continue; try { await fn(ctx); } catch (e) { console.log(`FAILED ${name}: ${e.message}`); } } } finally { - app.exit(0); + ws.close(); + proc.kill(); + fs.rmSync(tmp, { recursive: true, force: true }); } -}); +})(); diff --git a/scripts/capture/run.sh b/scripts/capture/run.sh index a431058..87b346d 100755 --- a/scripts/capture/run.sh +++ b/scripts/capture/run.sh @@ -1,12 +1,14 @@ #!/usr/bin/env sh -# Captures all website screenshots from the real Ink app (read only) into src/assets/screens/. -# Usage: scripts/capture/run.sh (env: INK_APP, OUT, SCENES) +# Captures all website screenshots from the released Ink AppImage into src/assets/screens/. +# Usage: scripts/capture/run.sh Ink-1.0.0-beta-x86_64.AppImage (env: OUT, SCENES) set -e HERE="$(cd "$(dirname "$0")" && pwd)" -INK_APP="${INK_APP:-$HERE/../../../../App/Ink}" -INK_APP="$(cd "$INK_APP" && pwd)" -export INK_APP +APPIMAGE="$(realpath "${1:-${INK_APPIMAGE:?pass the AppImage path}}")" +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT +# Extract in an empty directory of its own; the AppImage is only unpacked, never run from here. +(cd "$WORK" && cp "$APPIMAGE" ink.AppImage && chmod +x ink.AppImage && ./ink.AppImage --appimage-extract >/dev/null) +export INK_BIN="$WORK/squashfs-root/ink" for theme in dark light; do - THEME=$theme "$INK_APP/node_modules/.bin/electron" "$HERE/capture.cjs" \ - --ozone-platform=headless --force-device-scale-factor=1.5 --no-sandbox 2>&1 | grep -E 'saved|FAILED|Error' || true + THEME=$theme node "$HERE/capture.cjs" 2>&1 | grep -E 'saved|FAILED|Error' || true done diff --git a/scripts/capture/scenes.cjs b/scripts/capture/scenes.cjs index aaa0a3c..1ca01c0 100644 --- a/scripts/capture/scenes.cjs +++ b/scripts/capture/scenes.cjs @@ -110,7 +110,7 @@ module.exports = { }, async settings(ctx) { - const { sleep, shot, key } = ctx; + const { js, sleep, shot, key } = ctx; await reset(ctx); await openFromSidebar(ctx, 'Roadmap'); await key(',', ['control']); @@ -132,4 +132,39 @@ module.exports = { await shot('history'); await key('Escape'); }, + + async databases(ctx) { + const { js, sleep, shot } = ctx; + await reset(ctx); + await openFromSidebar(ctx, 'Feature tracker'); + await sleep(1200); + await scrollPage(js, 230); + await sleep(400); + await shot('databases'); + }, + + async 'database-board'(ctx) { + const { js, sleep, shot } = ctx; + await reset(ctx); + await openFromSidebar(ctx, 'Feature tracker'); + await sleep(800); + await js(`[...document.querySelectorAll('button, [role="tab"]')].find((b) => b.textContent.trim() === 'Board')?.click()`); + await sleep(1000); + await scrollPage(js, 230); + await sleep(400); + await shot('database-board'); + }, + + // The settings dialog on the color theme and accent pickers. Run once per palette by run.sh. + async themes(ctx) { + const { js, sleep, shot, key } = ctx; + await reset(ctx); + await openFromSidebar(ctx, 'Roadmap'); + await key(',', ['control']); + await sleep(900); + await js(`document.querySelector('[aria-label="Color theme"]')?.scrollIntoView({ block: 'center' })`); + await sleep(500); + await shot('themes'); + await key('Escape'); + }, }; diff --git a/scripts/capture/seed-vault.cjs b/scripts/capture/seed-vault.cjs index ac2c548..08316fe 100644 --- a/scripts/capture/seed-vault.cjs +++ b/scripts/capture/seed-vault.cjs @@ -60,7 +60,7 @@ function seedVault(root) { - [x] Ship the first public build as an AppImage - [x] Canvas export to PNG, SVG and PDF - [x] Version history for pages and canvases -- [ ] Test the Windows installer on a clean machine +- [x] Windows installer and Linux deb - [ ] Write the docs, starting with [[Keyboard shortcuts]] - [ ] Follow the [[Launch plan]] @@ -69,8 +69,8 @@ function seedVault(root) { \`\`\`js const release = { - version: '0.1.0', - platforms: ['linux'], + version: '1.0.0-beta', + platforms: ['linux', 'windows'], notes: 'plain .md files, offline, no account', }; console.log(\`Shipping Ink \${release.version}\`); @@ -79,7 +79,7 @@ console.log(\`Shipping Ink \${release.version}\`);
Why Linux first? -The AppImage is the only target built and tested so far. Windows and macOS are configured but still need a proper pass. +The AppImage was the first target built and tested. Windows and the deb followed; macOS is still to do.
`); @@ -142,7 +142,6 @@ tags: [work, ink] ## To do -- [ ] Windows installer test pass - [ ] macOS dmg build - [ ] Docs: where notes are stored - [ ] Verify checksum instructions @@ -151,13 +150,14 @@ tags: [work, ink] ## Doing - [ ] Website screenshots -- [ ] Changelog for 0.1.0 +- [ ] Changelog for 1.0.0-beta - [ ] Spellcheck language picker polish ## Done -- [x] AppImage build +- [x] AppImage and deb builds +- [x] Windows installer - [x] Canvas export (PNG, SVG, PDF) - [x] Version history for canvases - [x] Graph view filters @@ -199,6 +199,42 @@ The release is a single AppImage, so the checklist is short: bump the version, w Once that is done, the website can go live with a plain download button and nothing else. `); + + // A database: the page holds the schema, every record is a sub-page whose properties are front matter. + const dbSchema = { + properties: [ + { key: 'status', name: 'Status', type: 'select', options: ['Backlog', 'In progress', 'Review', 'Done'], colors: { Backlog: 'gray', 'In progress': 'blue', Review: 'yellow', Done: 'green' } }, + { key: 'area', name: 'Area', type: 'select', options: ['Editor', 'Canvas', 'Databases', 'Themes', 'Site'], colors: { Editor: 'pink', Canvas: 'purple', Databases: 'teal', Themes: 'orange', Site: 'blue' } }, + { key: 'owner', name: 'Owner', type: 'people' }, + { key: 'start', name: 'Start', type: 'date' }, + { key: 'due', name: 'Due', type: 'date' }, + { key: 'progress', name: 'Progress', type: 'number', format: 'bar', max: 100 }, + { key: 'shipped', name: 'Shipped', type: 'checkbox' }, + ], + views: [ + { id: 'vtable', name: 'Table', type: 'table', sort: { key: 'due', dir: 'asc' }, filters: [], hidden: [], widths: {}, calc: {} }, + { id: 'vboard', name: 'Board', type: 'board', groupBy: 'status', sort: null, filters: [], hidden: [], widths: {}, calc: {} }, + { id: 'vcal', name: 'Calendar', type: 'calendar', dateProp: 'due', sort: null, filters: [], hidden: [], widths: {}, calc: {} }, + { id: 'vtime', name: 'Timeline', type: 'timeline', dateProp: 'start', endProp: 'due', sort: null, filters: [], hidden: [], widths: {}, calc: {} }, + ], + }; + w(root, 'Feature tracker.md', fm({ icon: '馃Л', tags: ['work', 'ink'], 'ink-database': 'basic' }) + `Everything planned for the next release, one record per feature.\n\n%% ink-database\n${JSON.stringify(dbSchema, null, 2)}\n%%\n`); + const recs = [ + ['Color themes', 'Themes', 'Done', ['Mara'], '2026-09-14', '2026-09-26', 100, true], + ['Accent colors', 'Themes', 'Done', ['Mara'], '2026-09-20', '2026-10-01', 100, true], + ['Table view', 'Databases', 'Done', ['Jo'], '2026-09-08', '2026-10-02', 100, true], + ['Board view', 'Databases', 'Review', ['Jo', 'Ana'], '2026-09-24', '2026-10-12', 80, false], + ['Calendar view', 'Databases', 'In progress', ['Ana'], '2026-10-01', '2026-10-16', 60, false], + ['Timeline view', 'Databases', 'In progress', ['Jo'], '2026-10-05', '2026-10-22', 40, false], + ['Formula properties', 'Databases', 'Backlog', ['Jo'], '2026-10-14', '2026-10-30', 10, false], + ['Canvas alignment guides', 'Canvas', 'Done', ['Ana'], '2026-09-01', '2026-09-18', 100, true], + ['Quick capture', 'Editor', 'Review', ['Mara'], '2026-10-03', '2026-10-14', 90, false], + ['Website refresh', 'Site', 'In progress', ['Mara', 'Jo'], '2026-10-08', '2026-10-20', 55, false], + ]; + for (const [t, area, status, owner, start, due, progress, shipped] of recs) { + w(root, `Feature tracker/${t}.md`, fm({ area, status, owner, start, due, progress, shipped }) + `${t}: notes and decisions live here, as an ordinary page.\n`); + } + // Earlier versions of the Roadmap page, so version history has something to show. const now = Date.now(); const hist = [ @@ -217,7 +253,7 @@ Once that is done, the website can go live with a plain download button and noth return { lastPage: 'Roadmap', vaultSettings: { - pins: ['Roadmap', 'Reading list', 'Sprint board', 'Project map.canvas'], + pins: ['Roadmap', 'Feature tracker', 'Sprint board', 'Project map.canvas'], recent: ['Roadmap', 'Reading list', 'Sprint board'], tagColors: { work: 'blue', ink: 'pink', reading: 'yellow', learning: 'teal', personal: 'green', ideas: 'purple' }, }, diff --git a/src/assets/screens/board-dark.png b/src/assets/screens/board-dark.png index 0a13240..17e39c6 100644 Binary files a/src/assets/screens/board-dark.png and b/src/assets/screens/board-dark.png differ diff --git a/src/assets/screens/board-light.png b/src/assets/screens/board-light.png index ecfa555..1870820 100644 Binary files a/src/assets/screens/board-light.png and b/src/assets/screens/board-light.png differ diff --git a/src/assets/screens/canvas-dark.png b/src/assets/screens/canvas-dark.png index 08718b8..48b048f 100644 Binary files a/src/assets/screens/canvas-dark.png and b/src/assets/screens/canvas-dark.png differ diff --git a/src/assets/screens/canvas-light.png b/src/assets/screens/canvas-light.png index 479f1da..5ed98f7 100644 Binary files a/src/assets/screens/canvas-light.png and b/src/assets/screens/canvas-light.png differ diff --git a/src/assets/screens/database-board-dark.png b/src/assets/screens/database-board-dark.png new file mode 100644 index 0000000..d18c483 Binary files /dev/null and b/src/assets/screens/database-board-dark.png differ diff --git a/src/assets/screens/database-board-light.png b/src/assets/screens/database-board-light.png new file mode 100644 index 0000000..53f4ff9 Binary files /dev/null and b/src/assets/screens/database-board-light.png differ diff --git a/src/assets/screens/databases-dark.png b/src/assets/screens/databases-dark.png new file mode 100644 index 0000000..377ebf5 Binary files /dev/null and b/src/assets/screens/databases-dark.png differ diff --git a/src/assets/screens/databases-light.png b/src/assets/screens/databases-light.png new file mode 100644 index 0000000..7c51d12 Binary files /dev/null and b/src/assets/screens/databases-light.png differ diff --git a/src/assets/screens/editor-dark.png b/src/assets/screens/editor-dark.png index 479967c..b3be612 100644 Binary files a/src/assets/screens/editor-dark.png and b/src/assets/screens/editor-dark.png differ diff --git a/src/assets/screens/editor-light.png b/src/assets/screens/editor-light.png index 661fd80..6039a35 100644 Binary files a/src/assets/screens/editor-light.png and b/src/assets/screens/editor-light.png differ diff --git a/src/assets/screens/graph-dark.png b/src/assets/screens/graph-dark.png index 285afac..53f993e 100644 Binary files a/src/assets/screens/graph-dark.png and b/src/assets/screens/graph-dark.png differ diff --git a/src/assets/screens/graph-light.png b/src/assets/screens/graph-light.png index 79fad9f..f129e84 100644 Binary files a/src/assets/screens/graph-light.png and b/src/assets/screens/graph-light.png differ diff --git a/src/assets/screens/history-dark.png b/src/assets/screens/history-dark.png index 91d48c4..d3285db 100644 Binary files a/src/assets/screens/history-dark.png and b/src/assets/screens/history-dark.png differ diff --git a/src/assets/screens/history-light.png b/src/assets/screens/history-light.png index 99c6834..14c904e 100644 Binary files a/src/assets/screens/history-light.png and b/src/assets/screens/history-light.png differ diff --git a/src/assets/screens/linking-dark.png b/src/assets/screens/linking-dark.png index 4836759..2abdd6c 100644 Binary files a/src/assets/screens/linking-dark.png and b/src/assets/screens/linking-dark.png differ diff --git a/src/assets/screens/linking-light.png b/src/assets/screens/linking-light.png index 2957736..3d0b54f 100644 Binary files a/src/assets/screens/linking-light.png and b/src/assets/screens/linking-light.png differ diff --git a/src/assets/screens/palette-dark.png b/src/assets/screens/palette-dark.png index 9ac5bb2..7dfeb71 100644 Binary files a/src/assets/screens/palette-dark.png and b/src/assets/screens/palette-dark.png differ diff --git a/src/assets/screens/palette-light.png b/src/assets/screens/palette-light.png index 2a3f142..c476e14 100644 Binary files a/src/assets/screens/palette-light.png and b/src/assets/screens/palette-light.png differ diff --git a/src/assets/screens/settings-dark.png b/src/assets/screens/settings-dark.png index 599f922..4393a3d 100644 Binary files a/src/assets/screens/settings-dark.png and b/src/assets/screens/settings-dark.png differ diff --git a/src/assets/screens/settings-light.png b/src/assets/screens/settings-light.png index 56d74d9..42f8cba 100644 Binary files a/src/assets/screens/settings-light.png and b/src/assets/screens/settings-light.png differ diff --git a/src/assets/screens/slash-menu-dark.png b/src/assets/screens/slash-menu-dark.png index e63a0c4..5516aa3 100644 Binary files a/src/assets/screens/slash-menu-dark.png and b/src/assets/screens/slash-menu-dark.png differ diff --git a/src/assets/screens/slash-menu-light.png b/src/assets/screens/slash-menu-light.png index 8b66172..6747713 100644 Binary files a/src/assets/screens/slash-menu-light.png and b/src/assets/screens/slash-menu-light.png differ diff --git a/src/assets/screens/themes-dark.png b/src/assets/screens/themes-dark.png new file mode 100644 index 0000000..315e4db Binary files /dev/null and b/src/assets/screens/themes-dark.png differ diff --git a/src/assets/screens/themes-light.png b/src/assets/screens/themes-light.png new file mode 100644 index 0000000..eb61a08 Binary files /dev/null and b/src/assets/screens/themes-light.png differ diff --git a/src/components/DownloadButton.astro b/src/components/DownloadButton.astro index 52bb5c2..c987558 100644 --- a/src/components/DownloadButton.astro +++ b/src/components/DownloadButton.astro @@ -1,21 +1,23 @@ --- // A direct download: the href is the file itself, in the HTML, so it works without JS and never -// bounces through another page. Only visitors on Windows, macOS or a phone (detected in -// scripts/site.ts) are sent to /download/ instead, because the Linux AppImage is no use to them yet. +// bounces through another page. Only visitors on macOS or a phone (detected in +// scripts/site.ts) are sent to /download/ instead, because the Linux AppImage is no use to them. import { config, formatSize } from '../config'; import { href } from '../lib/url'; interface Props { variant?: 'primary' | 'default'; note?: boolean; center?: boolean } const { variant = 'primary', note = true, center = false } = Astro.props; const linux = config.platforms.linux; +const win = config.platforms.windows; const meta = `v${config.version} 路 ${linux.format} 路 ${formatSize(linux.bytes)}`; +const winMeta = `v${config.version} 路 ${win.format} 路 ${formatSize(win.bytes)}`; --- -
+
Download for Linux - {note &&

{meta} 路 Windows & macOS coming soon

} + {note &&

{meta} 路 also for Windows and as a deb 路 macOS coming soon

}