Initial commit
No files matched your search
@@ -0,0 +1,43 @@
|
|||||||
|
name: Deploy to GitHub Pages
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: pages
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: oven-sh/setup-bun@v2
|
||||||
|
- run: bun install --frozen-lockfile
|
||||||
|
- name: Build
|
||||||
|
run: bun run build
|
||||||
|
env:
|
||||||
|
# User/org site or custom domain: set SITE_URL, leave BASE_PATH unset.
|
||||||
|
# Project site: SITE_URL=https://<user>.github.io and BASE_PATH=/<repo>.
|
||||||
|
SITE_URL: https://example.github.io
|
||||||
|
BASE_PATH: /ink-website
|
||||||
|
- uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
steps:
|
||||||
|
- id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.astro
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import sitemap from '@astrojs/sitemap';
|
||||||
|
import { readdirSync, readFileSync, rmSync, statSync } from 'node:fs';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
|
// Astro emits every imported screenshot at full size even when pages only use the optimised
|
||||||
|
// WebP variants. Drop originals nothing links to, so a deploy isn't ~17 MB heavier than needed.
|
||||||
|
function pruneUnusedOriginals() {
|
||||||
|
return {
|
||||||
|
name: 'prune-unused-originals',
|
||||||
|
hooks: {
|
||||||
|
'astro:build:done': ({ dir }) => {
|
||||||
|
const root = fileURLToPath(dir);
|
||||||
|
const files = [];
|
||||||
|
const walk = (d) => {
|
||||||
|
for (const n of readdirSync(d)) {
|
||||||
|
const p = join(d, n);
|
||||||
|
statSync(p).isDirectory() ? walk(p) : files.push(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walk(root);
|
||||||
|
const text = files
|
||||||
|
.filter((f) => /\.(html|css|js|xml|json|webmanifest)$/.test(f))
|
||||||
|
.map((f) => readFileSync(f, 'utf8'))
|
||||||
|
.join('\n');
|
||||||
|
for (const f of files) {
|
||||||
|
if (!/_astro[\\/][^\\/]+\.(png|jpe?g)$/.test(f)) continue;
|
||||||
|
const name = f.split(/[\\/]/).pop();
|
||||||
|
if (!text.includes(name)) rmSync(f);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// SITE_URL / BASE_PATH let CI set these without editing the file
|
||||||
|
// (e.g. a GitHub Pages project site: https://user.github.io + /ink-website).
|
||||||
|
export default defineConfig({
|
||||||
|
site: process.env.SITE_URL ?? 'https://ink.example',
|
||||||
|
base: process.env.BASE_PATH ?? '/',
|
||||||
|
output: 'static',
|
||||||
|
trailingSlash: 'always',
|
||||||
|
integrations: [sitemap(), pruneUnusedOriginals()],
|
||||||
|
build: { inlineStylesheets: 'auto' },
|
||||||
|
// lightningcss merges `backdrop-filter` + `-webkit-backdrop-filter` into the prefixed one only,
|
||||||
|
// which Chrome and Firefox ignore (no blur). esbuild leaves both declarations alone.
|
||||||
|
vite: { build: { cssMinify: 'esbuild' } },
|
||||||
|
devToolbar: { enabled: false },
|
||||||
|
});
|
||||||
@@ -0,0 +1,770 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"configVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "ink-website",
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/sitemap": "^3.7.4",
|
||||||
|
"@fontsource-variable/inter": "^5.3.0",
|
||||||
|
"@fontsource/jetbrains-mono": "^5.3.0",
|
||||||
|
"astro": "^7.3.8",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@astrojs/check": "^0.9.10",
|
||||||
|
"esbuild": "^0.28.2",
|
||||||
|
"sharp": "^0.35.5",
|
||||||
|
"typescript": "^6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@astrojs/astro2tsx": ["@astrojs/astro2tsx@0.1.2", "", { "dependencies": { "@emnapi/core": "1.11.3", "@napi-rs/wasm-runtime": "1.2.4" } }, "sha512-Uj/M9zbrhu5cIZEvWAGTbQJRKSxXWJ/E59BZwt7F+chrKhbfttW+9rkS/l+1QxoJP1KU3UnpM62M7yszfAI0WA=="],
|
||||||
|
|
||||||
|
"@astrojs/check": ["@astrojs/check@0.9.10", "", { "dependencies": { "@astrojs/language-server": "^2.16.7", "chokidar": "^4.0.3", "kleur": "^4.1.5", "yargs": "^18.0.0" }, "peerDependencies": { "typescript": "^5.0.0 || ^6.0.0" }, "bin": { "astro-check": "./bin/astro-check.js" } }, "sha512-zgx/UQMozdjOa3bOxjgeCFdtpE3c9rRX6xHwa+2QXvy8z8Akifu2AtubHyv/zzC2znO8dl8fFWL4K+Ba9kS8HQ=="],
|
||||||
|
|
||||||
|
"@astrojs/compiler-binding": ["@astrojs/compiler-binding@0.5.1", "", { "optionalDependencies": { "@astrojs/compiler-binding-android-arm64": "0.5.1", "@astrojs/compiler-binding-darwin-arm64": "0.5.1", "@astrojs/compiler-binding-darwin-x64": "0.5.1", "@astrojs/compiler-binding-linux-arm64-gnu": "0.5.1", "@astrojs/compiler-binding-linux-arm64-musl": "0.5.1", "@astrojs/compiler-binding-linux-x64-gnu": "0.5.1", "@astrojs/compiler-binding-linux-x64-musl": "0.5.1", "@astrojs/compiler-binding-wasm32-wasi": "0.5.1", "@astrojs/compiler-binding-win32-arm64-msvc": "0.5.1", "@astrojs/compiler-binding-win32-x64-msvc": "0.5.1" } }, "sha512-oczyolLUl9mNrtuBuNjuvn5thpHrFLXAHAWGTRg0JDHQ8cGVsVeFekOu2uSoQGgainJIi+A3/9m0DyYwz3Nlnw=="],
|
||||||
|
|
||||||
|
"@astrojs/compiler-binding-android-arm64": ["@astrojs/compiler-binding-android-arm64@0.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-nMb2nMIJtcWLNH+fV75Enmi+jp4tIOYOfsv4XtpgdOWkqc9P4uSSdK5u1xb340AzrXo5V18SxLl/nc3vcqtamQ=="],
|
||||||
|
|
||||||
|
"@astrojs/compiler-binding-darwin-arm64": ["@astrojs/compiler-binding-darwin-arm64@0.5.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5k4+nZ/WRt+dcVow4z7rBuI7iv9A58h/uQ91YJe1PrTivbcCkq3u2Ynx/AZJwII4CsZawBCeGpojkFjutZBhBQ=="],
|
||||||
|
|
||||||
|
"@astrojs/compiler-binding-darwin-x64": ["@astrojs/compiler-binding-darwin-x64@0.5.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-0Buw+YST9Ff2EqQyhRMQujuFq/dijLw3zXWhsvI0dJ2pGrT38iVoxTEca2eBX/PfhlwkVWSRJEKO5gMqKHyeHg=="],
|
||||||
|
|
||||||
|
"@astrojs/compiler-binding-linux-arm64-gnu": ["@astrojs/compiler-binding-linux-arm64-gnu@0.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-x5LRpyCoMKuu3INr18cn42YhwSOaRpp9b5Ny3eJJ/sWVeejC16Zeshu1JlvlSmEZACLeWxU5JM0RX6T6nI+YyA=="],
|
||||||
|
|
||||||
|
"@astrojs/compiler-binding-linux-arm64-musl": ["@astrojs/compiler-binding-linux-arm64-musl@0.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mL7tQEuDk3rGfaKmrO63nw6WBy1TPVioQGmVyjcAr+C+a/k3SVMYBytNSMqH9mZPT/Swj30JaZ2N842Z1NnOEQ=="],
|
||||||
|
|
||||||
|
"@astrojs/compiler-binding-linux-x64-gnu": ["@astrojs/compiler-binding-linux-x64-gnu@0.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-uLGc9jtxwiDe4cQyhNPQim4ECiB/YzbWlXssfLAv8r374kArfTwBHjwR2+j+XVyF71yCx5mF9YNRclRqdNBgeg=="],
|
||||||
|
|
||||||
|
"@astrojs/compiler-binding-linux-x64-musl": ["@astrojs/compiler-binding-linux-x64-musl@0.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-G0cywinaQUc8v1MrytvYKUX7svTDMmYtOCZfxjwKPbJi59zIqM+Ll0rWgO0GQWLZH3XoPYo19ZwlP3Xt9ep9YA=="],
|
||||||
|
|
||||||
|
"@astrojs/compiler-binding-wasm32-wasi": ["@astrojs/compiler-binding-wasm32-wasi@0.5.1", "", { "dependencies": { "@emnapi/core": "1.11.3", "@emnapi/runtime": "1.11.3", "@napi-rs/wasm-runtime": "^1.2.4" }, "cpu": "none" }, "sha512-yMhLL8asmLydoAdnEZxAw6eLfNDs3YJl6jqn0pjPt6zrb2U5qIDLnii4/twThpIFODKkQ6YDpBRTOz/vSfZCxw=="],
|
||||||
|
|
||||||
|
"@astrojs/compiler-binding-win32-arm64-msvc": ["@astrojs/compiler-binding-win32-arm64-msvc@0.5.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aClIG6poFt3Wf/bZykrEAX2nAnKvBHW6RXiKUa69QjBtWH0/FOlOAjnFcxRNlhej2AMWNYFx0o+LGjwMcH8P7Q=="],
|
||||||
|
|
||||||
|
"@astrojs/compiler-binding-win32-x64-msvc": ["@astrojs/compiler-binding-win32-x64-msvc@0.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-/E6uBCagY70YpWqssBC+/FuniNSloeQPRgj7l3LDWiqjxIyTLnIgX1sTt18FU7jrigetpSnC9cHAM6b6IxSgPw=="],
|
||||||
|
|
||||||
|
"@astrojs/compiler-rs": ["@astrojs/compiler-rs@0.5.1", "", { "dependencies": { "@astrojs/compiler-binding": "0.5.1" } }, "sha512-1k1acnX7DepIiea9U9/Y+d1hdAFfylu1ZQQkkYgRSwBOBAsXInolyZuh/2tax+83vu/A+hOYP6P3+265RvMTvw=="],
|
||||||
|
|
||||||
|
"@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.12.0", "", { "dependencies": { "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", "picomatch": "^4.0.4", "retext-smartypants": "^6.2.0", "shiki": "^4.0.2", "smol-toml": "^1.8.0", "unified": "^11.0.5", "yaml": "^2.9.1" } }, "sha512-pKPuwCDtO0L7zgmt1JGd9X4Aes1+0SX3IEBSqBNPHsScYdPMlfmed3Y7DTbXyF7E+8ylnb563o/0OzGQPOFehw=="],
|
||||||
|
|
||||||
|
"@astrojs/language-server": ["@astrojs/language-server@2.17.2", "", { "dependencies": { "@astrojs/astro2tsx": "^0.1.2", "@astrojs/yaml2ts": "^0.2.4", "@volar/kit": "~2.4.28", "@volar/language-core": "~2.4.28", "@volar/language-server": "~2.4.28", "@volar/language-service": "~2.4.28", "muggle-string": "^0.4.1", "tinyglobby": "^0.2.17", "volar-service-css": "0.0.71", "volar-service-emmet": "0.0.71", "volar-service-html": "0.0.71", "volar-service-prettier": "0.0.71", "volar-service-typescript": "0.0.71", "volar-service-typescript-twoslash-queries": "0.0.71", "volar-service-yaml": "0.0.71", "vscode-html-languageservice": "^5.6.2", "vscode-uri": "^3.2.0" }, "peerDependencies": { "prettier": "^3.0.0", "prettier-plugin-astro": ">=0.11.0" }, "optionalPeers": ["prettier", "prettier-plugin-astro"], "bin": { "astro-ls": "./bin/nodeServer.js" } }, "sha512-WzqCBYo0RtKrkPLPMnTz2+UOQPosaFhoG/PvoSyPIgBU0BKeS6qeaYv7Rscq7P1LxJdoN0VosQF2h2TZTsIGHg=="],
|
||||||
|
|
||||||
|
"@astrojs/markdown-satteri": ["@astrojs/markdown-satteri@0.4.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.12.0", "@astrojs/prism": "4.0.2", "github-slugger": "^2.0.0", "satteri": "^0.10.3" } }, "sha512-PFbEHPnwUlLvpXd7RXrrKM+j12akF4bG+DSuNuoN+3eiwjOwp4HaPi+QQdbkBV7QEYXmzyKca50OJsmGxhNIUA=="],
|
||||||
|
|
||||||
|
"@astrojs/prism": ["@astrojs/prism@4.0.2", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA=="],
|
||||||
|
|
||||||
|
"@astrojs/sitemap": ["@astrojs/sitemap@3.7.4", "", { "dependencies": { "sitemap": "^9.0.0", "zod": "^4.3.6" } }, "sha512-LbKNC24bdUWcQf/pThB6qLlSqHojxGjZDURIzFocY8rlWnAn2t74nnhnK6S5x0NHriHoAduLEpVjRykmeGiVvA=="],
|
||||||
|
|
||||||
|
"@astrojs/telemetry": ["@astrojs/telemetry@3.3.3", "", { "dependencies": { "ci-info": "^4.4.0", "dset": "^3.1.4", "is-docker": "^4.0.0", "package-manager-detector": "^1.6.0" } }, "sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ=="],
|
||||||
|
|
||||||
|
"@astrojs/yaml2ts": ["@astrojs/yaml2ts@0.2.4", "", { "dependencies": { "yaml": "^2.8.3" } }, "sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A=="],
|
||||||
|
|
||||||
|
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||||
|
|
||||||
|
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||||
|
|
||||||
|
"@babel/parser": ["@babel/parser@7.29.9", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA=="],
|
||||||
|
|
||||||
|
"@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="],
|
||||||
|
|
||||||
|
"@bruits/satteri-darwin-arm64": ["@bruits/satteri-darwin-arm64@0.10.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-27KTVl4TJkVahMy/ohyA7qd4938G5UNneFUz/PsScYfpIhj0IVAS23mpcJXdPF44sa6nva198lmV/cKIb2YPyA=="],
|
||||||
|
|
||||||
|
"@bruits/satteri-darwin-x64": ["@bruits/satteri-darwin-x64@0.10.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-IjnLe3nKspq6qaeqGgjT7MT8VrTV74yWRlaag7ZdNsI8TDAYZ0iPxMCo+9KQZHUk5EyVB+reBI/PFWL5KuFw9Q=="],
|
||||||
|
|
||||||
|
"@bruits/satteri-linux-arm64-gnu": ["@bruits/satteri-linux-arm64-gnu@0.10.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-glkYXZCJywjP13v67eAyAMSJdF+ncvEbYvgi/wOtffL9tQ27lr/zsyzUfgs+ovjJ9d8JNQKiXeiArJcX8PJL9w=="],
|
||||||
|
|
||||||
|
"@bruits/satteri-linux-arm64-musl": ["@bruits/satteri-linux-arm64-musl@0.10.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-yWdgG1g17Nh2QyGVlFUxGRa3FEFwiMcpZEyMNWkbM3deC94cmVc+/i9OuyFpdKuWo3GkgoCtYVOoxk1uCnCZIA=="],
|
||||||
|
|
||||||
|
"@bruits/satteri-linux-x64-gnu": ["@bruits/satteri-linux-x64-gnu@0.10.5", "", { "os": "linux", "cpu": "x64" }, "sha512-FVaLoPT1fBgGl0J+AYebyyXJYBachGl8Oyyrf1lye4RTqCB4S0Gwkj1uM9RJyThUOvx5VUmAT1CnNh1SFHA+kw=="],
|
||||||
|
|
||||||
|
"@bruits/satteri-linux-x64-musl": ["@bruits/satteri-linux-x64-musl@0.10.5", "", { "os": "linux", "cpu": "x64" }, "sha512-EHpVAx2bqW3GINHTKkljtxVfQmVDGWIuwOYOP5YghTj+0PkBa2o8oKPRtQ9Kbsr1Fye8jtUcDjhwj2jMNugZKg=="],
|
||||||
|
|
||||||
|
"@bruits/satteri-wasm32-wasi": ["@bruits/satteri-wasm32-wasi@0.10.5", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.2.3" }, "cpu": "none" }, "sha512-ypz8c/Zmipxp4IoeDa228Gstv6TLzVmNs3yC6wKCoNSOjx1iwpgzu87Y3hTkXFdwChVGU85qeUDuOIarGUZQLw=="],
|
||||||
|
|
||||||
|
"@bruits/satteri-win32-arm64-msvc": ["@bruits/satteri-win32-arm64-msvc@0.10.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-siTV88nb0LRqNpkL2gXboqCwVdq95sLtzMHS1/3eONV2gLbB3NAK46wmSMvCO/yquBvI2lvaFIfd8P12ecsxBw=="],
|
||||||
|
|
||||||
|
"@bruits/satteri-win32-x64-msvc": ["@bruits/satteri-win32-x64-msvc@0.10.5", "", { "os": "win32", "cpu": "x64" }, "sha512-C3IfPvfvMXmlzBxaMPKFS1XiuV9pu2mC7YqkPk7PSvTgPZ8gbdASIpHpztDLvTTQjqZ0z1Ol8tK5X+V6XXC0wQ=="],
|
||||||
|
|
||||||
|
"@capsizecss/unpack": ["@capsizecss/unpack@4.0.1", "", { "dependencies": { "fontkitten": "^1.0.3" } }, "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ=="],
|
||||||
|
|
||||||
|
"@clack/core": ["@clack/core@1.5.1", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ=="],
|
||||||
|
|
||||||
|
"@clack/prompts": ["@clack/prompts@1.8.1", "", { "dependencies": { "@clack/core": "1.5.1", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ=="],
|
||||||
|
|
||||||
|
"@emmetio/abbreviation": ["@emmetio/abbreviation@2.3.3", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA=="],
|
||||||
|
|
||||||
|
"@emmetio/css-abbreviation": ["@emmetio/css-abbreviation@2.1.8", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw=="],
|
||||||
|
|
||||||
|
"@emmetio/css-parser": ["@emmetio/css-parser@0.4.1", "", { "dependencies": { "@emmetio/stream-reader": "^2.2.0", "@emmetio/stream-reader-utils": "^0.1.0" } }, "sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ=="],
|
||||||
|
|
||||||
|
"@emmetio/html-matcher": ["@emmetio/html-matcher@1.3.0", "", { "dependencies": { "@emmetio/scanner": "^1.0.0" } }, "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ=="],
|
||||||
|
|
||||||
|
"@emmetio/scanner": ["@emmetio/scanner@1.0.4", "", {}, "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA=="],
|
||||||
|
|
||||||
|
"@emmetio/stream-reader": ["@emmetio/stream-reader@2.2.0", "", {}, "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw=="],
|
||||||
|
|
||||||
|
"@emmetio/stream-reader-utils": ["@emmetio/stream-reader-utils@0.1.0", "", {}, "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A=="],
|
||||||
|
|
||||||
|
"@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" } }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="],
|
||||||
|
|
||||||
|
"@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
|
||||||
|
|
||||||
|
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="],
|
||||||
|
|
||||||
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="],
|
||||||
|
|
||||||
|
"@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="],
|
||||||
|
|
||||||
|
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="],
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="],
|
||||||
|
|
||||||
|
"@fontsource-variable/inter": ["@fontsource-variable/inter@5.3.0", "", {}, "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA=="],
|
||||||
|
|
||||||
|
"@fontsource/jetbrains-mono": ["@fontsource/jetbrains-mono@5.3.0", "", {}, "sha512-fqDfB5I9f1p1TV486aUgB9t8zP84P0O1FtQR5Ol9vjwPy+S+EIGlVYm1cvj2W5shcZMTg2nZFdVMoH5wFu8a1A=="],
|
||||||
|
|
||||||
|
"@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
|
||||||
|
|
||||||
|
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg=="],
|
||||||
|
|
||||||
|
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.4" }, "os": "darwin", "cpu": "x64" }, "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw=="],
|
||||||
|
|
||||||
|
"@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.5", "", { "dependencies": { "@img/sharp-wasm32": "0.35.5" }, "os": "freebsd" }, "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw=="],
|
||||||
|
|
||||||
|
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw=="],
|
||||||
|
|
||||||
|
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ=="],
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.4", "", { "os": "linux", "cpu": "arm" }, "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw=="],
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A=="],
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ=="],
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.4", "", { "os": "linux", "cpu": "none" }, "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q=="],
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ=="],
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw=="],
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA=="],
|
||||||
|
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg=="],
|
||||||
|
|
||||||
|
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.4" }, "os": "linux", "cpu": "arm" }, "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ=="],
|
||||||
|
|
||||||
|
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.4" }, "os": "linux", "cpu": "arm64" }, "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ=="],
|
||||||
|
|
||||||
|
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA=="],
|
||||||
|
|
||||||
|
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.4" }, "os": "linux", "cpu": "none" }, "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ=="],
|
||||||
|
|
||||||
|
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.4" }, "os": "linux", "cpu": "s390x" }, "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA=="],
|
||||||
|
|
||||||
|
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.4" }, "os": "linux", "cpu": "x64" }, "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog=="],
|
||||||
|
|
||||||
|
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" }, "os": "linux", "cpu": "arm64" }, "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg=="],
|
||||||
|
|
||||||
|
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.4" }, "os": "linux", "cpu": "x64" }, "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A=="],
|
||||||
|
|
||||||
|
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.5", "", { "dependencies": { "@emnapi/runtime": "^1.11.3" } }, "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ=="],
|
||||||
|
|
||||||
|
"@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.5", "", { "dependencies": { "@img/sharp-wasm32": "0.35.5" }, "cpu": "none" }, "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA=="],
|
||||||
|
|
||||||
|
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q=="],
|
||||||
|
|
||||||
|
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A=="],
|
||||||
|
|
||||||
|
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.5", "", { "os": "win32", "cpu": "x64" }, "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg=="],
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="],
|
||||||
|
|
||||||
|
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g=="],
|
||||||
|
|
||||||
|
"@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
|
||||||
|
|
||||||
|
"@oxc-project/types": ["@oxc-project/types@0.153.0", "", {}, "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.13", "", { "os": "android", "cpu": "arm" }, "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.13", "", { "os": "android", "cpu": "arm64" }, "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.13", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.13", "", { "os": "linux", "cpu": "arm" }, "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.13", "", { "os": "linux", "cpu": "ppc64" }, "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.13", "", { "os": "linux", "cpu": "s390x" }, "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.13", "", { "os": "linux", "cpu": "x64" }, "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.13", "", { "os": "linux", "cpu": "x64" }, "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.13", "", { "os": "none", "cpu": "arm64" }, "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ=="],
|
||||||
|
|
||||||
|
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.13", "", { "os": "win32", "cpu": "x64" }, "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg=="],
|
||||||
|
|
||||||
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
|
||||||
|
|
||||||
|
"@shikijs/core": ["@shikijs/core@4.5.0", "", { "dependencies": { "@shikijs/primitive": "4.5.0", "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5", "hast-util-to-html": "^9.0.5" } }, "sha512-6CIgwq+bt2JZ3OSjb/nBkkwEf/CfpHcbam+Mg5Pllco7HAGRQ2/EFhZrrJtbyNTgKwknfVS+llsXGygiDGwJWw=="],
|
||||||
|
|
||||||
|
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.5.0", "", { "dependencies": { "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-Ed6UdEj84LyYkwnxM5CvSMWAD+id9ExTYri0/+x41U9zrgNifPSS9lZm0JqBP0VfYI8yr8tY5nbNq9+p9R0kFA=="],
|
||||||
|
|
||||||
|
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.5.0", "", { "dependencies": { "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-dKVmbJecVdB9recl8a/CU4jdpNXyjJQrIcesIkpS70s5HS02ohpnCSpMUbotPnEUzbAcLQ+S4hwZYbkBBiTcJw=="],
|
||||||
|
|
||||||
|
"@shikijs/langs": ["@shikijs/langs@4.5.0", "", { "dependencies": { "@shikijs/types": "4.5.0" } }, "sha512-wwBTqwduXSz0X4v9Qa2EWeFaYJEw1clCncWjoRNQRbVDu3SPRTRWucnouYapKj3b8Q+sk/41Vbc4B5c8nuYzLA=="],
|
||||||
|
|
||||||
|
"@shikijs/primitive": ["@shikijs/primitive@4.5.0", "", { "dependencies": { "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-UM7ShlA8oz0OWxLLO8BmO9Ip6fVqdkt8yF0EH7nOCqHfrx1eEAzF0ZUVK7L+I8NEgeu1PEWSRlxaiPPeCb4iDg=="],
|
||||||
|
|
||||||
|
"@shikijs/themes": ["@shikijs/themes@4.5.0", "", { "dependencies": { "@shikijs/types": "4.5.0" } }, "sha512-zGb87H3UBbpuI4yoh5UguB1XbU+2BDU3kflM7UfZ+Cy2hWo9lBtqjSVK20gmLY6ukYl040hAypGXMgud9vul8Q=="],
|
||||||
|
|
||||||
|
"@shikijs/types": ["@shikijs/types@4.5.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-drtPrtD8N0DENyD2cyt6Ajy+IWsiCLgMxODwLTpvH1UsN6a9jf90tNTPjqwHvA+0Lzu9e0txk8DjfqH1p9UJRw=="],
|
||||||
|
|
||||||
|
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
|
||||||
|
|
||||||
|
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.4", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A=="],
|
||||||
|
|
||||||
|
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
||||||
|
|
||||||
|
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
||||||
|
|
||||||
|
"@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="],
|
||||||
|
|
||||||
|
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
|
||||||
|
|
||||||
|
"@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@24.19.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ=="],
|
||||||
|
|
||||||
|
"@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="],
|
||||||
|
|
||||||
|
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
||||||
|
|
||||||
|
"@ungap/structured-clone": ["@ungap/structured-clone@1.4.0", "", {}, "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ=="],
|
||||||
|
|
||||||
|
"@volar/kit": ["@volar/kit@2.4.28", "", { "dependencies": { "@volar/language-service": "2.4.28", "@volar/typescript": "2.4.28", "typesafe-path": "^0.2.2", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "typescript": "*" } }, "sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg=="],
|
||||||
|
|
||||||
|
"@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="],
|
||||||
|
|
||||||
|
"@volar/language-server": ["@volar/language-server@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "@volar/language-service": "2.4.28", "@volar/typescript": "2.4.28", "path-browserify": "^1.0.1", "request-light": "^0.7.0", "vscode-languageserver": "^9.0.1", "vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" } }, "sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw=="],
|
||||||
|
|
||||||
|
"@volar/language-service": ["@volar/language-service@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" } }, "sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw=="],
|
||||||
|
|
||||||
|
"@volar/source-map": ["@volar/source-map@2.4.28", "", {}, "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ=="],
|
||||||
|
|
||||||
|
"@volar/typescript": ["@volar/typescript@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="],
|
||||||
|
|
||||||
|
"@vscode/emmet-helper": ["@vscode/emmet-helper@2.11.0", "", { "dependencies": { "emmet": "^2.4.3", "jsonc-parser": "^2.3.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.15.1", "vscode-uri": "^3.0.8" } }, "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw=="],
|
||||||
|
|
||||||
|
"@vscode/l10n": ["@vscode/l10n@0.0.18", "", {}, "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ=="],
|
||||||
|
|
||||||
|
"ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
|
||||||
|
|
||||||
|
"ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="],
|
||||||
|
|
||||||
|
"ajv-i18n": ["ajv-i18n@4.2.0", "", { "peerDependencies": { "ajv": "^8.0.0-beta.0" } }, "sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg=="],
|
||||||
|
|
||||||
|
"am-i-vibing": ["am-i-vibing@0.5.0", "", { "dependencies": { "process-ancestry": "^0.1.0" }, "bin": { "am-i-vibing": "dist/cli.mjs" } }, "sha512-KLcsDhqf1XRrJmJr+9I/C0EX2K+sGrAGZFgkmd84c8TJO9zJBu+/kFInjazu1KfJWFhWDKdRWkpBzEs7s35DVA=="],
|
||||||
|
|
||||||
|
"ansi-regex": ["ansi-regex@6.4.0", "", {}, "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw=="],
|
||||||
|
|
||||||
|
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||||
|
|
||||||
|
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||||
|
|
||||||
|
"arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
|
||||||
|
|
||||||
|
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
|
||||||
|
|
||||||
|
"astro": ["astro@7.3.8", "", { "dependencies": { "@astrojs/compiler-rs": "^0.5.1", "@astrojs/internal-helpers": "0.12.0", "@astrojs/markdown-satteri": "0.4.3", "@astrojs/telemetry": "3.3.3", "@capsizecss/unpack": "^4.0.1", "@clack/prompts": "^1.8.1", "@oslojs/encoding": "^1.1.0", "am-i-vibing": "^0.5.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^2.0.1", "devalue": "^5.9.4", "diff": "^9.0.0", "dset": "^3.1.4", "es-module-lexer": "^3.0.2", "find-proc": "0.2.0", "flattie": "^1.1.1", "fontace": "~0.4.1", "get-tsconfig": "5.0.0-beta.6", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "jsonc-parser": "^3.3.1", "magic-string": "^1.4.2", "magicast": "^0.5.5", "mrmime": "^2.0.1", "neotraverse": "^1.0.1", "obug": "^3.0.0", "p-limit": "^7.3.3", "p-queue": "^9.3.3", "package-manager-detector": "^1.8.0", "piccolore": "^0.1.3", "picomatch": "^4.0.7", "rolldown": "^1.2.11", "shiki": "^4.4.3", "smol-toml": "^1.9.0", "svgo": "^4.1.0", "tinyclip": "^1.0.3", "tinyexec": "^1.3.1", "tinyglobby": "^0.2.17", "ultrahtml": "^1.7.0", "unifont": "~0.7.5", "unstorage": "^1.17.5", "verkit": "^0.5.0", "vite": "^8.3.1", "vitefu": "^1.1.3", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.6.5" }, "optionalDependencies": { "sharp": "^0.35.4" }, "peerDependencies": { "@astrojs/markdown-remark": "^7.3.0" }, "optionalPeers": ["@astrojs/markdown-remark"], "bin": { "astro": "./bin/astro.mjs" } }, "sha512-LHzDHSaV+YzZiR3EFndDYTLLeNeuMtP1qX1FLcCNUUHOCqTTIWPczy2UV3zpA5VqvJe5cWZolqM9Z8JFqR2i9w=="],
|
||||||
|
|
||||||
|
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||||
|
|
||||||
|
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||||
|
|
||||||
|
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||||
|
|
||||||
|
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||||
|
|
||||||
|
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
|
||||||
|
|
||||||
|
"character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
|
||||||
|
|
||||||
|
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||||
|
|
||||||
|
"ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="],
|
||||||
|
|
||||||
|
"cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
|
||||||
|
|
||||||
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
|
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
|
||||||
|
|
||||||
|
"commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
|
||||||
|
|
||||||
|
"common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="],
|
||||||
|
|
||||||
|
"cookie": ["cookie@2.0.1", "", {}, "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w=="],
|
||||||
|
|
||||||
|
"cookie-es": ["cookie-es@1.2.3", "", {}, "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw=="],
|
||||||
|
|
||||||
|
"crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="],
|
||||||
|
|
||||||
|
"css-select": ["css-select@6.0.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^7.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "nth-check": "^2.1.1" } }, "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw=="],
|
||||||
|
|
||||||
|
"css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
|
||||||
|
|
||||||
|
"css-what": ["css-what@7.0.0", "", {}, "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ=="],
|
||||||
|
|
||||||
|
"csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="],
|
||||||
|
|
||||||
|
"defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="],
|
||||||
|
|
||||||
|
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||||
|
|
||||||
|
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
||||||
|
|
||||||
|
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||||
|
|
||||||
|
"devalue": ["devalue@5.9.4", "", {}, "sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg=="],
|
||||||
|
|
||||||
|
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
||||||
|
|
||||||
|
"diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="],
|
||||||
|
|
||||||
|
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||||
|
|
||||||
|
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||||
|
|
||||||
|
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
||||||
|
|
||||||
|
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||||
|
|
||||||
|
"dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="],
|
||||||
|
|
||||||
|
"emmet": ["emmet@2.4.11", "", { "dependencies": { "@emmetio/abbreviation": "^2.3.3", "@emmetio/css-abbreviation": "^2.1.8" } }, "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ=="],
|
||||||
|
|
||||||
|
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||||
|
|
||||||
|
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||||
|
|
||||||
|
"es-module-lexer": ["es-module-lexer@3.0.3", "", {}, "sha512-yWGe1O/j+almnV4hOYrmZo8u58JdnmXEMneRQAYkWtqSiT86yoMIEKF4rpqEDj52tbYl3xceBD3DQIBwcsC9Pw=="],
|
||||||
|
|
||||||
|
"esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="],
|
||||||
|
|
||||||
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
|
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
|
||||||
|
|
||||||
|
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||||
|
|
||||||
|
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||||
|
|
||||||
|
"fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
|
||||||
|
|
||||||
|
"fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="],
|
||||||
|
|
||||||
|
"fast-uri": ["fast-uri@3.1.8", "", {}, "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg=="],
|
||||||
|
|
||||||
|
"fast-wrap-ansi": ["fast-wrap-ansi@0.2.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="],
|
||||||
|
|
||||||
|
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
|
"find-proc": ["find-proc@0.2.0", "", {}, "sha512-a6h2nTrgB3g5Wrn4au9Ux4kMk7IJtyF6oA8uGn+mk3weLZssUtc1cp6meHIzzuW2o1srrBAxzkmXLbqjRkgB6w=="],
|
||||||
|
|
||||||
|
"flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="],
|
||||||
|
|
||||||
|
"fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="],
|
||||||
|
|
||||||
|
"fontkitten": ["fontkitten@1.0.3", "", { "dependencies": { "tiny-inflate": "^1.0.3" } }, "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw=="],
|
||||||
|
|
||||||
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
|
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||||
|
|
||||||
|
"get-east-asian-width": ["get-east-asian-width@1.7.0", "", {}, "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q=="],
|
||||||
|
|
||||||
|
"get-tsconfig": ["get-tsconfig@5.0.0-beta.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-X6fBC0pmImC70gvX2zm56go9hx0MyoGVdG0tUCkg/D+Xnh5TJsOZ7iDbOdI3PvmtrDxnu1YdDufpK2QJX1Meqw=="],
|
||||||
|
|
||||||
|
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
|
||||||
|
|
||||||
|
"h3": ["h3@1.15.11", "", { "dependencies": { "cookie-es": "^1.2.3", "crossws": "^0.3.5", "defu": "^6.1.6", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="],
|
||||||
|
|
||||||
|
"hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
|
||||||
|
|
||||||
|
"hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
|
||||||
|
|
||||||
|
"html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="],
|
||||||
|
|
||||||
|
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
|
||||||
|
|
||||||
|
"http-cache-semantics": ["http-cache-semantics@4.3.0", "", {}, "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA=="],
|
||||||
|
|
||||||
|
"iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
|
||||||
|
|
||||||
|
"is-docker": ["is-docker@4.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA=="],
|
||||||
|
|
||||||
|
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||||
|
|
||||||
|
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||||
|
|
||||||
|
"jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
|
||||||
|
|
||||||
|
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||||
|
|
||||||
|
"lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="],
|
||||||
|
|
||||||
|
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="],
|
||||||
|
|
||||||
|
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="],
|
||||||
|
|
||||||
|
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="],
|
||||||
|
|
||||||
|
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="],
|
||||||
|
|
||||||
|
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="],
|
||||||
|
|
||||||
|
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="],
|
||||||
|
|
||||||
|
"lru-cache": ["lru-cache@11.5.3", "", {}, "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg=="],
|
||||||
|
|
||||||
|
"magic-string": ["magic-string@1.4.3", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.6.0" } }, "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ=="],
|
||||||
|
|
||||||
|
"magicast": ["magicast@0.5.5", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "source-map-js": "^1.2.1" } }, "sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA=="],
|
||||||
|
|
||||||
|
"mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="],
|
||||||
|
|
||||||
|
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
|
||||||
|
|
||||||
|
"micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
|
||||||
|
|
||||||
|
"micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
|
||||||
|
|
||||||
|
"micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
|
||||||
|
|
||||||
|
"micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
|
||||||
|
|
||||||
|
"micromark-util-types": ["micromark-util-types@2.0.3", "", {}, "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg=="],
|
||||||
|
|
||||||
|
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
||||||
|
|
||||||
|
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
|
||||||
|
|
||||||
|
"nanoid": ["nanoid@3.3.20", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ=="],
|
||||||
|
|
||||||
|
"neotraverse": ["neotraverse@1.0.1", "", {}, "sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w=="],
|
||||||
|
|
||||||
|
"nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="],
|
||||||
|
|
||||||
|
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||||
|
|
||||||
|
"node-mock-http": ["node-mock-http@1.0.5", "", {}, "sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw=="],
|
||||||
|
|
||||||
|
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||||
|
|
||||||
|
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||||
|
|
||||||
|
"obug": ["obug@3.0.0", "", {}, "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw=="],
|
||||||
|
|
||||||
|
"ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
||||||
|
|
||||||
|
"ohash": ["ohash@2.0.12", "", {}, "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw=="],
|
||||||
|
|
||||||
|
"oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="],
|
||||||
|
|
||||||
|
"oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="],
|
||||||
|
|
||||||
|
"p-limit": ["p-limit@7.3.3", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-SKgVvDU5TNIxQ4r30U75BNLC6a+6GOEjngrk/ysM5+Zu3oJuk1SC5ApzIzY/7PjCEjoVPMWJ0zho/4QUiEB7TQ=="],
|
||||||
|
|
||||||
|
"p-queue": ["p-queue@9.3.4", "", { "dependencies": { "eventemitter3": "^5.0.4", "p-timeout": "^7.0.0" } }, "sha512-bpTeG3CuPiOgPcDqn51QUsRYshkiKiaG+hP49tCGI0+nT92h7rqD4LPKZzP8kwab/OniQf+ntA4ta6yCp1eA0Q=="],
|
||||||
|
|
||||||
|
"p-timeout": ["p-timeout@7.0.2", "", {}, "sha512-prbX4Z3YszrFNgH+MW5Zoeq3baXrMtP/MQnFeET90UB/GtGcGDQ5Usg9OCy6ETjTTntOw1SL2z9fMPUppN3Guw=="],
|
||||||
|
|
||||||
|
"package-manager-detector": ["package-manager-detector@1.9.0", "", {}, "sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA=="],
|
||||||
|
|
||||||
|
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
||||||
|
|
||||||
|
"piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="],
|
||||||
|
|
||||||
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
|
"picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="],
|
||||||
|
|
||||||
|
"postcss": ["postcss@8.5.29", "", { "dependencies": { "nanoid": "^3.3.19", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" } }, "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ=="],
|
||||||
|
|
||||||
|
"prettier": ["prettier@3.9.10", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA=="],
|
||||||
|
|
||||||
|
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
|
||||||
|
|
||||||
|
"process-ancestry": ["process-ancestry@0.1.0", "", {}, "sha512-tGqJW/UnclpYASFcM6Xh8D8l/BMtaQ9+CSG0vlJSJTcdMM4lDRv4c6H0Pdcsfted+bVczdYSfk2fdukg2gQkZg=="],
|
||||||
|
|
||||||
|
"property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="],
|
||||||
|
|
||||||
|
"radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
|
||||||
|
|
||||||
|
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||||
|
|
||||||
|
"regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
|
||||||
|
|
||||||
|
"regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="],
|
||||||
|
|
||||||
|
"regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="],
|
||||||
|
|
||||||
|
"request-light": ["request-light@0.7.0", "", {}, "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q=="],
|
||||||
|
|
||||||
|
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||||
|
|
||||||
|
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||||
|
|
||||||
|
"retext-smartypants": ["retext-smartypants@6.2.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ=="],
|
||||||
|
|
||||||
|
"rolldown": ["rolldown@1.2.13", "", { "dependencies": { "@oxc-project/types": "=0.153.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.13", "@rolldown/binding-android-arm64": "1.2.13", "@rolldown/binding-darwin-arm64": "1.2.13", "@rolldown/binding-darwin-x64": "1.2.13", "@rolldown/binding-freebsd-x64": "1.2.13", "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", "@rolldown/binding-linux-arm64-gnu": "1.2.13", "@rolldown/binding-linux-arm64-musl": "1.2.13", "@rolldown/binding-linux-ppc64-gnu": "1.2.13", "@rolldown/binding-linux-s390x-gnu": "1.2.13", "@rolldown/binding-linux-x64-gnu": "1.2.13", "@rolldown/binding-linux-x64-musl": "1.2.13", "@rolldown/binding-openharmony-arm64": "1.2.13", "@rolldown/binding-win32-arm64-msvc": "1.2.13", "@rolldown/binding-win32-x64-msvc": "1.2.13" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ=="],
|
||||||
|
|
||||||
|
"satteri": ["satteri@0.10.5", "", { "dependencies": { "@types/estree-jsx": "^1.0.5", "@types/hast": "^3.0.5", "@types/mdast": "^4.0.4", "@types/unist": "^3.0.3" }, "optionalDependencies": { "@bruits/satteri-darwin-arm64": "0.10.5", "@bruits/satteri-darwin-x64": "0.10.5", "@bruits/satteri-linux-arm64-gnu": "0.10.5", "@bruits/satteri-linux-arm64-musl": "0.10.5", "@bruits/satteri-linux-x64-gnu": "0.10.5", "@bruits/satteri-linux-x64-musl": "0.10.5", "@bruits/satteri-wasm32-wasi": "0.10.5", "@bruits/satteri-win32-arm64-msvc": "0.10.5", "@bruits/satteri-win32-x64-msvc": "0.10.5" } }, "sha512-Ao1LKpAEa9Wdg0otgbVKViZHEq9ebdXe4DMrp3s9vQAU0HNIuHnFEuMuOcm0ZIXyV0Yzxj91NvhLpvXZJO/5ZQ=="],
|
||||||
|
|
||||||
|
"sax": ["sax@1.6.1", "", {}, "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q=="],
|
||||||
|
|
||||||
|
"semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
|
"sharp": ["sharp@0.35.5", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.5", "@img/sharp-darwin-x64": "0.35.5", "@img/sharp-freebsd-wasm32": "0.35.5", "@img/sharp-libvips-darwin-arm64": "1.3.4", "@img/sharp-libvips-darwin-x64": "1.3.4", "@img/sharp-libvips-linux-arm": "1.3.4", "@img/sharp-libvips-linux-arm64": "1.3.4", "@img/sharp-libvips-linux-ppc64": "1.3.4", "@img/sharp-libvips-linux-riscv64": "1.3.4", "@img/sharp-libvips-linux-s390x": "1.3.4", "@img/sharp-libvips-linux-x64": "1.3.4", "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", "@img/sharp-libvips-linuxmusl-x64": "1.3.4", "@img/sharp-linux-arm": "0.35.5", "@img/sharp-linux-arm64": "0.35.5", "@img/sharp-linux-ppc64": "0.35.5", "@img/sharp-linux-riscv64": "0.35.5", "@img/sharp-linux-s390x": "0.35.5", "@img/sharp-linux-x64": "0.35.5", "@img/sharp-linuxmusl-arm64": "0.35.5", "@img/sharp-linuxmusl-x64": "0.35.5", "@img/sharp-webcontainers-wasm32": "0.35.5", "@img/sharp-win32-arm64": "0.35.5", "@img/sharp-win32-ia32": "0.35.5", "@img/sharp-win32-x64": "0.35.5" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g=="],
|
||||||
|
|
||||||
|
"shiki": ["shiki@4.5.0", "", { "dependencies": { "@shikijs/core": "4.5.0", "@shikijs/engine-javascript": "4.5.0", "@shikijs/engine-oniguruma": "4.5.0", "@shikijs/langs": "4.5.0", "@shikijs/themes": "4.5.0", "@shikijs/types": "4.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-OcRW72ngQvc+MSVWBRIovC/BTW589iUE/Vxav7VeM4RcY9MUjqB+IR6ILG1k9nuW6LiG/nct9uma4/MBtnZbmQ=="],
|
||||||
|
|
||||||
|
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
|
||||||
|
|
||||||
|
"sitemap": ["sitemap@9.0.1", "", { "dependencies": { "@types/node": "^24.9.2", "@types/sax": "^1.2.1", "arg": "^5.0.0", "sax": "^1.4.1" }, "bin": { "sitemap": "dist/esm/cli.js" } }, "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ=="],
|
||||||
|
|
||||||
|
"smol-toml": ["smol-toml@1.9.1", "", {}, "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ=="],
|
||||||
|
|
||||||
|
"source-map-js": ["source-map-js@1.2.2", "", {}, "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw=="],
|
||||||
|
|
||||||
|
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
||||||
|
|
||||||
|
"string-width": ["string-width@8.3.0", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ=="],
|
||||||
|
|
||||||
|
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
||||||
|
|
||||||
|
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||||
|
|
||||||
|
"svgo": ["svgo@4.1.0", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^6.0.0", "css-tree": "^3.0.1", "css-what": "^7.0.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "1.6.1" }, "bin": { "svgo": "bin/svgo.js" } }, "sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q=="],
|
||||||
|
|
||||||
|
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
|
||||||
|
|
||||||
|
"tinyclip": ["tinyclip@1.0.3", "", {}, "sha512-nTv2raKFMzGG6JJp0lpNDsRPdfFE6UHlu2rVEjXo8vkx1ZS3LW2CVXqIaLu8xZlu+6tbFs9V58Qc/Jmk/Ii7wA=="],
|
||||||
|
|
||||||
|
"tinyexec": ["tinyexec@1.3.1", "", {}, "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA=="],
|
||||||
|
|
||||||
|
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
|
||||||
|
|
||||||
|
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
|
||||||
|
|
||||||
|
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
||||||
|
|
||||||
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"typesafe-path": ["typesafe-path@0.2.2", "", {}, "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
||||||
|
|
||||||
|
"typescript-auto-import-cache": ["typescript-auto-import-cache@0.3.6", "", { "dependencies": { "semver": "^7.3.8" } }, "sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ=="],
|
||||||
|
|
||||||
|
"ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="],
|
||||||
|
|
||||||
|
"ultrahtml": ["ultrahtml@1.7.0", "", {}, "sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g=="],
|
||||||
|
|
||||||
|
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
|
||||||
|
|
||||||
|
"undici": ["undici@8.11.2", "", {}, "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||||
|
|
||||||
|
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||||
|
|
||||||
|
"unifont": ["unifont@0.7.5", "", { "dependencies": { "css-tree": "^3.1.0", "ohash": "^2.0.11", "undici": "^8.0.0" } }, "sha512-ULe/Cs+ZIsq+dcFofNkhqielCrUJnb5mr+Yc4EBM2VlL+6OZR6+cjtI2mT1bJvRBrVncqHAbLURxmPLcCXzWMg=="],
|
||||||
|
|
||||||
|
"unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
|
||||||
|
|
||||||
|
"unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
|
||||||
|
|
||||||
|
"unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
|
||||||
|
|
||||||
|
"unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
|
||||||
|
|
||||||
|
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
|
||||||
|
|
||||||
|
"unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
||||||
|
|
||||||
|
"verkit": ["verkit@0.5.0", "", {}, "sha512-u4J1hr6Y+C/hit34GwL3eIcUf3wngT8TlYUjK7tk0jxydKLAq3sYvifjdTDA6U3KCKRcQCQDGf/srjxfZF3l7g=="],
|
||||||
|
|
||||||
|
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
|
||||||
|
|
||||||
|
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
|
||||||
|
|
||||||
|
"vite": ["vite@8.3.4", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.29", "rolldown": "~1.2.12", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ=="],
|
||||||
|
|
||||||
|
"vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
|
||||||
|
|
||||||
|
"volar-service-css": ["volar-service-css@0.0.71", "", { "dependencies": { "vscode-css-languageservice": "^6.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-wRRFt9BpjMKCazcgOh67MSjUjiWUCAh99DyYSDIOTuxaRjEtDC7PpB0k1Y1wbJIW/pVtMUSVbpPo3UGSm0Byxw=="],
|
||||||
|
|
||||||
|
"volar-service-emmet": ["volar-service-emmet@0.0.71", "", { "dependencies": { "@emmetio/css-parser": "^0.4.1", "@emmetio/html-matcher": "^1.3.0", "@vscode/emmet-helper": "^2.9.3", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-zqjzt6bN95e3CUstBm0PBFAJnrfz0ZAARka87fart46/gNCLLuP3Vujy8V/J8HEziTFLnfkgIASLFYPUhonJcA=="],
|
||||||
|
|
||||||
|
"volar-service-html": ["volar-service-html@0.0.71", "", { "dependencies": { "vscode-html-languageservice": "^5.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-e8tHPhgQ7ooLfudAEIku+kgd9pWkq3SSz8RbnQDI1+Eb8wbenkLGHqoirLqz5ORLV6wIMr2Iv08RWBG5eOcgpw=="],
|
||||||
|
|
||||||
|
"volar-service-prettier": ["volar-service-prettier@0.0.71", "", { "dependencies": { "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0", "prettier": "^2.2 || ^3.0" }, "optionalPeers": ["@volar/language-service", "prettier"] }, "sha512-Rz7JVH3qD108UCdmIEiZvOBNljMt2nLFdbN8AXcDfn7xD9F5I2aCIsDVqBbXw21PsnxG0b7MfwtNF+zPS/NKUg=="],
|
||||||
|
|
||||||
|
"volar-service-typescript": ["volar-service-typescript@0.0.71", "", { "dependencies": { "path-browserify": "^1.0.1", "semver": "^7.6.2", "typescript-auto-import-cache": "^0.3.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-nls": "^5.2.0", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-yTtM/BVT6hoyEYnDtaCyAtNhdNeS/mhTTABlBOdw3NNiRBUin3IznFJpgfjer4c6RYopiPjjQjc9VFhxVl1mLw=="],
|
||||||
|
|
||||||
|
"volar-service-typescript-twoslash-queries": ["volar-service-typescript-twoslash-queries@0.0.71", "", { "dependencies": { "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-9K2k72s4n7rV9s4bX0MyjbX9iBribvKZbBJKuEmTCZfeWJXs6Yh7bGpY4eoc7UufAjvpheBqwyZCOIPBvxCv0A=="],
|
||||||
|
|
||||||
|
"volar-service-yaml": ["volar-service-yaml@0.0.71", "", { "dependencies": { "vscode-uri": "^3.0.8", "yaml-language-server": "~1.23.0" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-qYGWGuVpUTnZGu5P/CR4KLK4aIR8RrcVnmfZ2eRcj9q/I8VZCoC5yy9FtEvfNvnDp4MU17yhdJcvpQPIqhJS2Q=="],
|
||||||
|
|
||||||
|
"vscode-css-languageservice": ["vscode-css-languageservice@6.3.10", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "3.17.5", "vscode-uri": "^3.1.0" } }, "sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA=="],
|
||||||
|
|
||||||
|
"vscode-html-languageservice": ["vscode-html-languageservice@5.6.2", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "^3.17.5", "vscode-uri": "^3.1.0" } }, "sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg=="],
|
||||||
|
|
||||||
|
"vscode-json-languageservice": ["vscode-json-languageservice@4.1.8", "", { "dependencies": { "jsonc-parser": "^3.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.2" } }, "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg=="],
|
||||||
|
|
||||||
|
"vscode-jsonrpc": ["vscode-jsonrpc@9.0.3", "", {}, "sha512-qETYf9PCl1lVfwR+oBmMfc4vP2nqLNA35ABxorw3j06+xlKRgDVxbLgKm4Y/xPL0YWdeDjLoI4ImICIT3nB0zQ=="],
|
||||||
|
|
||||||
|
"vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="],
|
||||||
|
|
||||||
|
"vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.18.4", "", { "dependencies": { "vscode-jsonrpc": "9.0.3", "vscode-languageserver-types": "3.18.4" } }, "sha512-CCXbyr99dafRJzJH/0Ryk7ly5T8XPmSR0//OuF2w4m+9JiOin7zcAT5iET8GRE7Z9taFBejb3GbM0a0mbjdbrg=="],
|
||||||
|
|
||||||
|
"vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.15", "", {}, "sha512-Gsfr9S8LU3S6885cS+NaleasJ0n3V5y/SOTqHCifQiXWFw3G9w9/YItDfwHrlCz+dSZWWZ8ilYVMcJe7Sz7Sxw=="],
|
||||||
|
|
||||||
|
"vscode-languageserver-types": ["vscode-languageserver-types@3.18.4", "", {}, "sha512-nQsacoiijvl0fRf2q28uFDrAVwRhdCa3QveiF7/CX0r18mLR5FU/1WskSsCKOjmEbtteKUnY8x8Yzxop+k0zwg=="],
|
||||||
|
|
||||||
|
"vscode-nls": ["vscode-nls@5.2.0", "", {}, "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng=="],
|
||||||
|
|
||||||
|
"vscode-uri": ["vscode-uri@3.2.0", "", {}, "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg=="],
|
||||||
|
|
||||||
|
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||||
|
|
||||||
|
"xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
|
||||||
|
|
||||||
|
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||||
|
|
||||||
|
"yaml": ["yaml@2.9.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw=="],
|
||||||
|
|
||||||
|
"yaml-language-server": ["yaml-language-server@1.23.0", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-i18n": "^4.2.0", "prettier": "^3.8.1", "request-light": "^0.5.7", "vscode-json-languageservice": "4.1.8", "vscode-languageserver": "^9.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", "vscode-uri": "^3.0.2", "yaml": "2.8.3" }, "bin": { "yaml-language-server": "bin/yaml-language-server" } }, "sha512-3qVyCOexLCWw06PQa5kRPwvMWMZ/eZeCRWUvgD6a0OkqL/4iCnxy2WumbWifa937Uo5xhyWJ0uxlU39ljhNh7A=="],
|
||||||
|
|
||||||
|
"yargs": ["yargs@18.2.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^8.2.1", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w=="],
|
||||||
|
|
||||||
|
"yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
|
||||||
|
|
||||||
|
"yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="],
|
||||||
|
|
||||||
|
"zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
|
||||||
|
|
||||||
|
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||||
|
|
||||||
|
"@astrojs/compiler-binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA=="],
|
||||||
|
|
||||||
|
"@bruits/satteri-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="],
|
||||||
|
|
||||||
|
"@bruits/satteri-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="],
|
||||||
|
|
||||||
|
"@bruits/satteri-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA=="],
|
||||||
|
|
||||||
|
"@types/sax/@types/node": ["@types/node@26.6.5", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg=="],
|
||||||
|
|
||||||
|
"@vscode/emmet-helper/jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="],
|
||||||
|
|
||||||
|
"anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||||
|
|
||||||
|
"cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||||
|
|
||||||
|
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
|
||||||
|
|
||||||
|
"unstorage/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||||
|
|
||||||
|
"vscode-css-languageservice/vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="],
|
||||||
|
|
||||||
|
"vscode-languageserver/vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.5", "", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="],
|
||||||
|
|
||||||
|
"wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||||
|
|
||||||
|
"yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="],
|
||||||
|
|
||||||
|
"yaml-language-server/yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="],
|
||||||
|
|
||||||
|
"@bruits/satteri-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
|
||||||
|
|
||||||
|
"@types/sax/@types/node/undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="],
|
||||||
|
|
||||||
|
"csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
|
||||||
|
|
||||||
|
"unstorage/chokidar/readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="],
|
||||||
|
|
||||||
|
"vscode-languageserver/vscode-languageserver-protocol/vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="],
|
||||||
|
|
||||||
|
"vscode-languageserver/vscode-languageserver-protocol/vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "ink-website",
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"build": "astro check && astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"assets": "bun scripts/make-assets.ts",
|
||||||
|
"screenshots": "sh scripts/capture/run.sh"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/sitemap": "^3.7.4",
|
||||||
|
"@fontsource-variable/inter": "^5.3.0",
|
||||||
|
"@fontsource/jetbrains-mono": "^5.3.0",
|
||||||
|
"astro": "^7.3.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@astrojs/check": "^0.9.10",
|
||||||
|
"esbuild": "^0.28.2",
|
||||||
|
"sharp": "^0.35.5",
|
||||||
|
"typescript": "^6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 764 B |
|
After Width: | Height: | Size: 364 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="100 100 824 824">
|
||||||
|
<defs><linearGradient id="n" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f7d3c9"/><stop offset=".6" stop-color="#e4ada5"/><stop offset="1" stop-color="#c97a6d"/></linearGradient></defs>
|
||||||
|
<rect x="100" y="100" width="824" height="824" rx="190" fill="#1a1a1c"/>
|
||||||
|
<path d="M330 340 C330 296 362 266 406 266 H618 C662 266 694 296 694 340 C694 448 646 528 598 600 C560 656 530 724 512 806 C494 724 464 656 426 600 C378 528 330 448 330 340 Z" fill="url(#n)"/>
|
||||||
|
<path d="M512 520 V800" stroke="#2a1a16" stroke-width="22" stroke-linecap="round"/>
|
||||||
|
<circle cx="512" cy="500" r="34" fill="#2a1a16"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 683 B |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 83 KiB |
@@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "Ink",
|
||||||
|
"short_name": "Ink",
|
||||||
|
"description": "A calm, offline Markdown notes app.",
|
||||||
|
"icons": [
|
||||||
|
{ "src": "icon-192.png", "sizes": "192x192", "type": "image/png" },
|
||||||
|
{ "src": "icon-512.png", "sizes": "512x512", "type": "image/png" }
|
||||||
|
],
|
||||||
|
"theme_color": "#0e0e0f",
|
||||||
|
"background_color": "#0e0e0f",
|
||||||
|
"display": "browser"
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
'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');
|
||||||
|
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 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 { seedVault } = require('./seed-vault.cjs');
|
||||||
|
const scenes = require('./scenes.cjs');
|
||||||
|
|
||||||
|
const W = 1600, H = 1000;
|
||||||
|
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(
|
||||||
|
'<html><head><title>JSON Canvas</title><meta property="og:title" content="JSON Canvas"><meta name="description" content="An open file format for infinite canvas data.">' +
|
||||||
|
'</head><body></body></html>', { 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 },
|
||||||
|
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 shot = async (name) => {
|
||||||
|
await sleep(500);
|
||||||
|
const img = await wc.capturePage();
|
||||||
|
const file = path.join(OUT, `${name}-${theme}.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}`);
|
||||||
|
};
|
||||||
|
const ctx = { win, wc, js, key, type, click, shot, sleep, vault, W, H };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sleep(2200);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#!/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)
|
||||||
|
set -e
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
INK_APP="${INK_APP:-$HERE/../../../../App/Ink}"
|
||||||
|
INK_APP="$(cd "$INK_APP" && pwd)"
|
||||||
|
export INK_APP
|
||||||
|
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
|
||||||
|
done
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
'use strict';
|
||||||
|
// Each scene leaves the app looking the way the website screenshot should, then calls shot(name).
|
||||||
|
// Scenes run in order in one session; each one starts by getting back to a known state.
|
||||||
|
|
||||||
|
// Cosmetic: show the default notes location instead of the temp folder this run uses.
|
||||||
|
const tidyPath = (js) => js(`
|
||||||
|
for (const el of document.querySelectorAll('.ws-path, .mono')) {
|
||||||
|
if (/^\\/tmp\\/ink-shots-[^/]+\\/Notes$/.test(el.textContent.trim())) { el.textContent = '~/Notes'; el.title = '~/Notes'; }
|
||||||
|
}`);
|
||||||
|
|
||||||
|
const reset = async ({ key, sleep, js }) => {
|
||||||
|
await tidyPath(js);
|
||||||
|
for (let i = 0; i < 3; i++) await key('Escape');
|
||||||
|
await js(`document.activeElement?.blur?.()`);
|
||||||
|
await sleep(150);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open a page the way a user would: Ctrl+K, type its name, Enter.
|
||||||
|
const openFromSidebar = async ({ key, type, sleep }, text) => {
|
||||||
|
await key('K', ['control']);
|
||||||
|
await sleep(400);
|
||||||
|
await type(text, 30);
|
||||||
|
await sleep(700);
|
||||||
|
await key('Return');
|
||||||
|
await sleep(1000);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The element that actually scrolls the page.
|
||||||
|
const scrollPage = (js, y) => js(`
|
||||||
|
const c = [...document.querySelectorAll('*')].filter((e) => { const s = getComputedStyle(e); return /(auto|scroll)/.test(s.overflowY) && e.scrollHeight > e.clientHeight + 20 && e.clientWidth > 600; })
|
||||||
|
.sort((a, b) => b.clientWidth * b.clientHeight - a.clientWidth * a.clientHeight)[0];
|
||||||
|
if (c) { c.scrollTop = ${y}; return c.className; } return null;`);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
async editor(ctx) {
|
||||||
|
const { js, sleep, shot } = ctx;
|
||||||
|
await tidyPath(js);
|
||||||
|
await reset(ctx);
|
||||||
|
await openFromSidebar(ctx, 'Roadmap');
|
||||||
|
await scrollPage(js, 120);
|
||||||
|
await sleep(300);
|
||||||
|
await shot('editor');
|
||||||
|
},
|
||||||
|
|
||||||
|
async 'slash-menu'(ctx) {
|
||||||
|
const { js, type, sleep, shot, key } = ctx;
|
||||||
|
await reset(ctx);
|
||||||
|
await openFromSidebar(ctx, 'Launch plan');
|
||||||
|
await scrollPage(js, 170);
|
||||||
|
await sleep(300);
|
||||||
|
await js(`document.querySelector('.tail').click()`);
|
||||||
|
await sleep(400);
|
||||||
|
await type('/');
|
||||||
|
await sleep(500);
|
||||||
|
await shot('slash-menu');
|
||||||
|
await key('Escape');
|
||||||
|
},
|
||||||
|
|
||||||
|
async linking(ctx) {
|
||||||
|
const { js, sleep, shot } = ctx;
|
||||||
|
await reset(ctx);
|
||||||
|
await openFromSidebar(ctx, 'Reading list');
|
||||||
|
await scrollPage(js, 2000);
|
||||||
|
await sleep(600);
|
||||||
|
await shot('linking');
|
||||||
|
},
|
||||||
|
|
||||||
|
async canvas(ctx) {
|
||||||
|
const { sleep, shot, key } = ctx;
|
||||||
|
await reset(ctx);
|
||||||
|
await openFromSidebar(ctx, 'Project map');
|
||||||
|
await sleep(1200);
|
||||||
|
await key('1', ['shift']);
|
||||||
|
await sleep(900);
|
||||||
|
await shot('canvas');
|
||||||
|
},
|
||||||
|
|
||||||
|
async board(ctx) {
|
||||||
|
const { sleep, shot } = ctx;
|
||||||
|
await reset(ctx);
|
||||||
|
await openFromSidebar(ctx, 'Sprint board');
|
||||||
|
await sleep(900);
|
||||||
|
await scrollPage(ctx.js, 200);
|
||||||
|
await sleep(400);
|
||||||
|
await shot('board');
|
||||||
|
},
|
||||||
|
|
||||||
|
async graph(ctx) {
|
||||||
|
const { js, sleep, shot, key } = ctx;
|
||||||
|
await reset(ctx);
|
||||||
|
await js(`[...document.querySelectorAll('.sidebar .qa')].find((b) => b.textContent.includes('Graph view')).click()`);
|
||||||
|
await sleep(3500);
|
||||||
|
await js(`document.querySelector('.graph-top [aria-label="Graph settings"]')?.click()`);
|
||||||
|
await sleep(900);
|
||||||
|
await shot('graph');
|
||||||
|
await key('Escape');
|
||||||
|
},
|
||||||
|
|
||||||
|
async palette(ctx) {
|
||||||
|
const { sleep, shot, key, type } = ctx;
|
||||||
|
await reset(ctx);
|
||||||
|
await openFromSidebar(ctx, 'Roadmap');
|
||||||
|
await key('K', ['control']);
|
||||||
|
await sleep(500);
|
||||||
|
await type('tag:work road');
|
||||||
|
await sleep(700);
|
||||||
|
await shot('palette');
|
||||||
|
await key('Escape');
|
||||||
|
},
|
||||||
|
|
||||||
|
async settings(ctx) {
|
||||||
|
const { sleep, shot, key } = ctx;
|
||||||
|
await reset(ctx);
|
||||||
|
await openFromSidebar(ctx, 'Roadmap');
|
||||||
|
await key(',', ['control']);
|
||||||
|
await sleep(900);
|
||||||
|
await tidyPath(js);
|
||||||
|
await sleep(200);
|
||||||
|
await shot('settings');
|
||||||
|
await key('Escape');
|
||||||
|
},
|
||||||
|
|
||||||
|
async history(ctx) {
|
||||||
|
const { js, sleep, shot, key } = ctx;
|
||||||
|
await reset(ctx);
|
||||||
|
await openFromSidebar(ctx, 'Roadmap');
|
||||||
|
await js(`document.querySelector('[aria-label="Page options"]')?.click()`);
|
||||||
|
await sleep(500);
|
||||||
|
await js(`[...document.querySelectorAll('[role="menuitem"]')].find((m) => m.textContent.includes('Version history'))?.click()`);
|
||||||
|
await sleep(1200);
|
||||||
|
await shot('history');
|
||||||
|
await key('Escape');
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
'use strict';
|
||||||
|
// Builds a believable notes folder for the website screenshots. Usage: seedVault(dir)
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
const w = (root, rel, text) => {
|
||||||
|
const f = path.join(root, rel);
|
||||||
|
fs.mkdirSync(path.dirname(f), { recursive: true });
|
||||||
|
fs.writeFileSync(f, text);
|
||||||
|
};
|
||||||
|
const fm = (o) => {
|
||||||
|
const lines = Object.entries(o).map(([k, v]) => `${k}: ${Array.isArray(v) ? `[${v.join(', ')}]` : v}`);
|
||||||
|
return `---\n${lines.join('\n')}\n---\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// [title, icon, tags, links, one-line body]
|
||||||
|
const STUBS = [
|
||||||
|
['Meeting notes', '🗓️', ['work'], ['Roadmap', 'Release checklist'], 'Weekly sync. Decided to ship Linux first and test the Windows installer on a clean machine before announcing it.'],
|
||||||
|
['Release checklist', '✅', ['work', 'ink'], ['Roadmap', 'Windows build', 'Docs'], 'Version bump, changelog, AppImage, checksum, website config.'],
|
||||||
|
['Windows build', '🪟', ['work', 'ink'], ['Release checklist', 'Roadmap'], 'NSIS installer is configured. Needs a test pass on a clean Windows install.'],
|
||||||
|
['Docs', '📖', ['work', 'ink'], ['Roadmap', 'Keyboard shortcuts'], 'Where notes live, Obsidian compatibility, shortcuts, privacy.'],
|
||||||
|
['Keyboard shortcuts', '⌨️', ['ink'], ['Docs'], 'Ctrl+K palette, Ctrl+N new page, Ctrl+G graph.'],
|
||||||
|
['Ink website', '🌐', ['work', 'ink'], ['Roadmap', 'Docs', 'Typography'], 'Static site. Calm, dark first, no analytics.'],
|
||||||
|
['Typography', '🔤', ['learning', 'ideas'], ['Ink website', 'Colour notes'], 'Inter for UI, a mono for code. Keep the measure around 66 characters.'],
|
||||||
|
['Colour notes', '🎨', ['learning', 'ideas'], ['Typography', 'Ink website'], 'Warm blush accent on near-black. Keep contrast above 4.5:1.'],
|
||||||
|
['Ideas', '💡', ['ideas'], ['Canvas experiments', 'Plugin ideas', 'Roadmap'], 'Things I might build one day.'],
|
||||||
|
['Canvas experiments', '🧪', ['ideas', 'ink'], ['Ideas', 'Project map'], 'Alignment guides, grouping, export. What else would make a canvas calmer?'],
|
||||||
|
['Plugin ideas', '🧩', ['ideas'], ['Ideas'], 'Probably not. Keep the core small.'],
|
||||||
|
['Reading list', '📚', ['reading'], ['The Pragmatic Programmer', 'A Pattern Language', 'Designing Data-Intensive Applications', 'Meeting notes'], 'Books and articles I want to get to.'],
|
||||||
|
['The Pragmatic Programmer', '📘', ['reading'], ['Reading list', 'Software craft'], 'Tracer bullets, orthogonality, DRY. Re-read every few years.'],
|
||||||
|
['A Pattern Language', '🏛️', ['reading', 'learning'], ['Reading list', 'Software craft'], 'Patterns that make places feel alive. Applies to software more than expected.'],
|
||||||
|
['Designing Data-Intensive Applications', '📗', ['reading', 'learning'], ['Reading list', 'SQLite notes'], 'Storage engines, replication, the trouble with distributed clocks.'],
|
||||||
|
['Software craft', '🛠️', ['learning'], ['The Pragmatic Programmer', 'A Pattern Language', 'Code review'], 'Small tools, sharp edges, patient iteration.'],
|
||||||
|
['Code review', '🔍', ['learning', 'work'], ['Software craft'], 'Review the change, not the person. Ask questions first.'],
|
||||||
|
['SQLite notes', '🗃️', ['learning', 'ink'], ['Designing Data-Intensive Applications', 'Full-text search'], 'FTS5, WAL mode, and why a single file is enough.'],
|
||||||
|
['Full-text search', '🔎', ['learning', 'ink'], ['SQLite notes', 'Roadmap'], 'Index lives next to the notes and can be rebuilt any time.'],
|
||||||
|
['Trip to Lisbon', '✈️', ['personal'], ['Packing list', 'Food to try'], 'Four days in April. Trams, tiles and custard tarts.'],
|
||||||
|
['Packing list', '🎒', ['personal'], ['Trip to Lisbon'], 'Light jacket, walking shoes, charger, notebook.'],
|
||||||
|
['Food to try', '🥐', ['personal'], ['Trip to Lisbon', 'Sourdough'], 'Pastéis de nata, grilled sardines, ginjinha.'],
|
||||||
|
['Sourdough', '🍞', ['personal'], ['Food to try'], '500 g flour, 350 g water, 100 g starter, 10 g salt. Fold every 30 minutes.'],
|
||||||
|
['Garden', '🌱', ['personal'], ['Sourdough', 'Planting calendar'], 'Tomatoes, basil, and a lemon tree that refuses to flower.'],
|
||||||
|
['Planting calendar', '📆', ['personal'], ['Garden'], 'March: sow tomatoes indoors. May: harden off and plant out.'],
|
||||||
|
['Running', '🏃', ['personal'], ['Training plan'], 'Easy runs mostly. Pace is a distraction.'],
|
||||||
|
['Training plan', '📈', ['personal'], ['Running'], 'Three runs a week, one long. Add 10% at most.'],
|
||||||
|
['Book club', '☕', ['reading', 'personal'], ['Reading list', 'A Pattern Language'], 'First Thursday of the month. Next: The Overstory.'],
|
||||||
|
['Gift ideas', '🎁', ['personal', 'ideas'], ['Book club'], 'Good pens, a nice notebook, tickets for something.'],
|
||||||
|
['Home projects', '🔧', ['personal'], ['Garden', 'Gift ideas'], 'Fix the squeaky door. Paint the hallway.'],
|
||||||
|
['Learning Rust', '🦀', ['learning'], ['Software craft', 'SQLite notes'], 'Ownership clicked after drawing it on a canvas.'],
|
||||||
|
['Weekly review', '🗒️', ['work', 'personal'], ['Roadmap', 'Meeting notes', 'Training plan'], 'What went well, what did not, what is next.'],
|
||||||
|
];
|
||||||
|
|
||||||
|
function seedVault(root) {
|
||||||
|
const stubTitles = new Set(STUBS.map((s) => s[0]));
|
||||||
|
|
||||||
|
// The hero page.
|
||||||
|
w(root, 'Roadmap.md', fm({ icon: '🗺️', tags: ['work', 'ink'], created: '2026-09-02' }) + `Where Ink is heading this quarter. Details live in [[Projects]], and every meeting ends up in [[Meeting notes]].
|
||||||
|
|
||||||
|
## Q4 goals
|
||||||
|
|
||||||
|
- [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
|
||||||
|
- [ ] Write the docs, starting with [[Keyboard shortcuts]]
|
||||||
|
- [ ] Follow the [[Launch plan]]
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> Keep releases small. A calm app needs fewer surprises, not more features.
|
||||||
|
|
||||||
|
\`\`\`js
|
||||||
|
const release = {
|
||||||
|
version: '0.1.0',
|
||||||
|
platforms: ['linux'],
|
||||||
|
notes: 'plain .md files, offline, no account',
|
||||||
|
};
|
||||||
|
console.log(\`Shipping Ink \${release.version}\`);
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Why Linux first?</summary>
|
||||||
|
|
||||||
|
The AppImage is the only target built and tested so far. Windows and macOS are configured but still need a proper pass.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
`);
|
||||||
|
|
||||||
|
w(root, 'Projects.md', fm({ icon: '📁', tags: ['work'] }) + `# Projects
|
||||||
|
|
||||||
|
- [[Ink website]]
|
||||||
|
- [[Windows build]]
|
||||||
|
- [[Docs]]
|
||||||
|
- [[Release checklist]]
|
||||||
|
|
||||||
|
Back to the [[Roadmap]]. Work in progress lives on the [[Sprint board]].
|
||||||
|
`);
|
||||||
|
w(root, 'Projects/Ink website.md', fm({ icon: '🌐', tags: ['work', 'ink'] }) + 'Static site, calm and dark-first. See [[Typography]] and [[Colour notes]].\n');
|
||||||
|
|
||||||
|
// Linking demo page.
|
||||||
|
w(root, 'Reading list.md', fm({ icon: '📚', tags: ['reading'] }) + `# Reading list
|
||||||
|
|
||||||
|
Books and articles I want to get to. I keep notes on each in [[The Pragmatic Programmer]] and [[A Pattern Language]], and the monthly [[Book club]] picks the next one.
|
||||||
|
|
||||||
|
## Currently
|
||||||
|
|
||||||
|
- [x] [[The Pragmatic Programmer]]
|
||||||
|
- [ ] [[A Pattern Language]]
|
||||||
|
- [ ] [[Designing Data-Intensive Applications]]
|
||||||
|
|
||||||
|
Highlights from the weekly sync are in [[Roadmap#Q4 goals]].
|
||||||
|
|
||||||
|
![[Meeting notes]]
|
||||||
|
`);
|
||||||
|
w(root, 'Meeting notes.md', fm({ icon: '🗓️', tags: ['work'] }) + `# Meeting notes
|
||||||
|
|
||||||
|
Weekly sync, 7 October.
|
||||||
|
|
||||||
|
- Ship Linux first, announce Windows once it is tested
|
||||||
|
- Keep the docs short and honest
|
||||||
|
- Review the website copy on Friday
|
||||||
|
|
||||||
|
Follow-ups go into [[Roadmap]], [[Release checklist]] and the [[Sprint board]].
|
||||||
|
`);
|
||||||
|
|
||||||
|
for (const [title, icon, tags, links, body] of STUBS) {
|
||||||
|
if (['Meeting notes', 'Reading list'].includes(title)) continue;
|
||||||
|
const rel = ['Ink website'].includes(title) ? null : `${title}.md`;
|
||||||
|
if (!rel) continue;
|
||||||
|
const refs = links.filter((l) => stubTitles.has(l) || ['Roadmap', 'Projects', 'Project map'].includes(l)).map((l) => `[[${l}]]`);
|
||||||
|
w(root, rel, fm({ icon, tags }) + `${body}\n\nSee also ${refs.join(', ')}.\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Daily notes.
|
||||||
|
w(root, 'Daily/2026-10-10.md', fm({ icon: '📅', tags: ['personal'] }) + '- Morning run, easy pace\n- Reviewed the [[Roadmap]]\n- Sourdough went well: see [[Sourdough]]\n');
|
||||||
|
w(root, 'Daily/2026-10-11.md', fm({ icon: '📅', tags: ['personal'] }) + '- [ ] Write the release notes\n- [ ] Pack for the weekend\n- [x] Call about the [[Trip to Lisbon]] flights\n');
|
||||||
|
|
||||||
|
// Board.
|
||||||
|
w(root, 'Sprint board.md', `---
|
||||||
|
kanban-plugin: basic
|
||||||
|
icon: 🗂️
|
||||||
|
tags: [work, ink]
|
||||||
|
---
|
||||||
|
|
||||||
|
## To do
|
||||||
|
|
||||||
|
- [ ] Windows installer test pass
|
||||||
|
- [ ] macOS dmg build
|
||||||
|
- [ ] Docs: where notes are stored
|
||||||
|
- [ ] Verify checksum instructions
|
||||||
|
|
||||||
|
|
||||||
|
## Doing
|
||||||
|
|
||||||
|
- [ ] Website screenshots
|
||||||
|
- [ ] Changelog for 0.1.0
|
||||||
|
- [ ] Spellcheck language picker polish
|
||||||
|
|
||||||
|
|
||||||
|
## Done
|
||||||
|
|
||||||
|
- [x] AppImage build
|
||||||
|
- [x] Canvas export (PNG, SVG, PDF)
|
||||||
|
- [x] Version history for canvases
|
||||||
|
- [x] Graph view filters
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% kanban:settings
|
||||||
|
\`\`\`
|
||||||
|
{"kanban-plugin":"basic"}
|
||||||
|
\`\`\`
|
||||||
|
%%
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Canvas: cards, a live note preview, a link card, a group, and arrows.
|
||||||
|
const id = (n) => `n${String(n).padStart(15, '0')}`;
|
||||||
|
const canvas = {
|
||||||
|
nodes: [
|
||||||
|
{ id: id(1), type: 'group', label: 'Q4', x: -60, y: -60, width: 760, height: 520, color: '6' },
|
||||||
|
{ id: id(2), type: 'text', text: '## Research\n\n- user interviews\n- survey of 40 people\n- what makes notes calm?', x: 0, y: 20, width: 300, height: 190 },
|
||||||
|
{ id: id(3), type: 'text', text: '## Principles\n\n1. Plain files\n2. Offline first\n3. No lock-in', x: 0, y: 270, width: 300, height: 160, color: '4' },
|
||||||
|
{ id: id(4), type: 'file', file: 'Roadmap.md', subpath: '#Q4 goals', x: 400, y: 20, width: 270, height: 330 },
|
||||||
|
{ id: id(5), type: 'link', url: 'https://jsoncanvas.org', x: 860, y: 40, width: 300, height: 150 },
|
||||||
|
{ id: id(6), type: 'text', text: '## Ship it\n\nAppImage first. Windows and macOS when tested.', x: 860, y: 300, width: 300, height: 140, color: '1' },
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
|
{ id: 'e0000000000000a1', fromNode: id(2), fromSide: 'right', toNode: id(4), toSide: 'left', label: 'informs' },
|
||||||
|
{ id: 'e0000000000000a2', fromNode: id(3), fromSide: 'right', toNode: id(4), toSide: 'bottom' },
|
||||||
|
{ id: 'e0000000000000a3', fromNode: id(4), fromSide: 'right', toNode: id(5), toSide: 'left', label: 'format' },
|
||||||
|
{ id: 'e0000000000000a4', fromNode: id(4), fromSide: 'bottom', toNode: id(6), toSide: 'left' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
w(root, 'Project map.canvas', JSON.stringify(canvas, null, '\t'));
|
||||||
|
|
||||||
|
// A short page for the slash-menu shot, with room under the last line.
|
||||||
|
w(root, 'Launch plan.md', fm({ icon: '🚀', tags: ['work', 'ink'] }) + `Everything we need before the first public build goes out.
|
||||||
|
|
||||||
|
The release is a single AppImage, so the checklist is short: bump the version, write the changelog and publish the checksum next to the download.
|
||||||
|
|
||||||
|
Once that is done, the website can go live with a plain download button and nothing else.
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Earlier versions of the Roadmap page, so version history has something to show.
|
||||||
|
const now = Date.now();
|
||||||
|
const hist = [
|
||||||
|
[20 * 60e3, 'Where Ink is heading this quarter.\n\n## Q4 goals\n\n- [x] Ship the first public build as an AppImage\n- [x] Canvas export to PNG, SVG and PDF\n- [x] Version history for pages and canvases\n- [ ] Test the Windows installer on a clean machine\n'],
|
||||||
|
[3 * 3600e3, 'Where Ink is heading this quarter.\n\n## Q4 goals\n\n- [x] Ship the first public build as an AppImage\n- [x] Canvas export to PNG, SVG and PDF\n- [ ] Version history for pages and canvases\n- [ ] Test the Windows installer on a clean machine\n'],
|
||||||
|
[26 * 3600e3, 'Where Ink is heading this quarter.\n\n## Q4 goals\n\n- [x] Ship the first public build as an AppImage\n- [ ] Canvas export to PNG, SVG and PDF\n- [ ] Windows installer\n'],
|
||||||
|
[49 * 3600e3, 'Roadmap for the quarter.\n\n- [ ] AppImage\n- [ ] Canvas export\n- [ ] Windows installer\n'],
|
||||||
|
[4 * 86400e3, 'Roadmap\n\n- first build\n- canvas\n'],
|
||||||
|
[9 * 86400e3, 'Ideas for later.\n'],
|
||||||
|
];
|
||||||
|
for (const [ago, body] of hist) {
|
||||||
|
w(root, `.notes/history/Roadmap/${now - ago}.md`, fm({ icon: '🗺️', tags: ['work', 'ink'], created: '2026-09-02' }) + body);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sidebar niceties and tag colours are settings, returned for the caller.
|
||||||
|
return {
|
||||||
|
lastPage: 'Roadmap',
|
||||||
|
vaultSettings: {
|
||||||
|
pins: ['Roadmap', 'Reading list', 'Sprint board', 'Project map.canvas'],
|
||||||
|
recent: ['Roadmap', 'Reading list', 'Sprint board'],
|
||||||
|
tagColors: { work: 'blue', ink: 'pink', reading: 'yellow', learning: 'teal', personal: 'green', ideas: 'purple' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { seedVault };
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// Regenerates favicons, the small touch icons and placeholder screenshots.
|
||||||
|
// Run: bun run assets (placeholders never overwrite a file that already exists)
|
||||||
|
import sharp from 'sharp';
|
||||||
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
||||||
|
import { screens } from '../src/lib/screens.ts';
|
||||||
|
|
||||||
|
const root = new URL('..', import.meta.url).pathname;
|
||||||
|
const NIB =
|
||||||
|
'M330 340 C330 296 362 266 406 266 H618 C662 266 694 296 694 340 C694 448 646 528 598 600 C560 656 530 724 512 806 C494 724 464 656 426 600 C378 528 330 448 330 340 Z';
|
||||||
|
|
||||||
|
// Favicon: the app's nib on a flat tile (the full icon's filters are too heavy for 16px).
|
||||||
|
const favicon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="100 100 824 824">
|
||||||
|
<defs><linearGradient id="n" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f7d3c9"/><stop offset=".6" stop-color="#e4ada5"/><stop offset="1" stop-color="#c97a6d"/></linearGradient></defs>
|
||||||
|
<rect x="100" y="100" width="824" height="824" rx="190" fill="#1a1a1c"/>
|
||||||
|
<path d="${NIB}" fill="url(#n)"/>
|
||||||
|
<path d="M512 520 V800" stroke="#2a1a16" stroke-width="22" stroke-linecap="round"/>
|
||||||
|
<circle cx="512" cy="500" r="34" fill="#2a1a16"/>
|
||||||
|
</svg>`;
|
||||||
|
writeFileSync(`${root}public/favicon.svg`, favicon);
|
||||||
|
for (const [file, size] of [['favicon-32.png', 32], ['apple-touch-icon.png', 180], ['icon-192.png', 192], ['icon-512.png', 512]] as const) {
|
||||||
|
await sharp(Buffer.from(favicon), { density: 384 }).resize(size, size).png().toFile(`${root}public/${file}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder screenshots: 16:10 wireframes that say what to capture.
|
||||||
|
const esc = (s: string) => s.replace(/&/g, '&').replace(/</g, '<');
|
||||||
|
function wrap(text: string, max: number): string[] {
|
||||||
|
const out: string[] = [];
|
||||||
|
let line = '';
|
||||||
|
for (const w of text.split(' ')) {
|
||||||
|
if ((line + ' ' + w).trim().length > max) { out.push(line); line = w; } else line = (line + ' ' + w).trim();
|
||||||
|
}
|
||||||
|
return line ? [...out, line] : out;
|
||||||
|
}
|
||||||
|
function placeholder(name: string, capture: string, theme: 'dark' | 'light') {
|
||||||
|
const c = theme === 'dark'
|
||||||
|
? { ground: '#0e0e0f', island: '#161617', bar: 'rgba(255,255,255,.07)', ink: '#ececed', mute: '#a1a1a6', accent: '#e4ada5' }
|
||||||
|
: { ground: '#e7e7e9', island: '#fbfbfb', bar: 'rgba(0,0,0,.07)', ink: '#1c1c1e', mute: '#5c5c61', accent: '#b8695f' };
|
||||||
|
const lines = wrap(capture, 62).slice(0, 5);
|
||||||
|
const side = [0, 1, 2, 3, 4, 5, 6].map((i) => `<rect x="48" y="${100 + i * 44}" width="${150 - (i % 3) * 30}" height="14" rx="7" fill="${c.bar}"/>`).join('');
|
||||||
|
const text = [0, 1, 2, 3].map((i) => `<rect x="${340}" y="${280 + i * 40}" width="${760 - (i % 2) * 160}" height="12" rx="6" fill="${c.bar}"/>`).join('');
|
||||||
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="1000" viewBox="0 0 1600 1000" font-family="Inter, system-ui, sans-serif">
|
||||||
|
<rect width="1600" height="1000" fill="${c.ground}"/>
|
||||||
|
<rect x="20" y="20" width="248" height="960" rx="14" fill="${c.island}"/>${side}
|
||||||
|
<rect x="288" y="20" width="1292" height="960" rx="14" fill="${c.island}"/>
|
||||||
|
<rect x="340" y="110" width="420" height="30" rx="8" fill="${c.bar}"/>${text}
|
||||||
|
<text x="800" y="640" text-anchor="middle" font-size="30" font-weight="600" fill="${c.accent}">Placeholder: ${esc(name)}-${theme}.png</text>
|
||||||
|
${lines.map((l, i) => `<text x="800" y="${690 + i * 30}" text-anchor="middle" font-size="20" fill="${c.mute}">${esc(l)}</text>`).join('')}
|
||||||
|
</svg>`;
|
||||||
|
}
|
||||||
|
mkdirSync(`${root}src/assets/screens`, { recursive: true });
|
||||||
|
for (const [name, spec] of Object.entries(screens)) {
|
||||||
|
for (const theme of ['dark', 'light'] as const) {
|
||||||
|
const path = `${root}src/assets/screens/${name}-${theme}.svg`;
|
||||||
|
// Real captures (see scripts/capture) win; don't recreate a placeholder they shadow.
|
||||||
|
const real = ['png', 'webp', 'jpg'].some((e) => existsSync(path.replace(/\.svg$/, `.${e}`)));
|
||||||
|
if (!real && !existsSync(path)) writeFileSync(path, placeholder(name, spec.capture, theme));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('assets done');
|
||||||
|
After Width: | Height: | Size: 492 KiB |
@@ -0,0 +1,63 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
|
||||||
|
<!-- Ink app icon: a blush fountain-pen nib on the app's neutral dark ground.
|
||||||
|
Source for every size in resources/ (run `bun run icons` after editing). -->
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#2a2a2d"/>
|
||||||
|
<stop offset="1" stop-color="#121213"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="glow" cx="0.5" cy="0.5" r="0.45">
|
||||||
|
<stop offset="0" stop-color="#e4ada5" stop-opacity="0.22"/>
|
||||||
|
<stop offset="1" stop-color="#e4ada5" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="nib" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#f7d3c9"/>
|
||||||
|
<stop offset="0.6" stop-color="#e4ada5"/>
|
||||||
|
<stop offset="1" stop-color="#c97a6d"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="rim" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#ffffff" stop-opacity="0.14"/>
|
||||||
|
<stop offset="1" stop-color="#ffffff" stop-opacity="0.02"/>
|
||||||
|
</linearGradient>
|
||||||
|
<filter id="tileShadow" x="-20%" y="-20%" width="140%" height="150%">
|
||||||
|
<feDropShadow dx="0" dy="18" stdDeviation="22" flood-color="#000" flood-opacity="0.45"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="nibShadow" x="-30%" y="-30%" width="160%" height="160%">
|
||||||
|
<feDropShadow dx="0" dy="14" stdDeviation="18" flood-color="#0b0807" flood-opacity="0.55"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="grain" x="0" y="0" width="100%" height="100%">
|
||||||
|
<feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" stitchTiles="stitch"/>
|
||||||
|
<feColorMatrix values="0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0.9 0"/>
|
||||||
|
</filter>
|
||||||
|
<clipPath id="tile"><rect x="100" y="100" width="824" height="824" rx="190"/></clipPath>
|
||||||
|
<clipPath id="nibShape">
|
||||||
|
<path d="M330 340 C330 296 362 266 406 266 H618 C662 266 694 296 694 340 C694 448 646 528 598 600 C560 656 530 724 512 806 C494 724 464 656 426 600 C378 528 330 448 330 340 Z"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- tile -->
|
||||||
|
<g filter="url(#tileShadow)">
|
||||||
|
<rect x="100" y="100" width="824" height="824" rx="190" fill="url(#bg)"/>
|
||||||
|
</g>
|
||||||
|
<rect x="100" y="100" width="824" height="824" fill="url(#glow)" clip-path="url(#tile)"/>
|
||||||
|
<!-- the same fine grain as the app's background; also hides gradient banding -->
|
||||||
|
<rect x="100" y="100" width="824" height="824" filter="url(#grain)" opacity="0.07" clip-path="url(#tile)"/>
|
||||||
|
<rect x="101.5" y="101.5" width="821" height="821" rx="188.5" fill="none" stroke="url(#rim)" stroke-width="3"/>
|
||||||
|
|
||||||
|
<!-- nib -->
|
||||||
|
<g filter="url(#nibShadow)">
|
||||||
|
<path d="M330 340 C330 296 362 266 406 266 H618 C662 266 694 296 694 340 C694 448 646 528 598 600 C560 656 530 724 512 806 C494 724 464 656 426 600 C378 528 330 448 330 340 Z" fill="url(#nib)"/>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#nibShape)">
|
||||||
|
<!-- right half a little darker, so the nib reads as two folded tines -->
|
||||||
|
<rect x="512" y="250" width="200" height="580" fill="#5a2a22" opacity="0.16"/>
|
||||||
|
<!-- collar where the nib meets the pen -->
|
||||||
|
<rect x="320" y="250" width="400" height="92" fill="#5a2a22" opacity="0.22"/>
|
||||||
|
<rect x="320" y="340" width="400" height="5" fill="#fff6f0" opacity="0.35"/>
|
||||||
|
<!-- slit and breather hole -->
|
||||||
|
<path d="M512 520 V800" stroke="#2a1a16" stroke-width="15" stroke-linecap="round"/>
|
||||||
|
<circle cx="512" cy="500" r="30" fill="#2a1a16"/>
|
||||||
|
<!-- shine on the left tine -->
|
||||||
|
<path d="M378 378 C382 444 408 506 446 562" stroke="#fffaf6" stroke-width="16" stroke-linecap="round" fill="none" opacity="0.5"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 663 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 994 KiB |
|
After Width: | Height: | Size: 900 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1005 KiB |
|
After Width: | Height: | Size: 878 KiB |
|
After Width: | Height: | Size: 814 KiB |
|
After Width: | Height: | Size: 807 KiB |
|
After Width: | Height: | Size: 691 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 641 KiB |
|
After Width: | Height: | Size: 607 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 834 KiB |
|
After Width: | Height: | Size: 690 KiB |
@@ -0,0 +1,342 @@
|
|||||||
|
---
|
||||||
|
// Live, miniature reproductions of real Ink features. Decorative (aria-hidden); the captions carry the meaning.
|
||||||
|
// Each cell animates only while on screen ([data-play] → .playing, see scripts/site.ts) and shows a
|
||||||
|
// complete still frame when motion is reduced.
|
||||||
|
|
||||||
|
// Graph: a few notes that drift gently. Line ends use the same <animate> values as their nodes so they stay joined.
|
||||||
|
const tagColour = ['var(--blush)', '#93b98d', '#b7c3e2', '#e8bb83'];
|
||||||
|
const nodes = [
|
||||||
|
{ x: 100, y: 78, r: 7, c: 0 }, { x: 52, y: 44, r: 4.5, c: 0 }, { x: 150, y: 40, r: 5, c: 1 },
|
||||||
|
{ x: 160, y: 104, r: 4.5, c: 2 }, { x: 60, y: 118, r: 5.5, c: 3 }, { x: 24, y: 86, r: 3.5, c: 3 },
|
||||||
|
{ x: 120, y: 138, r: 3.5, c: 2 }, { x: 186, y: 70, r: 3.5, c: 1 }, { x: 96, y: 22, r: 3.5, c: 0 },
|
||||||
|
].map((n, i) => ({ ...n, dx: [6, -5, 4, -6, 5, -4, 6, -5, 4][i], dy: [-4, 5, -6, 4, -5, 6, -4, 5, -6][i], dur: `${6 + (i % 3) * 1.5}s` }));
|
||||||
|
const edges: [number, number][] = [[0, 1], [0, 2], [0, 3], [0, 4], [4, 5], [3, 6], [2, 7], [1, 8], [0, 8], [4, 6]];
|
||||||
|
const vals = (base: number, d: number) => `${base};${base + d};${base}`;
|
||||||
|
---
|
||||||
|
<div class="bento">
|
||||||
|
<!-- 1. Slash menu -->
|
||||||
|
<article class="cell spot c-slash" data-play>
|
||||||
|
<div class="demo" aria-hidden="true">
|
||||||
|
<div class="doc">
|
||||||
|
<p class="d-h">Q4 goals</p>
|
||||||
|
<p class="d-p typing">/<span class="caret anim"></span></p>
|
||||||
|
<div class="menu win">
|
||||||
|
<span class="hl anim"></span>
|
||||||
|
<div class="mi"><b>H1</b>Heading 1<i>#</i></div>
|
||||||
|
<div class="mi"><b>☐</b>To-do list<i>[]</i></div>
|
||||||
|
<div class="mi"><b>⊞</b>Table<i>/table</i></div>
|
||||||
|
<div class="mi"><b>{'{}'}</b>Code<i>```</i></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="saved win">
|
||||||
|
<p class="sv-h">Q4 goals.md</p>
|
||||||
|
<pre><code><span class="t-h">## Q4 goals</span>
|
||||||
|
<span class="t-l">- [x]</span> Ship the AppImage
|
||||||
|
<span class="t-l">- [ ]</span> Windows build
|
||||||
|
<span class="t-l">- [ ]</span> Write the docs
|
||||||
|
<span class="t-c">```js</span>
|
||||||
|
<span class="t-k">const</span> ink = <span class="t-s">'calm'</span>
|
||||||
|
<span class="t-c">```</span></code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="txt">
|
||||||
|
<h3>Type <kbd>/</kbd> for anything</h3>
|
||||||
|
<p>Headings, to-dos, tables, code, callouts, toggles, KaTeX math, Mermaid diagrams. Or just type Markdown.</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- 2. Wiki links -->
|
||||||
|
<article class="cell spot c-wiki" data-play>
|
||||||
|
<div class="demo" aria-hidden="true">
|
||||||
|
<div class="doc">
|
||||||
|
<p class="d-p">Next steps are in <span class="typed anim">[[Road</span><span class="caret anim"></span></p>
|
||||||
|
<div class="pop win anim">
|
||||||
|
<div class="po on">Roadmap</div>
|
||||||
|
<div class="po">Roadmap <em>›</em> Q4 goals</div>
|
||||||
|
<div class="po">Road trip</div>
|
||||||
|
</div>
|
||||||
|
<p class="back">↩ 3 backlinks</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="txt">
|
||||||
|
<h3>[[Links]] that keep up</h3>
|
||||||
|
<p>Link pages and headings, embed notes inline. Rename something and every link follows.</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- 3. Canvas -->
|
||||||
|
<article class="cell spot c-canvas" data-play>
|
||||||
|
<div class="demo" aria-hidden="true">
|
||||||
|
<div class="cv dots-bg">
|
||||||
|
<div class="grp" style="left:2.5%;top:6.4%;width:35%;height:48.2%"><span>Q4</span></div>
|
||||||
|
<div class="k" style="left:5%;top:16%;width:30%;height:34%"><strong>Research</strong><span>– interviews</span><span>– survey</span></div>
|
||||||
|
<div class="k note" style="left:60%;top:9%;width:35%;height:35.5%"><strong>Roadmap</strong><span class="ln"></span><span class="ln short"></span></div>
|
||||||
|
<div class="k link" style="left:50%;top:64%;width:37.5%;height:27%"><strong>jsoncanvas.org</strong><span>An open file format</span></div>
|
||||||
|
<svg class="arrows" viewBox="0 0 400 220">
|
||||||
|
<path class="draw anim" pathLength="1" d="M140 70 C 190 70, 190 59, 240 59" />
|
||||||
|
<path class="head anim" d="M233 54 L240 59 L233 64" />
|
||||||
|
<path class="draw d2 anim" pathLength="1" d="M80 112 V170 H200" />
|
||||||
|
<path class="head d2 anim" d="M193 165 L200 170 L193 175" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="txt">
|
||||||
|
<h3>A canvas, Obsidian-compatible</h3>
|
||||||
|
<p>Cards, note previews, link cards, groups and arrows, saved as JSON Canvas. Export to PNG, SVG or PDF.</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- 4. Board -->
|
||||||
|
<article class="cell spot c-board" data-play>
|
||||||
|
<div class="demo" aria-hidden="true">
|
||||||
|
<div class="board">
|
||||||
|
<div class="col"><p>To do</p><span class="bc">Windows build</span><span class="bc">Docs</span></div>
|
||||||
|
<div class="col"><p>Doing</p><span class="bc">Website</span></div>
|
||||||
|
<div class="col"><p>Done</p><span class="bc">Canvas</span><span class="bc">Search</span></div>
|
||||||
|
<span class="bc mover anim">AppImage</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="txt">
|
||||||
|
<h3>Boards from a page</h3>
|
||||||
|
<p>Headings become columns and to-dos become cards. It stays a Markdown file underneath.</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- 5. Graph -->
|
||||||
|
<article class="cell spot c-graph" data-play>
|
||||||
|
<div class="demo" aria-hidden="true">
|
||||||
|
<svg class="graph" viewBox="0 0 210 160" data-smil>
|
||||||
|
{edges.map(([a, b]) => {
|
||||||
|
const A = nodes[a], B = nodes[b];
|
||||||
|
return (
|
||||||
|
<line x1={A.x} y1={A.y} x2={B.x} y2={B.y}>
|
||||||
|
<animate attributeName="x1" values={vals(A.x, A.dx)} dur={A.dur} repeatCount="indefinite" />
|
||||||
|
<animate attributeName="y1" values={vals(A.y, A.dy)} dur={A.dur} repeatCount="indefinite" />
|
||||||
|
<animate attributeName="x2" values={vals(B.x, B.dx)} dur={B.dur} repeatCount="indefinite" />
|
||||||
|
<animate attributeName="y2" values={vals(B.y, B.dy)} dur={B.dur} repeatCount="indefinite" />
|
||||||
|
</line>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{nodes.map((n) => (
|
||||||
|
<circle cx={n.x} cy={n.y} r={n.r} fill={tagColour[n.c]}>
|
||||||
|
<animate attributeName="cx" values={vals(n.x, n.dx)} dur={n.dur} repeatCount="indefinite" />
|
||||||
|
<animate attributeName="cy" values={vals(n.y, n.dy)} dur={n.dur} repeatCount="indefinite" />
|
||||||
|
</circle>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="txt">
|
||||||
|
<h3>Graph view</h3>
|
||||||
|
<p>How your notes link, coloured by tag.</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- 6. Palette -->
|
||||||
|
<article class="cell spot c-pal" data-play>
|
||||||
|
<div class="demo" aria-hidden="true">
|
||||||
|
<div class="pal win">
|
||||||
|
<div class="q"><span class="mag">⌕</span>tag:work road<span class="caret anim"></span></div>
|
||||||
|
<div class="r anim on"><em>Page</em>Roadmap<i>#work</i></div>
|
||||||
|
<div class="r anim"><em>Page</em>Road to 1.0<i>#work</i></div>
|
||||||
|
<div class="r anim"><em>Canvas</em>Project map<i>#work</i></div>
|
||||||
|
<div class="r anim"><em>Command</em>New board</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="txt">
|
||||||
|
<h3>Everything is <kbd data-mod>Ctrl</kbd> <kbd>K</kbd> away</h3>
|
||||||
|
<p>Full-text search on a local SQLite index, with <code>tag:</code> and <code>in:</code> filters, plus every command.</p>
|
||||||
|
<button type="button" class="try" data-palette-open>Try it on this site <span aria-hidden="true">→</span></button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bento { display: grid; gap: 1rem; grid-template-columns: 1fr; }
|
||||||
|
@media (min-width: 680px) {
|
||||||
|
.bento { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
.c-slash, .c-pal { grid-column: span 2; }
|
||||||
|
}
|
||||||
|
@media (min-width: 980px) {
|
||||||
|
.bento { grid-template-columns: repeat(6, 1fr); }
|
||||||
|
.c-slash { grid-column: span 4; }
|
||||||
|
.c-wiki { grid-column: span 2; }
|
||||||
|
.c-canvas, .c-board { grid-column: span 3; }
|
||||||
|
.c-graph { grid-column: span 2; }
|
||||||
|
.c-pal { grid-column: span 4; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
display: flex; flex-direction: column; border-radius: 18px; overflow: hidden;
|
||||||
|
background: var(--surface); border: 1px solid var(--line);
|
||||||
|
transition: transform 0.35s var(--ease), border-color 0.35s, box-shadow 0.35s;
|
||||||
|
}
|
||||||
|
.cell:hover { transform: translateY(-3px); box-shadow: var(--shadow-sm); }
|
||||||
|
.demo {
|
||||||
|
position: relative; height: 230px; padding: 1.25rem 1.25rem 0; overflow: hidden;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
background: linear-gradient(to bottom, var(--surface-2), var(--surface));
|
||||||
|
-webkit-mask-image: linear-gradient(to bottom, #000 82%, transparent);
|
||||||
|
mask-image: linear-gradient(to bottom, #000 82%, transparent);
|
||||||
|
}
|
||||||
|
.txt { padding: 1.15rem 1.35rem 1.4rem; display: grid; gap: 0.4rem; align-content: start; }
|
||||||
|
.txt h3 { font-size: 1.08rem; }
|
||||||
|
.txt p { color: var(--ink-2); font-size: 0.95rem; }
|
||||||
|
.txt kbd { font-size: 0.8em; vertical-align: 0.08em; }
|
||||||
|
.try {
|
||||||
|
justify-self: start; margin-top: 0.4rem; padding: 0; border: 0; background: none; cursor: pointer;
|
||||||
|
color: var(--accent-text); font: 500 0.93rem/1.4 var(--f-ui);
|
||||||
|
}
|
||||||
|
.try:hover { text-decoration: underline; text-underline-offset: 3px; }
|
||||||
|
:global(html:not(.js)) .try { display: none; }
|
||||||
|
|
||||||
|
/* shared mock bits */
|
||||||
|
.win { background: var(--raise); border: 1px solid var(--line-2); border-radius: 10px; box-shadow: var(--shadow-sm); }
|
||||||
|
.doc { font-size: 0.86rem; color: var(--ink); }
|
||||||
|
.doc p { margin: 0 0 0.45rem; }
|
||||||
|
.d-h { font-weight: 650; font-size: 1.15rem; letter-spacing: -0.02em; }
|
||||||
|
.d-p { color: var(--ink-2); }
|
||||||
|
.caret { display: inline-block; width: 1.5px; height: 1.05em; margin-left: 1px; vertical-align: -0.15em; background: var(--blush); }
|
||||||
|
/* With JS, demos run only while on screen; without JS they simply run. */
|
||||||
|
:global(.js) .anim { animation-play-state: paused !important; }
|
||||||
|
:global(.js .playing) .anim { animation-play-state: running !important; }
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.caret { animation: blink 1.1s steps(1) infinite; }
|
||||||
|
}
|
||||||
|
@keyframes blink { 50% { opacity: 0; } }
|
||||||
|
|
||||||
|
/* 1. slash menu */
|
||||||
|
.saved { display: none; position: absolute; right: 1.25rem; top: 1.25rem; width: 46%; padding: 0; overflow: hidden; }
|
||||||
|
.sv-h { margin: 0; padding: 0.5rem 0.75rem; font: 500 0.7rem/1 var(--f-mono); color: var(--muted); border-bottom: 1px solid var(--line); }
|
||||||
|
.saved pre { border: 0; border-radius: 0; background: none; font-size: 0.76rem; line-height: 1.65; padding: 0.6rem 0.75rem; }
|
||||||
|
.t-h { color: var(--ink); font-weight: 600; }
|
||||||
|
.t-l, .t-k { color: var(--accent-text); }
|
||||||
|
.t-c { color: var(--muted); }
|
||||||
|
.t-s { color: #93b98d; }
|
||||||
|
@media (prefers-color-scheme: light) { .t-s { color: #4f7a3c; } }
|
||||||
|
@media (min-width: 980px) { .saved { display: block; } }
|
||||||
|
.typing { color: var(--ink); }
|
||||||
|
.menu { position: relative; width: min(100%, 290px); padding: 5px; }
|
||||||
|
.mi { position: relative; display: flex; align-items: center; gap: 0.6rem; height: 30px; padding: 0 0.55rem; color: var(--ink); font-size: 0.84rem; }
|
||||||
|
.mi b {
|
||||||
|
display: grid; place-items: center; width: 22px; height: 22px; border-radius: 6px; flex: none;
|
||||||
|
font: 600 0.66rem/1 var(--f-mono); border: 1px solid var(--line-2); color: var(--ink-2);
|
||||||
|
}
|
||||||
|
.mi i { margin-left: auto; font: 0.72rem/1 var(--f-mono); color: var(--muted); font-style: normal; }
|
||||||
|
.hl { position: absolute; left: 5px; right: 5px; top: 5px; height: 30px; border-radius: 7px; background: var(--blush-soft); }
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.hl { animation: hl 7.5s var(--ease) infinite; }
|
||||||
|
}
|
||||||
|
@keyframes hl {
|
||||||
|
0%, 20% { transform: translateY(0); }
|
||||||
|
25%, 45% { transform: translateY(30px); }
|
||||||
|
50%, 70% { transform: translateY(60px); }
|
||||||
|
75%, 95% { transform: translateY(90px); }
|
||||||
|
100% { transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. wiki link autocomplete */
|
||||||
|
.typed {
|
||||||
|
display: inline-block; overflow: hidden; white-space: nowrap; vertical-align: bottom; width: 6ch;
|
||||||
|
font: 0.84rem/1.4 var(--f-mono); color: var(--accent-text);
|
||||||
|
}
|
||||||
|
.pop { width: min(100%, 220px); padding: 4px; margin: 0.15rem 0 0.8rem 2.5rem; }
|
||||||
|
.po { padding: 0.42rem 0.6rem; border-radius: 6px; font-size: 0.82rem; color: var(--ink-2); }
|
||||||
|
.po.on { background: var(--blush-soft); color: var(--ink); }
|
||||||
|
.po em { color: var(--muted); font-style: normal; }
|
||||||
|
.back { font-size: 0.78rem; color: var(--muted); border-top: 1px solid var(--line); padding-top: 0.6rem; }
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.typed { animation: type 6s steps(6, end) infinite; }
|
||||||
|
.pop { animation: pop 6s var(--ease) infinite; }
|
||||||
|
}
|
||||||
|
@keyframes type { 0% { width: 0; } 30%, 100% { width: 6ch; } }
|
||||||
|
@keyframes pop {
|
||||||
|
0%, 30% { opacity: 0; transform: translateY(-4px); }
|
||||||
|
36%, 90% { opacity: 1; transform: none; }
|
||||||
|
100% { opacity: 0; transform: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. canvas */
|
||||||
|
.c-canvas .demo { padding: 0; }
|
||||||
|
.cv { position: absolute; inset: 0; }
|
||||||
|
.cv > div { position: absolute; }
|
||||||
|
.grp { border: 1px dashed var(--blush-line); border-radius: 10px; background: var(--blush-soft); }
|
||||||
|
.grp span { position: absolute; top: -0.65rem; left: 0.5rem; font: 500 0.68rem/1 var(--f-mono); color: var(--accent-text); background: var(--surface-2); padding: 0.1rem 0.3rem; border-radius: 4px; }
|
||||||
|
.k {
|
||||||
|
display: flex; flex-direction: column; gap: 0.15rem; padding: 0.5rem 0.6rem; border-radius: 8px;
|
||||||
|
background: var(--raise); border: 1px solid var(--line-2); box-shadow: var(--shadow-sm);
|
||||||
|
font-size: 0.72rem; color: var(--ink-2); overflow: hidden;
|
||||||
|
}
|
||||||
|
.k strong { color: var(--ink); font-size: 0.78rem; }
|
||||||
|
.k .ln { height: 6px; border-radius: 3px; background: var(--line-2); margin-top: 0.3rem; }
|
||||||
|
.k .ln.short { width: 60%; }
|
||||||
|
.k.link { border-left: 3px solid var(--blush); }
|
||||||
|
.arrows { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
|
||||||
|
.arrows path { fill: none; stroke: var(--ink-2); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
|
||||||
|
.draw { stroke-dasharray: 1; stroke-dashoffset: 0; }
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.draw { animation: draw 6s var(--ease) infinite; }
|
||||||
|
.head { animation: head 6s var(--ease) infinite; }
|
||||||
|
.d2 { animation-delay: 0.7s; }
|
||||||
|
}
|
||||||
|
@keyframes draw {
|
||||||
|
0% { stroke-dashoffset: 1; opacity: 1; }
|
||||||
|
30%, 85% { stroke-dashoffset: 0; opacity: 1; }
|
||||||
|
95%, 100% { stroke-dashoffset: 0; opacity: 0; }
|
||||||
|
}
|
||||||
|
@keyframes head { 0%, 26% { opacity: 0; } 32%, 85% { opacity: 1; } 95%, 100% { opacity: 0; } }
|
||||||
|
|
||||||
|
/* 4. board */
|
||||||
|
.board { position: relative; display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; height: 100%; }
|
||||||
|
.col { display: flex; flex-direction: column; gap: 6px; padding: 6px; border-radius: 10px; background: var(--hover); }
|
||||||
|
.col p { margin: 0 0 2px; padding: 2px 4px; font: 500 0.66rem/1.4 var(--f-mono); text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); }
|
||||||
|
.bc {
|
||||||
|
display: flex; align-items: center; height: 34px; padding: 0 0.6rem; border-radius: 7px; flex: none;
|
||||||
|
background: var(--raise); border: 1px solid var(--line-2); font-size: 0.76rem; color: var(--ink);
|
||||||
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
/* Starts in "Doing", slot 2; travels one column right and one slot down to "Done", slot 3. */
|
||||||
|
.mover {
|
||||||
|
position: absolute; top: calc(6px + 25px + 2px + 40px); left: calc((100% - 16px) / 3 + 8px + 6px);
|
||||||
|
width: calc((100% - 16px) / 3 - 12px);
|
||||||
|
border-color: var(--blush-line); box-shadow: 0 0 0 3px var(--blush-soft);
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.mover { animation: move 6.5s var(--ease) infinite; }
|
||||||
|
}
|
||||||
|
@keyframes move {
|
||||||
|
0% { opacity: 0; transform: none; }
|
||||||
|
6%, 18% { opacity: 1; transform: none; }
|
||||||
|
28% { transform: translate(8px, -6px) rotate(3deg); box-shadow: var(--shadow); }
|
||||||
|
55% { transform: translate(calc(100% + 20px), 34px) rotate(3deg); box-shadow: var(--shadow); }
|
||||||
|
62%, 86% { opacity: 1; transform: translate(calc(100% + 20px), 40px); }
|
||||||
|
96%, 100% { opacity: 0; transform: translate(calc(100% + 20px), 40px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. graph */
|
||||||
|
.c-graph .demo { display: grid; place-items: center; padding: 0.75rem; }
|
||||||
|
.graph { width: 100%; height: 100%; }
|
||||||
|
.graph line { stroke: var(--line-2); stroke-width: 1; }
|
||||||
|
|
||||||
|
/* 6. palette */
|
||||||
|
.pal { max-width: 460px; margin-inline: auto; padding: 4px; }
|
||||||
|
.q { display: flex; align-items: center; gap: 0.5rem; padding: 0.55rem 0.6rem 0.7rem; border-bottom: 1px solid var(--line); margin-bottom: 4px; font-size: 0.88rem; color: var(--ink); }
|
||||||
|
.mag { color: var(--muted); }
|
||||||
|
.r { display: flex; align-items: center; gap: 0.6rem; padding: 0.48rem 0.6rem; border-radius: 7px; font-size: 0.84rem; color: var(--ink-2); }
|
||||||
|
.r em { width: 4.6rem; font: normal 0.66rem/1 var(--f-mono); text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
|
||||||
|
.r i { margin-left: auto; font-style: normal; font-size: 0.74rem; color: var(--accent-text); }
|
||||||
|
.r.on { background: var(--blush-soft); color: var(--ink); }
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.r { animation: rise 5s var(--ease) infinite; }
|
||||||
|
.r:nth-child(3) { animation-delay: 0.12s; }
|
||||||
|
.r:nth-child(4) { animation-delay: 0.24s; }
|
||||||
|
.r:nth-child(5) { animation-delay: 0.36s; }
|
||||||
|
}
|
||||||
|
@keyframes rise {
|
||||||
|
0% { opacity: 0; transform: translateY(6px); }
|
||||||
|
8%, 90% { opacity: 1; transform: none; }
|
||||||
|
100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.r em { display: none; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
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;
|
||||||
|
---
|
||||||
|
<div class:list={['dl', { center }]} data-download data-linux-url={linux.url} data-linux-meta={`v${config.version} · ${linux.format} · ${formatSize(linux.bytes)}`}>
|
||||||
|
<a class:list={['btn', variant]} href={href('/download/')} data-download-btn>
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4v11m0 0l-4.5-4.5M12 15l4.5-4.5M5 19h14" /></svg>
|
||||||
|
<span data-download-label>Download Ink</span>
|
||||||
|
</a>
|
||||||
|
{note && <p class="muted" data-download-note>Linux AppImage available · Windows & macOS coming soon</p>}
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.dl { display: grid; gap: 0.7rem; justify-items: start; }
|
||||||
|
.btn { min-width: 12.5rem; }
|
||||||
|
p { font-size: 0.88rem; }
|
||||||
|
.dl.center { justify-items: center; text-align: center; }
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
// Progressive enhancement: without JS the button goes to /download/ and lists every platform.
|
||||||
|
import { detectOS } from '../lib/os';
|
||||||
|
const os = detectOS();
|
||||||
|
|
||||||
|
document.querySelectorAll<HTMLElement>('[data-download]').forEach((box) => {
|
||||||
|
const btn = box.querySelector<HTMLAnchorElement>('[data-download-btn]');
|
||||||
|
const label = box.querySelector<HTMLElement>('[data-download-label]');
|
||||||
|
const note = box.querySelector<HTMLElement>('[data-download-note]');
|
||||||
|
if (!btn || !label) return;
|
||||||
|
if (os === 'linux') {
|
||||||
|
label.textContent = 'Download for Linux';
|
||||||
|
btn.href = box.dataset.linuxUrl || btn.href;
|
||||||
|
if (note) note.textContent = box.dataset.linuxMeta ?? '';
|
||||||
|
} else if (os === 'windows') {
|
||||||
|
label.textContent = 'Download options';
|
||||||
|
if (note) note.textContent = 'The Windows version is not ready yet. The Linux AppImage is available now.';
|
||||||
|
} else if (os === 'macos') {
|
||||||
|
label.textContent = 'Download options';
|
||||||
|
if (note) note.textContent = 'The macOS version is not ready yet. The Linux AppImage is available now.';
|
||||||
|
} else if (os === 'mobile') {
|
||||||
|
label.textContent = 'Download options';
|
||||||
|
if (note) note.textContent = 'Ink is a desktop app. Open this page on your computer to download it.';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
import Screenshot from './Screenshot.astro';
|
||||||
|
import type { ScreenName } from '../lib/screens';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
id?: string;
|
||||||
|
eyebrow?: string;
|
||||||
|
title: string;
|
||||||
|
screen: ScreenName;
|
||||||
|
reverse?: boolean;
|
||||||
|
}
|
||||||
|
const { id, eyebrow, title, screen, reverse = false } = Astro.props;
|
||||||
|
---
|
||||||
|
<section class:list={['fb', 'reveal', { reverse }]} id={id}>
|
||||||
|
<div class="text">
|
||||||
|
{eyebrow && <p class="eyebrow">{eyebrow}</p>}
|
||||||
|
<h3 class="fb-title">{title}</h3>
|
||||||
|
<div class="copy"><slot /></div>
|
||||||
|
</div>
|
||||||
|
<div class="media"><Screenshot name={screen} sizes="(min-width: 960px) 640px, 100vw" /></div>
|
||||||
|
</section>
|
||||||
|
<style>
|
||||||
|
.fb { display: grid; gap: 1.75rem; align-items: center; padding-block: clamp(2rem, 5vw, 3.5rem); }
|
||||||
|
.text { display: grid; gap: 0.9rem; align-content: center; }
|
||||||
|
.fb-title { font-size: clamp(1.45rem, 1.6vw + 1rem, 2rem); letter-spacing: -0.025em; }
|
||||||
|
.copy { color: var(--ink-2); display: grid; gap: 0.8rem; }
|
||||||
|
.copy :global(ul) { margin: 0; padding: 0; list-style: none; display: grid; gap: 0.45rem; }
|
||||||
|
.copy :global(li) { padding-left: 1.25rem; position: relative; }
|
||||||
|
.copy :global(li)::before { content: ''; position: absolute; left: 0; top: 0.72em; width: 6px; height: 6px; border-radius: 50%; background: var(--blush); }
|
||||||
|
.media { min-width: 0; }
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.fb { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); gap: 3.5rem; }
|
||||||
|
.fb.reverse { grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr); }
|
||||||
|
.fb.reverse .text { order: 2; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
// "It's just files": a notes folder as Ink keeps it on disk, and the raw Markdown of one note.
|
||||||
|
type Row = { name: string; depth: number; kind: 'dir' | 'md' | 'canvas' | 'img' | 'hidden'; on?: boolean; note?: string };
|
||||||
|
const rows: Row[] = [
|
||||||
|
{ name: 'Notes', depth: 0, kind: 'dir' },
|
||||||
|
{ name: '.notes', depth: 1, kind: 'hidden', note: 'index, trash, templates' },
|
||||||
|
{ name: 'Daily', depth: 1, kind: 'dir' },
|
||||||
|
{ name: '2026-10-11.md', depth: 2, kind: 'md' },
|
||||||
|
{ name: 'Projects', depth: 1, kind: 'dir' },
|
||||||
|
{ name: 'Roadmap.md', depth: 2, kind: 'md', on: true },
|
||||||
|
{ name: 'sketch.png', depth: 2, kind: 'img' },
|
||||||
|
{ name: 'Projects.md', depth: 1, kind: 'md' },
|
||||||
|
{ name: 'Project map.canvas', depth: 1, kind: 'canvas' },
|
||||||
|
];
|
||||||
|
const icon = { dir: '▸', md: 'M↓', canvas: '◫', img: '▣', hidden: '·' } as const;
|
||||||
|
---
|
||||||
|
<div class="files win-frame">
|
||||||
|
<div class="bar" aria-hidden="true"><span class="dots"><i></i><i></i><i></i></span><span class="path">~/Notes/Projects/Roadmap.md</span></div>
|
||||||
|
<div class="split">
|
||||||
|
<ul class="tree" aria-label="Example notes folder">
|
||||||
|
{rows.map((r, i) => (
|
||||||
|
<li class:list={[r.kind, { on: r.on }]} style={`--d:${r.depth};--i:${i}`}>
|
||||||
|
<span class="ic" aria-hidden="true">{icon[r.kind]}</span>
|
||||||
|
<span class="nm">{r.name}</span>
|
||||||
|
{r.note && <span class="nt">{r.note}</span>}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<pre class="src" aria-label="Contents of Roadmap.md"><code><span class="fm">---
|
||||||
|
icon: 🗺️
|
||||||
|
tags: [work, q4]
|
||||||
|
---</span>
|
||||||
|
<span class="hd"># Roadmap</span>
|
||||||
|
|
||||||
|
<span class="li">- [x]</span> Ship the AppImage
|
||||||
|
<span class="li">- [ ]</span> Windows build
|
||||||
|
<span class="li">- [ ]</span> Write the docs
|
||||||
|
|
||||||
|
Ideas live in <span class="lk">[[Projects#Ideas]]</span>.
|
||||||
|
<span class="lk"></span></code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.files { border-radius: 16px; overflow: hidden; background: var(--surface); border: 1px solid var(--line-2); box-shadow: var(--shadow); }
|
||||||
|
.bar { display: flex; align-items: center; gap: 0.75rem; height: 38px; padding-inline: 14px; background: var(--surface-2); border-bottom: 1px solid var(--line); }
|
||||||
|
.dots { display: flex; gap: 7px; }
|
||||||
|
.dots i { width: 11px; height: 11px; border-radius: 50%; background: var(--line-2); }
|
||||||
|
.path { flex: 1; text-align: center; font: 0.75rem/1 var(--f-mono); color: var(--muted); padding-right: 46px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
|
.split { display: grid; }
|
||||||
|
.tree { list-style: none; margin: 0; padding: 0.75rem 0.5rem; font-size: 0.86rem; border-bottom: 1px solid var(--line); }
|
||||||
|
.tree li { display: flex; align-items: center; gap: 0.5rem; padding: 0.32rem 0.6rem 0.32rem calc(0.6rem + var(--d) * 1.1rem); border-radius: 8px; color: var(--ink-2); white-space: nowrap; }
|
||||||
|
.tree li.on { background: var(--blush-soft); color: var(--ink); }
|
||||||
|
.ic { width: 1.4rem; flex: none; font: 600 0.62rem/1 var(--f-mono); color: var(--muted); text-align: center; }
|
||||||
|
.md .ic, .on .ic { color: var(--accent-text); }
|
||||||
|
.dir .nm { color: var(--ink); font-weight: 500; }
|
||||||
|
.hidden .nm { color: var(--muted); }
|
||||||
|
.nt { margin-left: auto; font-size: 0.72rem; color: var(--muted); overflow: hidden; text-overflow: ellipsis; }
|
||||||
|
.src { border: 0; border-radius: 0; background: var(--code-bg); font-size: 0.82rem; line-height: 1.75; padding: 1rem 1.2rem; }
|
||||||
|
.fm { color: var(--muted); }
|
||||||
|
.hd { color: var(--ink); font-weight: 600; }
|
||||||
|
.li { color: var(--accent-text); }
|
||||||
|
.lk { color: #b7c3e2; }
|
||||||
|
@media (prefers-color-scheme: light) { .lk { color: #46609b; } }
|
||||||
|
|
||||||
|
/* Tree rows settle in one after another when the section scrolls into view. */
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
:global(.js) .tree li { opacity: 0; transform: translateX(-6px); transition: opacity 0.5s var(--ease), transform 0.5s var(--ease); transition-delay: calc(var(--i) * 55ms); }
|
||||||
|
:global(.js .reveal.in) .tree li { opacity: 1; transform: none; }
|
||||||
|
}
|
||||||
|
@media (min-width: 720px) {
|
||||||
|
.split { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); }
|
||||||
|
.tree { border-bottom: 0; border-right: 1px solid var(--line); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
import Wordmark from './Wordmark.astro';
|
||||||
|
import { config } from '../config';
|
||||||
|
import { href } from '../lib/url';
|
||||||
|
---
|
||||||
|
<footer class="site-footer">
|
||||||
|
<div class="wrap grid">
|
||||||
|
<div class="about">
|
||||||
|
<Wordmark size={24} />
|
||||||
|
<p class="muted">{config.alt}</p>
|
||||||
|
<p class="muted small">No analytics. No cookies. No third-party requests, fonts included.</p>
|
||||||
|
</div>
|
||||||
|
<nav aria-label="Footer">
|
||||||
|
<a href={href('/features/')}>Features</a>
|
||||||
|
<a href={href('/download/')}>Download</a>
|
||||||
|
<a href={href('/changelog/')}>Changelog</a>
|
||||||
|
<a href={href('/docs/')}>Docs & FAQ</a>
|
||||||
|
<a href={config.repo} rel="noopener">Source & issues</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<style>
|
||||||
|
.site-footer { border-top: 1px solid var(--line); padding-block: 3rem; margin-top: 2rem; }
|
||||||
|
.grid { display: grid; gap: 2rem; }
|
||||||
|
.about { display: grid; gap: 0.6rem; align-content: start; }
|
||||||
|
.small { font-size: 0.88rem; }
|
||||||
|
nav { display: flex; flex-wrap: wrap; gap: 0.5rem 1.4rem; align-content: start; }
|
||||||
|
nav a { color: var(--ink-2); text-decoration: none; font-size: 0.95rem; }
|
||||||
|
nav a:hover { color: var(--ink); text-decoration: underline; }
|
||||||
|
@media (min-width: 720px) { .grid { grid-template-columns: 1.4fr 1fr; } nav { justify-content: flex-end; } }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
import Wordmark from './Wordmark.astro';
|
||||||
|
import { href } from '../lib/url';
|
||||||
|
|
||||||
|
const { current = '' } = Astro.props as { current?: string };
|
||||||
|
const links = [
|
||||||
|
{ path: '/features/', label: 'Features' },
|
||||||
|
{ path: '/download/', label: 'Download' },
|
||||||
|
{ path: '/changelog/', label: 'Changelog' },
|
||||||
|
{ path: '/docs/', label: 'Docs' },
|
||||||
|
];
|
||||||
|
---
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="pill">
|
||||||
|
<a class="brand" href={href('/')} aria-label="Ink, home"><Wordmark size={26} /></a>
|
||||||
|
<nav id="site-nav" class="nav" aria-label="Main">
|
||||||
|
{links.map((l) => (
|
||||||
|
<a href={href(l.path)} aria-current={current === l.path ? 'page' : undefined}>{l.label}</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
<div class="tools">
|
||||||
|
<button type="button" class="search" data-palette-open aria-label="Search the site (Ctrl K)">
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="6.5" /><path d="M16 16l4 4" /></svg>
|
||||||
|
<span class="s-label">Search</span>
|
||||||
|
<span class="s-keys" aria-hidden="true"><kbd data-mod>Ctrl</kbd><kbd>K</kbd></span>
|
||||||
|
</button>
|
||||||
|
<a class="get" href={href('/download/')}>Get Ink</a>
|
||||||
|
<button type="button" class="menu" aria-expanded="false" aria-controls="site-nav" data-menu>
|
||||||
|
<span class="sr-only">Menu</span>
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path class="l1" d="M5 9h14" /><path class="l2" d="M5 15h14" /></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.site-header { position: sticky; top: 0; z-index: 50; padding: 12px 0.75rem 0; pointer-events: none; }
|
||||||
|
.pill {
|
||||||
|
pointer-events: auto;
|
||||||
|
width: min(100%, var(--wrap)); margin-inline: auto;
|
||||||
|
display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;
|
||||||
|
min-height: 56px; padding: 0 0.5rem 0 1rem;
|
||||||
|
background: color-mix(in srgb, var(--surface) 66%, transparent);
|
||||||
|
backdrop-filter: blur(20px) saturate(1.4);
|
||||||
|
-webkit-backdrop-filter: blur(20px) saturate(1.4);
|
||||||
|
border: 1px solid var(--line); border-radius: 16px;
|
||||||
|
box-shadow: 0 10px 30px -18px rgba(0, 0, 0, 0.5);
|
||||||
|
transition: border-color 0.3s, box-shadow 0.3s;
|
||||||
|
}
|
||||||
|
.brand { text-decoration: none; margin-right: auto; padding-block: 0.6rem; border-radius: 8px; }
|
||||||
|
.nav { display: flex; gap: 0.15rem; flex-wrap: wrap; order: 3; width: 100%; padding-bottom: 0.5rem; }
|
||||||
|
.nav a {
|
||||||
|
position: relative; color: var(--ink-2); text-decoration: none; padding: 0.45rem 0.8rem; border-radius: 10px;
|
||||||
|
font-size: 0.93rem; font-weight: 500; transition: color 0.2s, background-color 0.2s;
|
||||||
|
}
|
||||||
|
.nav a:hover { color: var(--ink); background: var(--hover); }
|
||||||
|
.nav a[aria-current='page'] { color: var(--ink); }
|
||||||
|
.nav a[aria-current='page']::after {
|
||||||
|
content: ''; position: absolute; left: 50%; bottom: 2px; width: 4px; height: 4px; margin-left: -2px;
|
||||||
|
border-radius: 50%; background: var(--blush);
|
||||||
|
}
|
||||||
|
.tools { display: flex; align-items: center; gap: 0.4rem; }
|
||||||
|
|
||||||
|
.search {
|
||||||
|
display: none; align-items: center; gap: 0.5rem; height: 36px; padding: 0 0.45rem 0 0.7rem; border-radius: 10px;
|
||||||
|
border: 1px solid var(--line); background: var(--hover); color: var(--muted); font: 500 0.85rem/1 var(--f-ui); cursor: pointer;
|
||||||
|
transition: color 0.2s, border-color 0.2s;
|
||||||
|
}
|
||||||
|
.search:hover { color: var(--ink); border-color: var(--line-2); }
|
||||||
|
.search svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; }
|
||||||
|
.s-keys { display: inline-flex; gap: 2px; }
|
||||||
|
.s-keys kbd { font-size: 0.68rem; padding: 0.22rem 0.35rem; }
|
||||||
|
|
||||||
|
.get {
|
||||||
|
display: inline-flex; align-items: center; height: 36px; padding: 0 0.95rem; border-radius: 10px;
|
||||||
|
background: var(--btn); color: var(--btn-ink); font-weight: 600; font-size: 0.9rem; text-decoration: none;
|
||||||
|
transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease);
|
||||||
|
}
|
||||||
|
.get:hover { transform: translateY(-1px); box-shadow: 0 8px 22px -10px var(--blush); }
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
display: none; width: 40px; height: 40px; place-items: center; border-radius: 10px;
|
||||||
|
background: none; border: 0; color: var(--ink-2); cursor: pointer;
|
||||||
|
}
|
||||||
|
.menu:hover { background: var(--hover); color: var(--ink); }
|
||||||
|
.menu svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
|
||||||
|
.menu path { transition: transform 0.3s var(--ease); transform-origin: 12px 12px; }
|
||||||
|
.menu[aria-expanded='true'] .l1 { transform: translateY(3px) rotate(45deg); }
|
||||||
|
.menu[aria-expanded='true'] .l2 { transform: translateY(-3px) rotate(-45deg); }
|
||||||
|
|
||||||
|
/* Small screens with JS: links fold behind the menu button. Without JS they simply wrap. */
|
||||||
|
@media (max-width: 759px) {
|
||||||
|
:global(.js) .menu { display: grid; }
|
||||||
|
:global(.js) .nav { display: none; flex-direction: column; padding: 0.25rem 0 0.75rem; }
|
||||||
|
:global(.js) .nav.open { display: flex; animation: drop 0.25s var(--ease); }
|
||||||
|
:global(.js) .nav a { padding: 0.75rem 0.6rem; font-size: 1.02rem; }
|
||||||
|
:global(.js) .nav a[aria-current='page']::after { left: auto; right: 0.8rem; bottom: 50%; margin-bottom: -2px; }
|
||||||
|
@keyframes drop { from { opacity: 0; transform: translateY(-6px); } }
|
||||||
|
}
|
||||||
|
@media (min-width: 760px) {
|
||||||
|
.pill { flex-wrap: nowrap; padding-left: 1.1rem; }
|
||||||
|
.brand { margin-right: 0; }
|
||||||
|
.nav { order: 0; width: auto; padding: 0; margin-inline: auto; }
|
||||||
|
:global(.js) .search { display: inline-flex; }
|
||||||
|
}
|
||||||
|
@media (min-width: 760px) and (max-width: 899px) { .s-label { display: none; } }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const btn = document.querySelector<HTMLButtonElement>('[data-menu]');
|
||||||
|
const nav = document.getElementById('site-nav');
|
||||||
|
const set = (open: boolean) => {
|
||||||
|
nav?.classList.toggle('open', open);
|
||||||
|
btn?.setAttribute('aria-expanded', String(open));
|
||||||
|
};
|
||||||
|
btn?.addEventListener('click', () => set(btn.getAttribute('aria-expanded') !== 'true'));
|
||||||
|
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') set(false); });
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
---
|
||||||
|
// Ctrl/⌘ K on the website, like in the app: jump to any page or section.
|
||||||
|
import { config } from '../config';
|
||||||
|
import { href } from '../lib/url';
|
||||||
|
|
||||||
|
interface Item { group: string; label: string; url: string; keys?: string }
|
||||||
|
const items: Item[] = [
|
||||||
|
{ group: 'Page', label: 'Home', url: href('/') },
|
||||||
|
{ group: 'Page', label: 'Features', url: href('/features/'), keys: 'tour' },
|
||||||
|
{ group: 'Page', label: 'Download', url: href('/download/'), keys: 'install get' },
|
||||||
|
{ group: 'Page', label: 'Changelog', url: href('/changelog/'), keys: 'release news versions' },
|
||||||
|
{ group: 'Page', label: 'Docs & FAQ', url: href('/docs/'), keys: 'help questions' },
|
||||||
|
{ group: 'Action', label: `Download Ink ${config.version} for Linux`, url: config.platforms.linux.url, keys: 'appimage linux get' },
|
||||||
|
{ group: 'Action', label: 'Verify the download (SHA-256)', url: href('/download/#verify'), keys: 'checksum sha256 hash' },
|
||||||
|
{ group: 'Action', label: 'Report a bug or request a feature', url: config.issues, keys: 'issue github' },
|
||||||
|
{ group: 'Features', label: 'Writing: block editor', url: href('/features/#writing'), keys: 'slash menu markdown code math katex mermaid callout table' },
|
||||||
|
{ group: 'Features', label: 'Linking: wiki links, embeds, backlinks', url: href('/features/#linking'), keys: '[[ ]] link rename' },
|
||||||
|
{ group: 'Features', label: 'Canvas', url: href('/features/#canvas'), keys: 'json canvas arrows groups export png svg pdf' },
|
||||||
|
{ group: 'Features', label: 'Kanban boards', url: href('/features/#boards'), keys: 'board columns cards' },
|
||||||
|
{ group: 'Features', label: 'Tags, search, graph, daily notes', url: href('/features/#organising'), keys: 'tag search palette graph daily templates favorites' },
|
||||||
|
{ group: 'Features', label: 'Version history, trash, export', url: href('/features/#safety'), keys: 'history restore trash export' },
|
||||||
|
{ group: 'Features', label: 'Themes, focus mode, spellcheck', url: href('/features/#comfort'), keys: 'dark light theme focus outline text size' },
|
||||||
|
{ group: 'Docs', label: 'Where are my notes stored?', url: href('/docs/#storage'), keys: 'folder files md .notes' },
|
||||||
|
{ group: 'Docs', label: 'Obsidian compatibility', url: href('/docs/#obsidian'), keys: 'vault obsidian canvas kanban' },
|
||||||
|
{ group: 'Docs', label: 'Keyboard shortcuts', url: href('/docs/#shortcuts'), keys: 'keys hotkeys ctrl' },
|
||||||
|
{ group: 'Docs', label: 'Privacy', url: href('/docs/#privacy'), keys: 'tracking analytics cookies data' },
|
||||||
|
];
|
||||||
|
---
|
||||||
|
<dialog class="palette" data-palette aria-label="Jump to a page or section">
|
||||||
|
<div class="p-head">
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="6.5" /><path d="M16 16l4 4" /></svg>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-controls="palette-list"
|
||||||
|
aria-autocomplete="list"
|
||||||
|
aria-label="Search pages and sections"
|
||||||
|
placeholder="Jump to…"
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
|
data-palette-input
|
||||||
|
/>
|
||||||
|
<kbd>Esc</kbd>
|
||||||
|
</div>
|
||||||
|
<ul id="palette-list" role="listbox" aria-label="Results" data-palette-list>
|
||||||
|
{items.map((it, i) => (
|
||||||
|
<li role="option" id={`palette-opt-${i}`} aria-selected="false" data-url={it.url} data-text={`${it.group} ${it.label} ${it.keys ?? ''}`.toLowerCase()}>
|
||||||
|
<span class="g">{it.group}</span>
|
||||||
|
<span class="l">{it.label}</span>
|
||||||
|
<span class="enter" aria-hidden="true">↵</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<p class="empty" hidden data-palette-empty>Nothing matches. Try “canvas”, “shortcuts” or “download”.</p>
|
||||||
|
<div class="p-foot" aria-hidden="true">
|
||||||
|
<span><kbd>↑</kbd><kbd>↓</kbd> move</span><span><kbd>↵</kbd> open</span><span><kbd>Esc</kbd> close</span>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.palette {
|
||||||
|
width: min(100% - 2rem, 600px); max-height: min(70vh, 560px); padding: 0; margin: 12vh auto auto;
|
||||||
|
border: 1px solid var(--line-2); border-radius: 16px; background: var(--surface); color: var(--ink);
|
||||||
|
box-shadow: var(--shadow); overflow: hidden; flex-direction: column;
|
||||||
|
}
|
||||||
|
.palette[open] { display: flex; animation: pop 0.22s var(--ease); }
|
||||||
|
.palette::backdrop { background: rgba(0, 0, 0, 0.45); backdrop-filter: blur(3px); animation: fade 0.22s var(--ease); }
|
||||||
|
@keyframes pop { from { opacity: 0; transform: translateY(-8px) scale(0.98); } }
|
||||||
|
@keyframes fade { from { opacity: 0; } }
|
||||||
|
.p-head { display: flex; align-items: center; gap: 0.7rem; padding: 0.95rem 1.1rem; border-bottom: 1px solid var(--line); }
|
||||||
|
.p-head svg { width: 18px; height: 18px; fill: none; stroke: var(--muted); stroke-width: 1.8; stroke-linecap: round; flex: none; }
|
||||||
|
input { flex: 1; min-width: 0; font: 400 1.05rem/1.4 var(--f-ui); color: var(--ink); background: none; border: 0; outline: none; }
|
||||||
|
input::placeholder { color: var(--muted); }
|
||||||
|
ul { list-style: none; margin: 0; padding: 0.4rem; overflow-y: auto; }
|
||||||
|
li {
|
||||||
|
display: flex; align-items: center; gap: 0.75rem; padding: 0.62rem 0.75rem; border-radius: 10px;
|
||||||
|
cursor: pointer; color: var(--ink-2);
|
||||||
|
}
|
||||||
|
li[hidden] { display: none; }
|
||||||
|
li[aria-selected='true'] { background: var(--blush-soft); color: var(--ink); }
|
||||||
|
.g { font: 500 0.7rem/1 var(--f-mono); text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); width: 4.8rem; flex: none; }
|
||||||
|
.l { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.enter { color: var(--accent-text); opacity: 0; font-size: 0.9rem; }
|
||||||
|
li[aria-selected='true'] .enter { opacity: 1; }
|
||||||
|
.empty { padding: 1.5rem 1.1rem; color: var(--muted); }
|
||||||
|
.p-foot { display: flex; gap: 1.1rem; padding: 0.6rem 1.1rem; border-top: 1px solid var(--line); font-size: 0.78rem; color: var(--muted); }
|
||||||
|
.p-foot kbd { font-size: 0.7rem; padding: 0.2rem 0.35rem; margin-right: 0.2rem; }
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.palette { margin-top: 1rem; }
|
||||||
|
.g { display: none; }
|
||||||
|
.p-foot { display: none; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const dlg = document.querySelector<HTMLDialogElement>('[data-palette]')!;
|
||||||
|
const input = dlg.querySelector<HTMLInputElement>('[data-palette-input]')!;
|
||||||
|
const opts = [...dlg.querySelectorAll<HTMLLIElement>('[role="option"]')];
|
||||||
|
const empty = dlg.querySelector<HTMLElement>('[data-palette-empty]')!;
|
||||||
|
let active = 0;
|
||||||
|
|
||||||
|
const visible = () => opts.filter((o) => !o.hidden);
|
||||||
|
function select(i: number) {
|
||||||
|
const list = visible();
|
||||||
|
if (!list.length) { input.removeAttribute('aria-activedescendant'); return; }
|
||||||
|
active = (i + list.length) % list.length;
|
||||||
|
opts.forEach((o) => o.setAttribute('aria-selected', 'false'));
|
||||||
|
const el = list[active];
|
||||||
|
el.setAttribute('aria-selected', 'true');
|
||||||
|
input.setAttribute('aria-activedescendant', el.id);
|
||||||
|
el.scrollIntoView({ block: 'nearest' });
|
||||||
|
}
|
||||||
|
function filter() {
|
||||||
|
const words = input.value.toLowerCase().split(/\s+/).filter(Boolean);
|
||||||
|
for (const o of opts) o.hidden = !words.every((w) => o.dataset.text!.includes(w));
|
||||||
|
empty.hidden = visible().length > 0;
|
||||||
|
select(0);
|
||||||
|
}
|
||||||
|
function go(o: HTMLElement | undefined) {
|
||||||
|
if (!o) return;
|
||||||
|
dlg.close();
|
||||||
|
location.href = o.dataset.url!;
|
||||||
|
}
|
||||||
|
function open() {
|
||||||
|
if (dlg.open) return;
|
||||||
|
input.value = '';
|
||||||
|
filter();
|
||||||
|
dlg.showModal();
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
input.addEventListener('input', filter);
|
||||||
|
input.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'ArrowDown') { e.preventDefault(); select(active + 1); }
|
||||||
|
else if (e.key === 'ArrowUp') { e.preventDefault(); select(active - 1); }
|
||||||
|
else if (e.key === 'Enter') { e.preventDefault(); go(visible()[active]); }
|
||||||
|
});
|
||||||
|
opts.forEach((o) => {
|
||||||
|
o.addEventListener('click', () => go(o));
|
||||||
|
o.addEventListener('pointermove', () => { const i = visible().indexOf(o); if (i !== active) select(i); });
|
||||||
|
});
|
||||||
|
// Click on the backdrop closes.
|
||||||
|
dlg.addEventListener('click', (e) => { if (e.target === dlg) dlg.close(); });
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
|
||||||
|
e.preventDefault();
|
||||||
|
dlg.open ? dlg.close() : open();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.querySelectorAll('[data-palette-open]').forEach((b) => b.addEventListener('click', open));
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
import { getImage } from 'astro:assets';
|
||||||
|
import type { ImageMetadata } from 'astro';
|
||||||
|
import { screens, type ScreenName } from '../lib/screens';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
name: ScreenName;
|
||||||
|
/** Above the fold: load immediately with high priority. */
|
||||||
|
priority?: boolean;
|
||||||
|
sizes?: string;
|
||||||
|
/** Override the default alt text. */
|
||||||
|
alt?: string;
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
const { name, priority = false, sizes = '(min-width: 1160px) 1120px, 100vw', alt, class: className } = Astro.props;
|
||||||
|
const spec = screens[name];
|
||||||
|
|
||||||
|
const files = import.meta.glob<{ default: ImageMetadata }>('/src/assets/screens/*.{png,jpg,jpeg,webp,svg}', { eager: true });
|
||||||
|
const raster = ['png', 'webp', 'jpg', 'jpeg'];
|
||||||
|
|
||||||
|
/** Real captures (png/webp/jpg) beat placeholder SVGs; `<name>-<theme>` beats a bare `<name>`. */
|
||||||
|
function pick(theme: 'dark' | 'light'): ImageMetadata {
|
||||||
|
const tries = [
|
||||||
|
...raster.map((e) => `${name}-${theme}.${e}`),
|
||||||
|
...raster.map((e) => `${name}.${e}`),
|
||||||
|
`${name}-${theme}.svg`,
|
||||||
|
`${name}.svg`,
|
||||||
|
];
|
||||||
|
for (const t of tries) {
|
||||||
|
const hit = files[`/src/assets/screens/${t}`];
|
||||||
|
if (hit) return hit.default;
|
||||||
|
}
|
||||||
|
throw new Error(`No screenshot found for "${name}" (${theme}) in src/assets/screens/`);
|
||||||
|
}
|
||||||
|
/** Optimised webp srcset for real captures; SVG placeholders pass through untouched. */
|
||||||
|
async function sources(img: ImageMetadata) {
|
||||||
|
if (img.format === 'svg') return { src: img.src, srcset: img.src };
|
||||||
|
const widths = [720, 1200, 1600, 2400].filter((w) => w < img.width).concat(img.width);
|
||||||
|
const out = await getImage({ src: img, widths, format: 'webp' });
|
||||||
|
return { src: out.src, srcset: out.srcSet.attribute };
|
||||||
|
}
|
||||||
|
const darkImg = pick('dark');
|
||||||
|
const dark = await sources(darkImg);
|
||||||
|
const light = await sources(pick('light'));
|
||||||
|
---
|
||||||
|
<figure class:list={['shot', className]}>
|
||||||
|
<div class="view">
|
||||||
|
<!-- The theme follows the system, so the browser fetches only the matching variant. -->
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset={light.srcset} sizes={sizes} />
|
||||||
|
<img
|
||||||
|
src={dark.src}
|
||||||
|
srcset={dark.srcset}
|
||||||
|
sizes={sizes}
|
||||||
|
width={darkImg.width}
|
||||||
|
height={darkImg.height}
|
||||||
|
alt={alt ?? spec.alt}
|
||||||
|
loading={priority ? 'eager' : 'lazy'}
|
||||||
|
fetchpriority={priority ? 'high' : undefined}
|
||||||
|
decoding={priority ? 'sync' : 'async'}
|
||||||
|
/>
|
||||||
|
</picture>
|
||||||
|
</div>
|
||||||
|
</figure>
|
||||||
|
<style>
|
||||||
|
.shot {
|
||||||
|
margin: 0; border-radius: 14px; overflow: hidden; background: var(--surface);
|
||||||
|
border: 1px solid var(--line-2); box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
.view { aspect-ratio: 16 / 10; background: var(--bg); }
|
||||||
|
.view picture { display: block; width: 100%; height: 100%; }
|
||||||
|
.view img { width: 100%; height: 100%; object-fit: cover; }
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.shot { border-radius: 10px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
// One big frame, five tabs. Auto-advances (with a progress bar) until you pick a tab yourself.
|
||||||
|
// Without JS: every panel is shown, stacked.
|
||||||
|
import Screenshot from './Screenshot.astro';
|
||||||
|
import type { ScreenName } from '../lib/screens';
|
||||||
|
|
||||||
|
interface Tab { id: string; label: string; screen: ScreenName; title: string; text: string }
|
||||||
|
const tabs: Tab[] = [
|
||||||
|
{ id: 'write', label: 'Write', screen: 'slash-menu', title: 'Blocks, saved as Markdown', text: 'Slash menu, formatting bar, drag to reorder. Every block is ordinary Markdown on disk.' },
|
||||||
|
{ id: 'link', label: 'Link', screen: 'linking', title: 'Links that survive renames', text: '[[Wiki links]], heading links, embeds and backlinks. Rename a page and the links follow.' },
|
||||||
|
{ id: 'canvas', label: 'Canvas', screen: 'canvas', title: 'Think in space', text: 'Cards, note previews, images, link cards, groups and arrows. Same .canvas format as Obsidian.' },
|
||||||
|
{ id: 'graph', label: 'Graph', screen: 'graph', title: 'See the shape of your notes', text: 'Every note and link, coloured by tag, with filters and force settings.' },
|
||||||
|
{ id: 'board', label: 'Board', screen: 'board', title: 'Kanban from a page', text: 'Headings become columns, to-dos become cards. Still a Markdown file.' },
|
||||||
|
];
|
||||||
|
---
|
||||||
|
<div class="showcase" data-showcase>
|
||||||
|
<div class="tabs" role="tablist" aria-label="Ink in use">
|
||||||
|
{tabs.map((t, i) => (
|
||||||
|
<button
|
||||||
|
type="button" role="tab" id={`sc-tab-${t.id}`} aria-controls={`sc-panel-${t.id}`}
|
||||||
|
aria-selected={i === 0 ? 'true' : 'false'} tabindex={i === 0 ? 0 : -1}
|
||||||
|
>
|
||||||
|
{t.label}<span class="bar" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div class="panels">
|
||||||
|
{tabs.map((t, i) => (
|
||||||
|
<div class:list={['panel', { active: i === 0 }]} role="tabpanel" id={`sc-panel-${t.id}`} aria-labelledby={`sc-tab-${t.id}`}>
|
||||||
|
<div class="cap">
|
||||||
|
<h3>{t.title}</h3>
|
||||||
|
<p>{t.text}</p>
|
||||||
|
</div>
|
||||||
|
<Screenshot name={t.screen} sizes="(min-width: 1160px) 1000px, 100vw" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.showcase { display: grid; gap: 1.5rem; }
|
||||||
|
.tabs {
|
||||||
|
position: relative; display: none; justify-self: center; gap: 0.25rem; padding: 0.3rem; border-radius: 14px;
|
||||||
|
background: var(--surface); border: 1px solid var(--line); max-width: 100%; overflow-x: auto; scrollbar-width: none;
|
||||||
|
}
|
||||||
|
:global(.js) .tabs { display: flex; }
|
||||||
|
[role='tab'] {
|
||||||
|
position: relative; overflow: hidden; padding: 0.55rem 1.05rem; border-radius: 10px; border: 0; background: none;
|
||||||
|
color: var(--ink-2); font: 500 0.93rem/1 var(--f-ui); cursor: pointer; white-space: nowrap;
|
||||||
|
transition: color 0.2s, background-color 0.25s;
|
||||||
|
}
|
||||||
|
[role='tab']:hover { color: var(--ink); }
|
||||||
|
[role='tab'][aria-selected='true'] { color: var(--ink); background: var(--blush-soft); }
|
||||||
|
.bar { position: absolute; left: 0; bottom: 0; height: 2px; width: 100%; background: var(--blush); transform: scaleX(0); transform-origin: left; }
|
||||||
|
.panels { display: grid; gap: 3rem; }
|
||||||
|
.cap { display: grid; gap: 0.35rem; text-align: center; margin-bottom: 1.25rem; }
|
||||||
|
.cap h3 { font-size: 1.2rem; }
|
||||||
|
.cap p { color: var(--ink-2); max-width: 56ch; margin-inline: auto; }
|
||||||
|
|
||||||
|
/* With JS the panels stack in one cell and crossfade. */
|
||||||
|
:global(.js) .panels { gap: 0; }
|
||||||
|
:global(.js) .panel { grid-area: 1 / 1; transition: opacity 0.5s var(--ease), transform 0.5s var(--ease), visibility 0.5s; }
|
||||||
|
:global(.js) .panel:not(.active) { opacity: 0; visibility: hidden; transform: translateY(8px) scale(0.995); }
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
:global(.js) .showcase.auto [aria-selected='true'] .bar { animation: progress 7s linear forwards; }
|
||||||
|
.showcase.auto.paused [aria-selected='true'] .bar { animation-play-state: paused; }
|
||||||
|
@keyframes progress { to { transform: scaleX(1); } }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.querySelectorAll<HTMLElement>('[data-showcase]').forEach((root) => {
|
||||||
|
const tabs = [...root.querySelectorAll<HTMLButtonElement>('[role="tab"]')];
|
||||||
|
const panels = [...root.querySelectorAll<HTMLElement>('[role="tabpanel"]')];
|
||||||
|
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
|
||||||
|
function show(i: number, focus = false) {
|
||||||
|
tabs.forEach((t, j) => {
|
||||||
|
t.setAttribute('aria-selected', String(i === j));
|
||||||
|
t.tabIndex = i === j ? 0 : -1;
|
||||||
|
});
|
||||||
|
panels.forEach((p, j) => p.classList.toggle('active', i === j));
|
||||||
|
if (focus) tabs[i].focus();
|
||||||
|
// Horizontal only: never move the page.
|
||||||
|
const bar = tabs[i].parentElement!;
|
||||||
|
bar.scrollTo({ left: tabs[i].offsetLeft - (bar.clientWidth - tabs[i].clientWidth) / 2, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
const current = () => tabs.findIndex((t) => t.getAttribute('aria-selected') === 'true');
|
||||||
|
const manual = () => root.classList.remove('auto');
|
||||||
|
|
||||||
|
tabs.forEach((t, i) => {
|
||||||
|
t.addEventListener('click', () => { manual(); show(i); });
|
||||||
|
t.addEventListener('keydown', (e) => {
|
||||||
|
const k = e.key;
|
||||||
|
if (!['ArrowRight', 'ArrowLeft', 'Home', 'End'].includes(k)) return;
|
||||||
|
e.preventDefault();
|
||||||
|
manual();
|
||||||
|
const n = tabs.length;
|
||||||
|
show(k === 'Home' ? 0 : k === 'End' ? n - 1 : (i + (k === 'ArrowRight' ? 1 : -1) + n) % n, true);
|
||||||
|
});
|
||||||
|
// The progress bar finishing is the auto-advance tick.
|
||||||
|
t.querySelector('.bar')?.addEventListener('animationend', () => {
|
||||||
|
if (root.classList.contains('auto')) show((current() + 1) % tabs.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!reduce) {
|
||||||
|
root.classList.add('auto');
|
||||||
|
// Pause while hovered/focused or off screen.
|
||||||
|
const pause = (on: boolean) => root.classList.toggle('paused', on);
|
||||||
|
root.addEventListener('pointerenter', () => pause(true));
|
||||||
|
root.addEventListener('pointerleave', () => pause(false));
|
||||||
|
root.addEventListener('focusin', () => pause(true));
|
||||||
|
root.addEventListener('focusout', () => pause(false));
|
||||||
|
new IntersectionObserver(([e]) => pause(!e.isIntersecting), { threshold: 0.3 }).observe(root);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
// A slow, edge-faded row of real feature names. Pauses on hover; static under reduced motion.
|
||||||
|
const items = [
|
||||||
|
'Slash menu', 'Markdown shortcuts', '[[Wiki links]]', 'Backlinks', '![[Embeds]]', 'Canvas', 'JSON Canvas',
|
||||||
|
'Kanban boards', 'Graph view', 'Coloured tags', 'Full-text search', 'Command palette', 'KaTeX math',
|
||||||
|
'Mermaid diagrams', 'Callouts', 'Toggles', 'Footnotes', 'Daily notes', 'Version history', 'Templates',
|
||||||
|
'Focus mode', 'Outline', 'Export to PDF', 'Spellcheck',
|
||||||
|
];
|
||||||
|
---
|
||||||
|
<div class="ticker" role="region" aria-label="Some of what Ink does">
|
||||||
|
<ul class="track">{items.map((i) => <li>{i}</li>)}</ul>
|
||||||
|
<ul class="track dup" aria-hidden="true">{items.map((i) => <li>{i}</li>)}</ul>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.ticker {
|
||||||
|
display: flex; overflow: hidden; gap: 0.6rem; padding-block: 0.25rem;
|
||||||
|
-webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
|
||||||
|
mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
|
||||||
|
}
|
||||||
|
.track { display: flex; gap: 0.6rem; margin: 0; padding: 0; list-style: none; flex: none; }
|
||||||
|
li {
|
||||||
|
padding: 0.45rem 0.9rem; border-radius: 99px; border: 1px solid var(--line); background: var(--surface);
|
||||||
|
color: var(--ink-2); font-size: 0.88rem; white-space: nowrap;
|
||||||
|
}
|
||||||
|
li:nth-child(4n + 1) { color: var(--accent-text); border-color: var(--blush-line); background: var(--blush-soft); }
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.track { animation: slide 70s linear infinite; }
|
||||||
|
.ticker:hover .track { animation-play-state: paused; }
|
||||||
|
@keyframes slide { to { transform: translateX(calc(-100% - 0.6rem)); } }
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.ticker { flex-wrap: wrap; justify-content: center; -webkit-mask-image: none; mask-image: none; }
|
||||||
|
.track { flex: 1 1 100%; flex-wrap: wrap; justify-content: center; }
|
||||||
|
.dup { display: none; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
// The app's nib mark (from resources/icon.svg) next to the name. Inline so it themes with the page.
|
||||||
|
interface Props { size?: number; text?: boolean }
|
||||||
|
const { size = 28, text = true } = Astro.props;
|
||||||
|
---
|
||||||
|
<span class="wordmark">
|
||||||
|
<svg viewBox="320 256 384 560" width={size * 0.7} height={size} aria-hidden="true" focusable="false">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="wm-nib" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#f7d3c9" />
|
||||||
|
<stop offset="0.6" stop-color="var(--blush)" />
|
||||||
|
<stop offset="1" stop-color="#c97a6d" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path d="M330 340 C330 296 362 266 406 266 H618 C662 266 694 296 694 340 C694 448 646 528 598 600 C560 656 530 724 512 806 C494 724 464 656 426 600 C378 528 330 448 330 340 Z" fill="url(#wm-nib)" />
|
||||||
|
<path d="M512 520 V800" stroke="var(--bg)" stroke-width="22" stroke-linecap="round" />
|
||||||
|
<circle cx="512" cy="500" r="34" fill="var(--bg)" />
|
||||||
|
</svg>
|
||||||
|
{text && <span class="wm-text">Ink</span>}
|
||||||
|
</span>
|
||||||
|
<style>
|
||||||
|
.wordmark { display: inline-flex; align-items: center; gap: 0.55rem; }
|
||||||
|
.wm-text { font-weight: 650; font-size: 1.25rem; letter-spacing: -0.03em; color: var(--ink); }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/** Single source of truth for everything release-specific. Edit here, nowhere else. */
|
||||||
|
|
||||||
|
const version = '0.1.0';
|
||||||
|
|
||||||
|
/** TODO: set to the real repository once it is public. Release links derive from it. */
|
||||||
|
const repo = 'https://github.com/OWNER/ink';
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
name: 'Ink',
|
||||||
|
tagline: 'A calm, offline Markdown notes app.',
|
||||||
|
alt: 'Your notes. Plain files. Yours.',
|
||||||
|
description:
|
||||||
|
'Ink is a calm, offline-first Markdown notes app for the desktop. Notes are plain .md files in a folder you choose. No account, nothing uploaded.',
|
||||||
|
version,
|
||||||
|
releaseDate: '2026-10-11',
|
||||||
|
repo,
|
||||||
|
issues: `${repo}/issues`,
|
||||||
|
releases: `${repo}/releases`,
|
||||||
|
license: 'TODO',
|
||||||
|
platforms: {
|
||||||
|
linux: {
|
||||||
|
label: 'Linux',
|
||||||
|
format: 'AppImage',
|
||||||
|
available: true,
|
||||||
|
arch: 'x86_64',
|
||||||
|
file: `Ink-${version}-x86_64.AppImage`,
|
||||||
|
url: `${repo}/releases/download/v${version}/Ink-${version}-x86_64.AppImage`,
|
||||||
|
bytes: 164_943_435,
|
||||||
|
sha256: '0d35758636f6cb7f5eb1c439a75ebd47ae79d31fc7bf91860d26d9eaef06ef3e',
|
||||||
|
},
|
||||||
|
windows: {
|
||||||
|
label: 'Windows',
|
||||||
|
format: 'NSIS installer',
|
||||||
|
available: false,
|
||||||
|
arch: 'x64',
|
||||||
|
file: `Ink-${version}-x64.exe`,
|
||||||
|
url: '',
|
||||||
|
bytes: 0,
|
||||||
|
sha256: '',
|
||||||
|
},
|
||||||
|
macos: {
|
||||||
|
label: 'macOS',
|
||||||
|
format: 'dmg',
|
||||||
|
available: false,
|
||||||
|
arch: '',
|
||||||
|
file: `Ink-${version}.dmg`,
|
||||||
|
url: '',
|
||||||
|
bytes: 0,
|
||||||
|
sha256: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type PlatformId = keyof typeof config.platforms;
|
||||||
|
|
||||||
|
/** "157 MB" (binary megabytes, which is what file managers show). */
|
||||||
|
export function formatSize(bytes: number): string {
|
||||||
|
return `${Math.round(bytes / 1024 / 1024)} MB`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { defineCollection } from 'astro:content';
|
||||||
|
import { glob } from 'astro/loaders';
|
||||||
|
import { z } from 'astro/zod';
|
||||||
|
|
||||||
|
const changelog = defineCollection({
|
||||||
|
loader: glob({ pattern: '*.md', base: './src/content/changelog' }),
|
||||||
|
schema: z.object({
|
||||||
|
version: z.string(),
|
||||||
|
date: z.coerce.date(),
|
||||||
|
title: z.string(),
|
||||||
|
/** Marks early preview builds. */
|
||||||
|
prerelease: z.boolean().default(false),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const collections = { changelog };
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
version: '0.0.1'
|
||||||
|
date: 2026-08-02
|
||||||
|
title: The editor
|
||||||
|
prerelease: true
|
||||||
|
---
|
||||||
|
- Block editor with slash menu and Markdown shortcuts.
|
||||||
|
- Notes stored as plain `.md` files in a folder you choose.
|
||||||
|
- Dark and light themes.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
version: '0.0.2'
|
||||||
|
date: 2026-08-30
|
||||||
|
title: Linking and search
|
||||||
|
prerelease: true
|
||||||
|
---
|
||||||
|
- Heading links (`[[Page#Heading]]`) and embeds.
|
||||||
|
- Renaming a page or heading rewrites the links to it.
|
||||||
|
- Tag colours, the `tag:` and `in:` search operators, and the command palette.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
version: '0.0.3'
|
||||||
|
date: 2026-09-20
|
||||||
|
title: Canvas polish
|
||||||
|
prerelease: true
|
||||||
|
---
|
||||||
|
- Alignment guides, align and distribute, and find-in-canvas.
|
||||||
|
- Groups can be folded away.
|
||||||
|
- Arrow styles (curved, straight, right-angled) and arrowheads.
|
||||||
|
- Version history for canvases.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
version: '0.1.0'
|
||||||
|
date: 2026-10-11
|
||||||
|
title: First public release
|
||||||
|
---
|
||||||
|
The first build of Ink you can download: a Linux AppImage.
|
||||||
|
|
||||||
|
### Writing
|
||||||
|
- Block editor with a slash menu, floating formatting bar, Markdown shortcuts and drag-to-reorder.
|
||||||
|
- Code with syntax highlighting, KaTeX math, Mermaid diagrams, callouts, toggles, tables, footnotes.
|
||||||
|
|
||||||
|
### Linking
|
||||||
|
- `[[Wiki links]]`, `[[Page#Heading]]` links, `![[embeds]]` and a backlinks panel.
|
||||||
|
- Renaming a page or heading updates the links that point to it.
|
||||||
|
|
||||||
|
### Canvas and boards
|
||||||
|
- Canvas in the JSON Canvas format, with export to PNG, SVG and PDF.
|
||||||
|
- Kanban boards built from a page.
|
||||||
|
|
||||||
|
### Everything else
|
||||||
|
- Graph view, tags with colours, full-text search and a command palette.
|
||||||
|
- Daily notes, version history, trash with restore, templates, export to Markdown, HTML and PDF.
|
||||||
|
- Dark, light and system themes; focus mode, outline panel and spellcheck.
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
import '../styles/global.css';
|
||||||
|
import interLatin from '@fontsource-variable/inter/files/inter-latin-wght-normal.woff2?url';
|
||||||
|
import Header from '../components/Header.astro';
|
||||||
|
import Footer from '../components/Footer.astro';
|
||||||
|
import Palette from '../components/Palette.astro';
|
||||||
|
import { config } from '../config';
|
||||||
|
import { href } from '../lib/url';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
/** Path used for the nav's current-page marker, e.g. "/features/". */
|
||||||
|
current?: string;
|
||||||
|
noindex?: boolean;
|
||||||
|
}
|
||||||
|
const { title, description = config.description, current = '', noindex = false } = Astro.props;
|
||||||
|
const fullTitle = title ? `${title} · ${config.name}` : `${config.name}: ${config.tagline}`;
|
||||||
|
const canonical = new URL(Astro.url.pathname, Astro.site);
|
||||||
|
const ogImage = new URL(href('/og.png'), Astro.site);
|
||||||
|
---
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>{fullTitle}</title>
|
||||||
|
<meta name="description" content={description} />
|
||||||
|
<link rel="canonical" href={canonical} />
|
||||||
|
{noindex && <meta name="robots" content="noindex" />}
|
||||||
|
<meta name="theme-color" content="#0e0e0f" media="(prefers-color-scheme: dark)" />
|
||||||
|
<meta name="theme-color" content="#f5f5f6" media="(prefers-color-scheme: light)" />
|
||||||
|
|
||||||
|
<link rel="icon" href={href('/favicon.svg')} type="image/svg+xml" />
|
||||||
|
<link rel="icon" href={href('/favicon-32.png')} type="image/png" sizes="32x32" />
|
||||||
|
<link rel="icon" href={href('/favicon.ico')} sizes="48x48" />
|
||||||
|
<link rel="apple-touch-icon" href={href('/apple-touch-icon.png')} />
|
||||||
|
<link rel="manifest" href={href('/site.webmanifest')} />
|
||||||
|
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:site_name" content={config.name} />
|
||||||
|
<meta property="og:title" content={fullTitle} />
|
||||||
|
<meta property="og:description" content={description} />
|
||||||
|
<meta property="og:url" content={canonical} />
|
||||||
|
<meta property="og:image" content={ogImage} />
|
||||||
|
<meta property="og:image:width" content="1200" />
|
||||||
|
<meta property="og:image:height" content="630" />
|
||||||
|
<meta property="og:image:alt" content="Ink: a calm, offline Markdown notes app" />
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:title" content={fullTitle} />
|
||||||
|
<meta name="twitter:description" content={description} />
|
||||||
|
<meta name="twitter:image" content={ogImage} />
|
||||||
|
|
||||||
|
<link rel="preload" as="font" type="font/woff2" href={interLatin} crossorigin />
|
||||||
|
<slot name="head" />
|
||||||
|
|
||||||
|
<script is:inline>document.documentElement.classList.add('js');</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a class="skip" href="#main">Skip to content</a>
|
||||||
|
<Header current={current} />
|
||||||
|
<main id="main"><slot /></main>
|
||||||
|
<Footer />
|
||||||
|
<Palette />
|
||||||
|
<script>import '../scripts/site.ts';</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export type OS = 'linux' | 'windows' | 'macos' | 'mobile' | 'other';
|
||||||
|
|
||||||
|
/** Best guess at the visitor's OS. Only used to tailor labels; every page works without it. */
|
||||||
|
export function detectOS(): OS {
|
||||||
|
type Nav = Navigator & { userAgentData?: { platform?: string; mobile?: boolean } };
|
||||||
|
const nav = navigator as Nav;
|
||||||
|
const ua = navigator.userAgent;
|
||||||
|
const plat = (nav.userAgentData?.platform || navigator.platform || '').toLowerCase();
|
||||||
|
if (/android|iphone|ipad|ipod/i.test(ua) || nav.userAgentData?.mobile) return 'mobile';
|
||||||
|
if (plat.includes('win') || /windows/i.test(ua)) return 'windows';
|
||||||
|
if (plat.includes('mac') || /macintosh/i.test(ua)) return 'macos';
|
||||||
|
if (plat.includes('linux') || /linux|x11|cros/i.test(ua)) return 'linux';
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Every screenshot the site uses. Drop real captures into src/assets/screens/ as
|
||||||
|
* `<name>-dark.png` and `<name>-light.png` (16:10, ideally 2400x1500) and they replace
|
||||||
|
* the placeholder SVGs automatically. A bare `<name>.png` is used for both themes.
|
||||||
|
*/
|
||||||
|
export interface ScreenSpec {
|
||||||
|
alt: string;
|
||||||
|
/** What to have on screen when capturing. Also drawn on the placeholder. */
|
||||||
|
capture: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const screens = {
|
||||||
|
editor: {
|
||||||
|
alt: 'Ink showing a note with a heading, a to-do list, a code block and a callout, with the page tree in the sidebar.',
|
||||||
|
capture:
|
||||||
|
'A well-filled page: emoji icon + title, a paragraph, a to-do list with some ticked, a code block with highlighting, a callout. Sidebar expanded with a few pages and favourites.',
|
||||||
|
},
|
||||||
|
'slash-menu': {
|
||||||
|
alt: 'The slash menu open in a note, listing block types such as headings, lists, tables, code, callouts and math.',
|
||||||
|
capture: 'Type "/" mid-page so the block menu is open and filtered to the full list. Selection highlight on one item.',
|
||||||
|
},
|
||||||
|
linking: {
|
||||||
|
alt: 'A note embedding another note inline with ![[…]] and showing a backlinks panel at the bottom.',
|
||||||
|
capture:
|
||||||
|
'A page with a [[wiki link]], a [[Page#Heading]] link and an ![[embed]] showing another note inline. Backlinks panel visible at the bottom with 2-3 entries.',
|
||||||
|
},
|
||||||
|
canvas: {
|
||||||
|
alt: 'An Ink canvas with Markdown cards, a note preview, a web link card and a group, joined by arrows.',
|
||||||
|
capture:
|
||||||
|
'A canvas with ~8 elements: Markdown cards, a live note preview, a web link card (title + description), one image, and a labelled group containing 2-3 cards. A few arrows (curved and straight). Alignment guides visible mid-drag if possible.',
|
||||||
|
},
|
||||||
|
graph: {
|
||||||
|
alt: 'The graph view showing notes as dots joined by link lines, coloured by tag.',
|
||||||
|
capture: 'Graph view of a vault with 30-60 notes, 3-4 tag colours clearly different, settings gear panel open with filters.',
|
||||||
|
},
|
||||||
|
board: {
|
||||||
|
alt: 'A kanban board built from a page, with To do, Doing and Done columns of cards.',
|
||||||
|
capture: 'A board page with 3-4 columns and 3-5 cards each, one card mid-drag if possible.',
|
||||||
|
},
|
||||||
|
palette: {
|
||||||
|
alt: 'The command palette open with a search for a tag, listing matching notes with snippets.',
|
||||||
|
capture: 'Ctrl+K palette with a query like "tag:work roadmap" typed, 4-6 results with snippets, page dimmed behind it.',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
alt: 'The settings dialog with theme, text size, page width, sidebar layout, notes folder, daily notes and canvas options.',
|
||||||
|
capture: 'Settings dialog (Ctrl+,) with theme, text size, page width, notes folder and spellcheck language sections visible.',
|
||||||
|
},
|
||||||
|
history: {
|
||||||
|
alt: 'The version history dialog listing earlier saved versions of a page, each with a Restore button.',
|
||||||
|
capture: 'Version history dialog for a page with several versions listed and a preview/diff of one selected.',
|
||||||
|
},
|
||||||
|
} satisfies Record<string, ScreenSpec>;
|
||||||
|
|
||||||
|
export type ScreenName = keyof typeof screens;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/** Prefix an internal path with the configured base ("/features/" -> "/ink-website/features/"). */
|
||||||
|
export function href(path = '/'): string {
|
||||||
|
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
|
||||||
|
return `${base}${path.startsWith('/') ? path : `/${path}`}`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
import Base from '../layouts/Base.astro';
|
||||||
|
import { href } from '../lib/url';
|
||||||
|
---
|
||||||
|
<Base title="Page not found" noindex description="This page does not exist.">
|
||||||
|
<section class="narrow nf">
|
||||||
|
<p class="eyebrow">404</p>
|
||||||
|
<h1>This page is blank.</h1>
|
||||||
|
<p class="lede">The page you were after is not here. It may have moved, or the link may be mistyped.</p>
|
||||||
|
<p class="links"><a class="btn primary" href={href('/')}>Back to home</a><a class="btn" href={href('/download/')}>Download</a></p>
|
||||||
|
</section>
|
||||||
|
</Base>
|
||||||
|
<style>
|
||||||
|
.nf { padding-block: clamp(4rem, 12vw, 9rem); display: grid; gap: 1.2rem; justify-items: start; }
|
||||||
|
.links { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-top: 0.5rem; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
import { getCollection, render } from 'astro:content';
|
||||||
|
import Base from '../layouts/Base.astro';
|
||||||
|
import { config } from '../config';
|
||||||
|
|
||||||
|
const entries = (await getCollection('changelog')).sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
|
||||||
|
if (entries[0]?.data.version !== config.version) {
|
||||||
|
console.warn(`[changelog] newest entry is ${entries[0]?.data.version}, but config.version is ${config.version}`);
|
||||||
|
}
|
||||||
|
const rendered = await Promise.all(entries.map(async (e) => ({ entry: e, Content: (await render(e)).Content })));
|
||||||
|
const fmt = new Intl.DateTimeFormat('en-GB', { dateStyle: 'long', timeZone: 'UTC' });
|
||||||
|
---
|
||||||
|
<Base title="Changelog" current="/changelog/" description="What changed in each version of Ink, newest first.">
|
||||||
|
<section class="wrap head">
|
||||||
|
<p class="eyebrow">Changelog</p>
|
||||||
|
<h1>What changed</h1>
|
||||||
|
<p class="lede">Newest first. Current version: {config.version}.</p>
|
||||||
|
</section>
|
||||||
|
<ol class="narrow timeline">
|
||||||
|
{rendered.map(({ entry, Content }, i) => (
|
||||||
|
<li class:list={['entry', 'reveal', { latest: i === 0 }]} id={`v${entry.data.version}`}>
|
||||||
|
<span class="node" aria-hidden="true"></span>
|
||||||
|
<header>
|
||||||
|
<h2><a href={`#v${entry.data.version}`}>{entry.data.version}</a></h2>
|
||||||
|
{i === 0 && <span class="tag new">Latest</span>}
|
||||||
|
{entry.data.prerelease && <span class="tag">Preview</span>}
|
||||||
|
<time datetime={entry.data.date.toISOString().slice(0, 10)}>{fmt.format(entry.data.date)}</time>
|
||||||
|
</header>
|
||||||
|
<div class="body card">
|
||||||
|
<p class="etitle">{entry.data.title}</p>
|
||||||
|
<div class="prose"><Content /></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</Base>
|
||||||
|
<style>
|
||||||
|
.head { padding-top: clamp(3rem, 7vw, 5.5rem); display: grid; gap: 1.1rem; }
|
||||||
|
.timeline { list-style: none; position: relative; padding: 3rem 0 clamp(3rem, 7vw, 6rem) 2rem; margin-block: 0; }
|
||||||
|
.timeline::before {
|
||||||
|
content: ''; position: absolute; left: 6px; top: 3.4rem; bottom: 6rem; width: 1px;
|
||||||
|
background: linear-gradient(to bottom, var(--blush-line), var(--line) 30%, var(--line) 80%, transparent);
|
||||||
|
}
|
||||||
|
.entry { position: relative; padding-bottom: 2.5rem; }
|
||||||
|
.node {
|
||||||
|
position: absolute; left: calc(-2rem + 1px); top: 0.55rem; width: 11px; height: 11px; border-radius: 50%;
|
||||||
|
background: var(--bg); border: 2px solid var(--line-2);
|
||||||
|
}
|
||||||
|
.latest .node { border-color: var(--blush); background: var(--blush); box-shadow: 0 0 0 5px var(--blush-soft); }
|
||||||
|
header { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; margin-bottom: 0.9rem; }
|
||||||
|
h2 { font-family: var(--f-mono); font-size: 1.35rem; letter-spacing: -0.02em; }
|
||||||
|
h2 a { color: var(--ink); text-decoration: none; }
|
||||||
|
h2 a:hover { color: var(--accent-text); }
|
||||||
|
time { color: var(--muted); font-size: 0.9rem; margin-left: auto; }
|
||||||
|
.tag { font: 500 0.7rem/1 var(--f-mono); padding: 0.32rem 0.55rem; border-radius: 99px; border: 1px solid var(--line-2); color: var(--ink-2); }
|
||||||
|
.tag.new { color: var(--accent-text); border-color: var(--blush-line); background: var(--blush-soft); }
|
||||||
|
.body { padding: 1.3rem 1.5rem; border-radius: 16px; }
|
||||||
|
.latest .body { border-color: var(--blush-line); }
|
||||||
|
.etitle { font-weight: 600; font-size: 1.05rem; }
|
||||||
|
.prose :global(h3) { font-size: 0.75rem; font-family: var(--f-mono); text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent-text); margin-top: 1.3rem; }
|
||||||
|
.prose :global(ul) { margin-top: 0.5rem; }
|
||||||
|
.prose :global(p) { margin-top: 0.5rem; color: var(--ink-2); }
|
||||||
|
.prose :global(li) { color: var(--ink-2); }
|
||||||
|
@media (max-width: 520px) { time { margin-left: 0; width: 100%; } }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
import Base from '../layouts/Base.astro';
|
||||||
|
import { config } from '../config';
|
||||||
|
import { href } from '../lib/url';
|
||||||
|
|
||||||
|
const shortcuts: [string, string][] = [
|
||||||
|
['Ctrl + K', 'Command palette: search notes and run commands'],
|
||||||
|
['Ctrl + N', 'New page (blank or from a template)'],
|
||||||
|
['Ctrl + Shift + D', "Open or create today's daily note"],
|
||||||
|
['Ctrl + G', 'Graph view (Esc to go back)'],
|
||||||
|
['Ctrl + ,', 'Settings'],
|
||||||
|
['Ctrl + F', 'Find in the page or canvas'],
|
||||||
|
['Shift + 1', 'Canvas: show everything'],
|
||||||
|
['Ctrl + \\', 'Show or hide the sidebar'],
|
||||||
|
['Ctrl + Shift + O', 'Show or hide the outline'],
|
||||||
|
['/', 'Block menu while writing'],
|
||||||
|
['[[', 'Link to a page, or create one'],
|
||||||
|
];
|
||||||
|
---
|
||||||
|
<Base
|
||||||
|
title="Docs & FAQ"
|
||||||
|
current="/docs/"
|
||||||
|
description="Where Ink stores your notes, how it works with Obsidian, keyboard shortcuts, and its privacy statement."
|
||||||
|
>
|
||||||
|
<section class="wrap head">
|
||||||
|
<p class="eyebrow">Docs & FAQ</p>
|
||||||
|
<h1>Short answers</h1>
|
||||||
|
<nav class="toc" aria-label="On this page">
|
||||||
|
<a href="#storage">Storage</a><a href="#obsidian">Obsidian</a><a href="#shortcuts">Shortcuts</a><a href="#privacy">Privacy</a><a href="#faq">FAQ</a>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="narrow prose body">
|
||||||
|
<section id="storage" aria-labelledby="storage-h">
|
||||||
|
<h2 id="storage-h">Where are my notes stored?</h2>
|
||||||
|
<p>In a folder on your computer. On first launch Ink offers <code>~/Notes</code> or lets you choose another folder, and you can change it later in Settings.</p>
|
||||||
|
<ul>
|
||||||
|
<li>A page is a Markdown file: <code>Projects.md</code>. Its children live in a folder of the same name: <code>Projects/Roadmap.md</code>.</li>
|
||||||
|
<li>Page details such as icon, cover and tags go in the file's front matter. Keys Ink does not know are preserved.</li>
|
||||||
|
<li>Images are saved next to the note that uses them.</li>
|
||||||
|
<li>Canvases are <code>.canvas</code> files, boards are Markdown pages.</li>
|
||||||
|
<li>Ink's search index, trash and templates live in a hidden <code>.notes</code> folder inside your notes folder. The index is rebuilt on launch if you delete it.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="obsidian" aria-labelledby="obsidian-h">
|
||||||
|
<h2 id="obsidian-h">Does it work with Obsidian?</h2>
|
||||||
|
<p>Yes. Open the same folder in both apps.</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>[[Wiki links]]</code>, heading links and <code>![[embeds]]</code> use the same syntax.</li>
|
||||||
|
<li><code>.canvas</code> files use the JSON Canvas format and are written the way Obsidian writes them, so canvases open in both. Unknown fields are kept.</li>
|
||||||
|
<li>Kanban boards use the Obsidian Kanban plugin's Markdown format.</li>
|
||||||
|
<li>Ink watches the folder, so edits made in another editor show up.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="shortcuts" aria-labelledby="shortcuts-h">
|
||||||
|
<h2 id="shortcuts-h">Keyboard shortcuts</h2>
|
||||||
|
<p>On macOS, read <kbd>Ctrl</kbd> as <kbd>⌘</kbd>.</p>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<caption class="sr-only">Keyboard shortcuts</caption>
|
||||||
|
<thead><tr><th scope="col">Keys</th><th scope="col">Action</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{shortcuts.map(([keys, action]) => (
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{keys.split(' + ').map((k, i) => <>{i > 0 && ' + '}<kbd>{k}</kbd></>)}</th>
|
||||||
|
<td>{action}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="privacy" aria-labelledby="privacy-h">
|
||||||
|
<h2 id="privacy-h">Privacy</h2>
|
||||||
|
<p><strong>The app.</strong> Your notes stay in your folder. There is no account, nothing is uploaded, and the app does not send analytics. Its interface and fonts are bundled, and it loads your notes through a handler that only reads inside your notes folder.</p>
|
||||||
|
<p><strong>This website.</strong> No analytics, no cookies, no third-party requests. Fonts are served from this site. Nothing is stored in your browser; the theme simply follows your system's light or dark setting.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="faq" aria-labelledby="faq-h">
|
||||||
|
<h2 id="faq-h">FAQ</h2>
|
||||||
|
<h3>Which platforms are supported?</h3>
|
||||||
|
<p>Linux (AppImage) is available now. Windows and macOS builds are configured but not built or tested yet, so there is no download for them. See <a href={href('/download/')}>Download</a>.</p>
|
||||||
|
<h3>Does it sync between devices?</h3>
|
||||||
|
<p>Ink has no sync of its own. Because your notes are plain files, you can keep the folder in any file-sync or backup tool you already trust.</p>
|
||||||
|
<h3>Does it need an internet connection?</h3>
|
||||||
|
<p>No. Everything runs locally. Web link cards fetch a page's title and description only when you add a link.</p>
|
||||||
|
<h3>Why are the builds unsigned?</h3>
|
||||||
|
<p>Code-signing certificates cost money and effort that a small project has not spent yet. Use the checksum on the download page to verify your file.</p>
|
||||||
|
<h3>Can I export my notes?</h3>
|
||||||
|
<p>They are already Markdown files. You can also export a page to Markdown, HTML or PDF, and a canvas to PNG, SVG or PDF.</p>
|
||||||
|
<h3>How do I report a bug or ask for something?</h3>
|
||||||
|
<p>Open an issue on the <a href={config.issues} rel="noopener">issue tracker</a>.</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</Base>
|
||||||
|
<style>
|
||||||
|
.head { padding-top: clamp(3rem, 7vw, 5.5rem); display: grid; gap: 1.1rem; }
|
||||||
|
.toc { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.75rem; }
|
||||||
|
.toc a {
|
||||||
|
padding: 0.4rem 0.85rem; border-radius: 99px; border: 1px solid var(--line-2); color: var(--ink-2);
|
||||||
|
text-decoration: none; font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.toc a:hover { color: var(--ink); border-color: var(--blush-line); }
|
||||||
|
.body { padding-bottom: clamp(3rem, 7vw, 6rem); }
|
||||||
|
.table-wrap { overflow-x: auto; margin-top: 1rem; border: 1px solid var(--line); border-radius: var(--r); background: var(--surface); }
|
||||||
|
table { width: 100%; border-collapse: collapse; font-size: 0.95rem; }
|
||||||
|
th, td { text-align: left; padding: 0.7rem 1rem; border-top: 1px solid var(--line); vertical-align: top; }
|
||||||
|
thead th { border-top: 0; color: var(--muted); font-weight: 500; font-size: 0.82rem; }
|
||||||
|
tbody th { font-weight: 400; white-space: nowrap; }
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
thead { display: none; }
|
||||||
|
tr, th, td { display: block; }
|
||||||
|
tbody th { padding-bottom: 0; }
|
||||||
|
tbody td { border-top: 0; padding-top: 0.4rem; color: var(--ink-2); }
|
||||||
|
tbody tr + tr th { border-top: 1px solid var(--line); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
---
|
||||||
|
import Base from '../layouts/Base.astro';
|
||||||
|
import { config, formatSize, type PlatformId } from '../config';
|
||||||
|
import { href } from '../lib/url';
|
||||||
|
|
||||||
|
const p = config.platforms;
|
||||||
|
const linux = p.linux;
|
||||||
|
const soon: PlatformId[] = ['windows', 'macos'];
|
||||||
|
---
|
||||||
|
<Base
|
||||||
|
title="Download"
|
||||||
|
current="/download/"
|
||||||
|
description={`Download Ink ${config.version} for Linux (AppImage). Windows and macOS builds are coming soon. Unsigned builds, no tracking.`}
|
||||||
|
>
|
||||||
|
<div class="head-bg">
|
||||||
|
<section class="wrap head">
|
||||||
|
<img class="app-icon rise" src={href('/icon-192.png')} width="88" height="88" alt="" />
|
||||||
|
<p class="eyebrow">Download</p>
|
||||||
|
<h1>Get Ink</h1>
|
||||||
|
<p class="lede">Version {config.version}, released {config.releaseDate}. Linux is available now. Windows and macOS are configured but not built or tested yet.</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="wrap platforms" aria-label="Platforms">
|
||||||
|
<article class="card main spot reveal" data-os="linux">
|
||||||
|
<div class="top">
|
||||||
|
<span class="os-ic" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="3" /><path d="M7 10l3 2.5L7 15M12.5 15H17" /></svg></span>
|
||||||
|
<span class="badges"><span class="badge you" hidden>Your system</span><span class="badge ok">Available</span></span>
|
||||||
|
</div>
|
||||||
|
<h2>Linux</h2>
|
||||||
|
<p class="meta">{linux.format} · {linux.arch} · {formatSize(linux.bytes)} · v{config.version}</p>
|
||||||
|
<p>A single file. No install step, no root needed.</p>
|
||||||
|
<a class="btn primary" href={linux.url}>
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4v11m0 0l-4.5-4.5M12 15l4.5-4.5M5 19h14" /></svg>
|
||||||
|
Download AppImage
|
||||||
|
</a>
|
||||||
|
<p class="file">{linux.file}</p>
|
||||||
|
</article>
|
||||||
|
{soon.map((id) => (
|
||||||
|
<article class="card soon spot reveal" aria-labelledby={`${id}-h`} data-os={id}>
|
||||||
|
<div class="top">
|
||||||
|
<span class="os-ic" aria-hidden="true">
|
||||||
|
{id === 'windows'
|
||||||
|
? <svg viewBox="0 0 24 24"><rect x="4" y="4" width="7" height="7" rx="1.2" /><rect x="13" y="4" width="7" height="7" rx="1.2" /><rect x="4" y="13" width="7" height="7" rx="1.2" /><rect x="13" y="13" width="7" height="7" rx="1.2" /></svg>
|
||||||
|
: <svg viewBox="0 0 24 24"><path d="M9 6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3z" /></svg>}
|
||||||
|
</span>
|
||||||
|
<span class="badges"><span class="badge you" hidden>Your system</span><span class="badge">Coming soon</span></span>
|
||||||
|
</div>
|
||||||
|
<h2 id={`${id}-h`}>{p[id].label}</h2>
|
||||||
|
<p class="meta">{p[id].format}</p>
|
||||||
|
<p>Not built or tested yet. There is no download to offer, so there is no link here.</p>
|
||||||
|
<p><a href={config.issues} rel="noopener">Watch the issue tracker</a> for news. No sign-up, nothing collected.</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="wrap steps-sec" aria-labelledby="run-h">
|
||||||
|
<h2 id="run-h" class="reveal">Run it on Linux</h2>
|
||||||
|
<ol class="steps">
|
||||||
|
<li class="step reveal">
|
||||||
|
<span class="n" aria-hidden="true">1</span>
|
||||||
|
<h3>Download</h3>
|
||||||
|
<p>Grab <code>{linux.file}</code> above. It's {formatSize(linux.bytes)}.</p>
|
||||||
|
</li>
|
||||||
|
<li class="step reveal">
|
||||||
|
<span class="n" aria-hidden="true">2</span>
|
||||||
|
<h3>Make it executable</h3>
|
||||||
|
<pre data-copy><code>chmod +x {linux.file}</code></pre>
|
||||||
|
<p class="small">Or: right-click → Properties → allow executing as a program.</p>
|
||||||
|
</li>
|
||||||
|
<li class="step reveal">
|
||||||
|
<span class="n" aria-hidden="true">3</span>
|
||||||
|
<h3>Run it</h3>
|
||||||
|
<pre data-copy><code>./{linux.file}</code></pre>
|
||||||
|
<p class="small">Then pick <code>~/Notes</code> or any folder, including an Obsidian vault.</p>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="wrap prose" aria-label="More about the download">
|
||||||
|
<p>When run from an AppImage, Ink adds a launcher entry and icons under <code>~/.local/share</code> so your dock can find it. Delete those files to remove them. If it complains about FUSE on a recent distribution, install FUSE 2 (<code>libfuse2</code>), or run it with <code>--appimage-extract-and-run</code>.</p>
|
||||||
|
|
||||||
|
<h2 id="verify">Verify the download</h2>
|
||||||
|
<p>Expected SHA-256 for <code>{linux.file}</code>:</p>
|
||||||
|
<pre><code>{linux.sha256}</code></pre>
|
||||||
|
<p>Check your copy against it:</p>
|
||||||
|
<pre><code>echo "{linux.sha256} {linux.file}" | sha256sum -c</code></pre>
|
||||||
|
|
||||||
|
<h2 id="unsigned">Unsigned builds</h2>
|
||||||
|
<p>These builds are not code-signed. On Windows and macOS, once those ship, you will likely see a warning from SmartScreen or Gatekeeper. The checksum above is how you confirm you have the file that was published.</p>
|
||||||
|
|
||||||
|
<h2>Your notes after installing</h2>
|
||||||
|
<p>On first launch you either use <code>~/Notes</code> or choose a folder (an existing Obsidian vault works). Ink reads and writes Markdown files there and keeps its own search index, trash and templates in a hidden <code>.notes</code> folder inside it. Nothing is sent anywhere. <a href={href('/docs/')}>Read the docs</a>.</p>
|
||||||
|
</section>
|
||||||
|
</Base>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.head-bg { position: relative; isolation: isolate; }
|
||||||
|
.head-bg::before {
|
||||||
|
content: ''; position: absolute; inset: -80px 0 0; z-index: -1; pointer-events: none;
|
||||||
|
background-image: radial-gradient(circle, var(--line-2) 1px, transparent 1.3px); background-size: 22px 22px;
|
||||||
|
-webkit-mask-image: radial-gradient(ellipse 60% 90% at 50% 20%, #000 20%, transparent 80%);
|
||||||
|
mask-image: radial-gradient(ellipse 60% 90% at 50% 20%, #000 20%, transparent 80%);
|
||||||
|
}
|
||||||
|
.head { padding-top: clamp(3rem, 7vw, 5rem); display: grid; gap: 1rem; justify-items: center; text-align: center; }
|
||||||
|
.head h1 { font-size: clamp(2.6rem, 5vw + 0.5rem, 4.4rem); letter-spacing: -0.045em; }
|
||||||
|
.app-icon { width: 88px; height: 88px; filter: drop-shadow(0 14px 28px rgba(0, 0, 0, 0.4)); margin-bottom: 0.5rem; }
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.rise { animation: rise 0.9s var(--ease) both; }
|
||||||
|
@keyframes rise { from { opacity: 0.001; transform: translateY(12px) scale(0.94); } }
|
||||||
|
}
|
||||||
|
|
||||||
|
.platforms { display: grid; gap: 1rem; padding-top: 3rem; }
|
||||||
|
.card { display: grid; gap: 0.6rem; align-content: start; border-radius: 18px; padding: 1.6rem; }
|
||||||
|
.card h2 { font-size: 1.6rem; }
|
||||||
|
.main { border-color: var(--blush-line); background: linear-gradient(160deg, var(--blush-soft), var(--surface) 70%); }
|
||||||
|
.main .btn { justify-self: start; margin-top: 0.5rem; }
|
||||||
|
.file { font: 0.76rem/1.4 var(--f-mono); color: var(--muted); overflow-wrap: anywhere; }
|
||||||
|
.top { display: flex; justify-content: space-between; align-items: center; gap: 1rem; margin-bottom: 0.4rem; }
|
||||||
|
.os-ic { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 12px; background: var(--hover); border: 1px solid var(--line); color: var(--ink-2); }
|
||||||
|
.main .os-ic { background: var(--blush-soft); border-color: var(--blush-line); color: var(--accent-text); }
|
||||||
|
.os-ic svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
|
||||||
|
.badges { display: flex; gap: 0.4rem; flex-wrap: wrap; justify-content: flex-end; }
|
||||||
|
.badge { font: 500 0.72rem/1 var(--f-mono); padding: 0.35rem 0.6rem; border-radius: 99px; border: 1px solid var(--line-2); color: var(--ink-2); white-space: nowrap; }
|
||||||
|
.badge.ok { color: var(--ok); border-color: currentColor; }
|
||||||
|
.badge.you { color: var(--accent-text); border-color: var(--blush-line); background: var(--blush-soft); }
|
||||||
|
.meta { color: var(--muted); font-size: 0.92rem; }
|
||||||
|
.soon p:not(.meta) { color: var(--ink-2); }
|
||||||
|
.card.yours:not(.main) { border-color: var(--line-2); }
|
||||||
|
@media (min-width: 900px) {
|
||||||
|
.platforms { grid-template-columns: 1.3fr 1fr 1fr; align-items: stretch; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-sec { padding-top: clamp(3.5rem, 7vw, 5.5rem); }
|
||||||
|
.steps-sec > h2 { margin-bottom: 1.5rem; }
|
||||||
|
.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 1rem; }
|
||||||
|
.step { position: relative; display: grid; gap: 0.6rem; align-content: start; padding: 1.4rem; border-radius: 16px; border: 1px solid var(--line); background: var(--surface); }
|
||||||
|
.n {
|
||||||
|
display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%;
|
||||||
|
font: 600 0.82rem/1 var(--f-mono); color: var(--btn-ink); background: var(--btn);
|
||||||
|
}
|
||||||
|
.step pre { font-size: 0.8rem; white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||||
|
.small { font-size: 0.86rem; color: var(--muted); }
|
||||||
|
@media (min-width: 900px) { .steps { grid-template-columns: repeat(3, 1fr); } }
|
||||||
|
.prose { padding-block: clamp(2rem, 5vw, 3.5rem) clamp(3rem, 7vw, 6rem); }
|
||||||
|
.prose :global(pre) { white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { detectOS } from '../lib/os';
|
||||||
|
const os = detectOS();
|
||||||
|
const card = document.querySelector<HTMLElement>(`[data-os="${os}"]`);
|
||||||
|
if (card) {
|
||||||
|
card.classList.add('yours');
|
||||||
|
card.querySelector<HTMLElement>('.badge.you')?.removeAttribute('hidden');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
---
|
||||||
|
import Base from '../layouts/Base.astro';
|
||||||
|
import Screenshot from '../components/Screenshot.astro';
|
||||||
|
import DownloadButton from '../components/DownloadButton.astro';
|
||||||
|
import type { ScreenName } from '../lib/screens';
|
||||||
|
|
||||||
|
interface Item { title: string; text: string }
|
||||||
|
interface Group {
|
||||||
|
id: string;
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
intro: string;
|
||||||
|
screen: ScreenName;
|
||||||
|
items: Item[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const groups: Group[] = [
|
||||||
|
{
|
||||||
|
id: 'writing', eyebrow: 'Writing', title: 'Write in blocks, save as Markdown',
|
||||||
|
intro: 'The editor feels like a document and stores like a text file.',
|
||||||
|
screen: 'slash-menu',
|
||||||
|
items: [
|
||||||
|
{ title: 'Slash menu', text: 'Press / for headings, lists, to-dos, tables, code, callouts, toggles, math, Mermaid diagrams, images, dividers and footnotes.' },
|
||||||
|
{ title: 'Formatting bar and shortcuts', text: 'A floating bar appears over selected text. Markdown shortcuts such as # and - work at the start of a line.' },
|
||||||
|
{ title: 'Drag to reorder', text: 'Grab a block by its handle and drag it to a new place.' },
|
||||||
|
{ title: 'Code, math, diagrams', text: 'Syntax highlighting in code blocks, KaTeX for formulas, Mermaid for diagrams. Diagrams follow the light and dark theme.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'linking', eyebrow: 'Linking', title: 'Notes that know about each other',
|
||||||
|
intro: 'Links are plain text, so they survive any tool you use later.',
|
||||||
|
screen: 'linking',
|
||||||
|
items: [
|
||||||
|
{ title: 'Wiki links', text: '[[Page]] links to a note and [[Page#Heading]] jumps to a section.' },
|
||||||
|
{ title: 'Embeds', text: '![[Page]] shows another note inline, so one note can be built from several.' },
|
||||||
|
{ title: 'Backlinks', text: 'Every page lists the notes that link to it, in a panel at the bottom.' },
|
||||||
|
{ title: 'Rename-safe', text: 'Rename a page or a heading and the links that point to it are rewritten.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'canvas', eyebrow: 'Canvas', title: 'An endless surface for ideas',
|
||||||
|
intro: 'Same JSON Canvas format Obsidian uses, so .canvas files open in both apps.',
|
||||||
|
screen: 'canvas',
|
||||||
|
items: [
|
||||||
|
{ title: 'Cards of every kind', text: 'Markdown cards with the full block editor inside, live previews of your notes, images, and web link cards with the page title and description.' },
|
||||||
|
{ title: 'Groups and arrows', text: 'Groups fold away when you do not need them. Arrows can be curved, straight or right-angled, with your choice of arrowheads.' },
|
||||||
|
{ title: 'Tidy tools', text: 'Alignment guides, align and distribute, find-in-canvas, and Shift+1 to show everything.' },
|
||||||
|
{ title: 'Export and history', text: 'Export as PNG, SVG or PDF. Version history keeps earlier states of a canvas.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'boards', eyebrow: 'Boards', title: 'Kanban, built from a page',
|
||||||
|
intro: 'Turn a page into a board and back again. It stays a Markdown file.',
|
||||||
|
screen: 'board',
|
||||||
|
items: [
|
||||||
|
{ title: 'Columns and cards', text: 'Each heading is a column and each to-do is a card. Drag cards and columns around.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'organising', eyebrow: 'Organising', title: 'Find things, keep things',
|
||||||
|
intro: 'Tags, search and a palette for everything else.',
|
||||||
|
screen: 'palette',
|
||||||
|
items: [
|
||||||
|
{ title: 'Tags with colours', text: 'Tag a page and give the tag a colour. The graph view uses those colours.' },
|
||||||
|
{ title: 'Search and palette', text: 'Full-text search runs on a local SQLite index. tag: and in: operators narrow it. Ctrl+K opens the command palette.' },
|
||||||
|
{ title: 'Graph view', text: 'See how notes link, with filter and force settings.' },
|
||||||
|
{ title: 'Daily notes', text: 'One note per day, in a folder you configure.' },
|
||||||
|
{ title: 'Favorites, icons, covers, templates', text: 'Star pages, give them an icon, emoji or cover, and start new pages from templates.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'safety', eyebrow: 'Safety net', title: 'Mistakes are recoverable',
|
||||||
|
intro: 'Undo is only the first layer.',
|
||||||
|
screen: 'history',
|
||||||
|
items: [
|
||||||
|
{ title: 'Version history', text: 'Pages and canvases keep earlier versions you can look back at.' },
|
||||||
|
{ title: 'Trash with restore', text: 'Deleted pages go to a trash you can restore from.' },
|
||||||
|
{ title: 'Export', text: 'Export a page to Markdown, HTML or PDF.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'comfort', eyebrow: 'Comfort', title: 'Make it fit how you read',
|
||||||
|
intro: 'Quiet by default, adjustable when you want it.',
|
||||||
|
screen: 'settings',
|
||||||
|
items: [
|
||||||
|
{ title: 'Themes', text: 'Dark, light, or follow the system.' },
|
||||||
|
{ title: 'Text size and page width', text: 'Both adjustable, plus a focus mode and an outline panel for long pages.' },
|
||||||
|
{ title: 'Spellcheck', text: 'Pick the languages you write in.' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
---
|
||||||
|
<Base
|
||||||
|
title="Features"
|
||||||
|
current="/features/"
|
||||||
|
description="A tour of Ink: block editor, wiki links and embeds, canvas, kanban boards, graph view, tags, search, version history and export."
|
||||||
|
>
|
||||||
|
<div class="head-bg">
|
||||||
|
<section class="wrap head">
|
||||||
|
<p class="eyebrow">Features</p>
|
||||||
|
<h1>Everything Ink does</h1>
|
||||||
|
<p class="lede">A tour of what ships in version 0.1. Screenshots follow your system's light or dark theme, like the app.</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toc-bar">
|
||||||
|
<nav class="toc" aria-label="On this page" data-toc>
|
||||||
|
{groups.map((g) => <a href={`#${g.id}`}>{g.eyebrow}</a>)}
|
||||||
|
<a href="#privacy">Privacy</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{groups.map((g, i) => (
|
||||||
|
<section class="group wrap" id={g.id} aria-labelledby={`${g.id}-h`} data-spy>
|
||||||
|
<div class:list={['row', 'reveal', { flip: i % 2 === 1 }]}>
|
||||||
|
<div class="txt">
|
||||||
|
<p class="eyebrow"><span class="num">{String(i + 1).padStart(2, '0')}</span> {g.eyebrow}</p>
|
||||||
|
<h2 id={`${g.id}-h`}>{g.title}</h2>
|
||||||
|
<p class="lede">{g.intro}</p>
|
||||||
|
<dl class:list={{ single: g.items.length === 1 }}>
|
||||||
|
{g.items.map((it) => (
|
||||||
|
<div class="item spot">
|
||||||
|
<dt>{it.title}</dt>
|
||||||
|
<dd>{it.text}</dd>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<div class="media"><Screenshot name={g.screen} sizes="(min-width: 960px) 620px, 100vw" /></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<section class="wrap group" id="privacy" aria-labelledby="privacy-h" data-spy>
|
||||||
|
<div class="card reveal privacy">
|
||||||
|
<p class="eyebrow"><span class="num">08</span> Privacy</p>
|
||||||
|
<h2 id="privacy-h">Your notes never leave your machine</h2>
|
||||||
|
<p class="lede">Notes are plain <code>.md</code> files in a folder you choose. Ink has no account system and uploads nothing. The app's UI is bundled and its fonts are bundled. This website uses no analytics, no cookies and no third-party requests.</p>
|
||||||
|
<DownloadButton note={false} />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Base>
|
||||||
|
<style>
|
||||||
|
.head-bg { position: relative; isolation: isolate; }
|
||||||
|
.head-bg::before {
|
||||||
|
content: ''; position: absolute; inset: -80px 0 0; z-index: -1; pointer-events: none;
|
||||||
|
background-image: radial-gradient(circle, var(--line-2) 1px, transparent 1.3px); background-size: 22px 22px;
|
||||||
|
-webkit-mask-image: radial-gradient(ellipse 70% 90% at 20% 30%, #000 20%, transparent 80%);
|
||||||
|
mask-image: radial-gradient(ellipse 70% 90% at 20% 30%, #000 20%, transparent 80%);
|
||||||
|
}
|
||||||
|
.head { padding-block: clamp(3rem, 7vw, 5.5rem) 2rem; display: grid; gap: 1.1rem; }
|
||||||
|
.head h1 { font-size: clamp(2.4rem, 5vw + 0.5rem, 4.2rem); letter-spacing: -0.04em; }
|
||||||
|
|
||||||
|
.toc-bar { position: sticky; top: 80px; z-index: 40; width: min(100% - 1.5rem, var(--wrap)); margin-inline: auto; }
|
||||||
|
.toc {
|
||||||
|
position: relative; display: flex; gap: 0.2rem; padding: 0.3rem; overflow-x: auto; scrollbar-width: none; width: max-content; max-width: 100%;
|
||||||
|
background: color-mix(in srgb, var(--surface) 78%, transparent); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
|
||||||
|
border: 1px solid var(--line); border-radius: 13px; box-shadow: 0 10px 30px -18px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
.toc::-webkit-scrollbar { display: none; }
|
||||||
|
.toc a {
|
||||||
|
padding: 0.42rem 0.8rem; border-radius: 9px; color: var(--ink-2); white-space: nowrap;
|
||||||
|
text-decoration: none; font-size: 0.88rem; font-weight: 500; transition: color 0.2s, background-color 0.25s;
|
||||||
|
}
|
||||||
|
.toc a:hover { color: var(--ink); background: var(--hover); }
|
||||||
|
.toc a.active { color: var(--ink); background: var(--blush-soft); }
|
||||||
|
|
||||||
|
.group { padding-top: clamp(3.5rem, 8vw, 6.5rem); scroll-margin-top: 140px; }
|
||||||
|
.num { color: var(--muted); margin-right: 0.35rem; }
|
||||||
|
.row { display: grid; gap: 2rem; align-items: start; }
|
||||||
|
.txt { display: grid; gap: 0.9rem; }
|
||||||
|
dl { margin: 0.75rem 0 0; display: grid; gap: 0.6rem; }
|
||||||
|
.item { padding: 0.9rem 1rem; border-radius: 12px; border: 1px solid var(--line); background: var(--surface); transition: border-color 0.3s; }
|
||||||
|
dt { font-weight: 600; }
|
||||||
|
dd { margin: 0.2rem 0 0; color: var(--ink-2); font-size: 0.95rem; }
|
||||||
|
.media { min-width: 0; }
|
||||||
|
.privacy { display: grid; gap: 1rem; justify-items: start; padding: clamp(1.75rem, 4vw, 3rem); margin-bottom: clamp(2rem, 5vw, 4rem); }
|
||||||
|
@media (min-width: 640px) { dl:not(.single) { grid-template-columns: 1fr 1fr; } }
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.row { grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr); gap: 3.5rem; }
|
||||||
|
.row.flip { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); }
|
||||||
|
.row.flip .txt { order: 2; }
|
||||||
|
.media { position: sticky; top: 150px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Scrollspy: highlight the section you're reading in the sticky bar.
|
||||||
|
const links = new Map([...document.querySelectorAll<HTMLAnchorElement>('[data-toc] a')].map((a) => [a.hash.slice(1), a]));
|
||||||
|
const seen = new Map<string, number>();
|
||||||
|
const io = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
for (const e of entries) seen.set(e.target.id, e.isIntersecting ? e.intersectionRatio : 0);
|
||||||
|
let best = '';
|
||||||
|
let max = 0;
|
||||||
|
for (const [id, r] of seen) if (r > max) { max = r; best = id; }
|
||||||
|
links.forEach((a, id) => {
|
||||||
|
const on = id === best;
|
||||||
|
a.classList.toggle('active', on);
|
||||||
|
if (on) {
|
||||||
|
a.setAttribute('aria-current', 'location');
|
||||||
|
// Keep the active pill visible inside the bar without touching the page's scroll.
|
||||||
|
const bar = a.parentElement!;
|
||||||
|
bar.scrollTo({ left: a.offsetLeft - (bar.clientWidth - a.clientWidth) / 2, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
else a.removeAttribute('aria-current');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ rootMargin: '-140px 0px -40% 0px', threshold: [0, 0.1, 0.25, 0.5, 0.75, 1] },
|
||||||
|
);
|
||||||
|
document.querySelectorAll('[data-spy]').forEach((s) => io.observe(s));
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
---
|
||||||
|
import Base from '../layouts/Base.astro';
|
||||||
|
import Screenshot from '../components/Screenshot.astro';
|
||||||
|
import DownloadButton from '../components/DownloadButton.astro';
|
||||||
|
import Ticker from '../components/Ticker.astro';
|
||||||
|
import Showcase from '../components/Showcase.astro';
|
||||||
|
import Bento from '../components/Bento.astro';
|
||||||
|
import Files from '../components/Files.astro';
|
||||||
|
import { config } from '../config';
|
||||||
|
import { href } from '../lib/url';
|
||||||
|
---
|
||||||
|
<Base description={config.description}>
|
||||||
|
<!-- Hero -->
|
||||||
|
<div class="hero-bg" data-spotlight>
|
||||||
|
<section class="hero wrap">
|
||||||
|
<div class="intro">
|
||||||
|
<a class="news rise" style="--i:0" href={href('/changelog/')}>
|
||||||
|
<span class="pulse" aria-hidden="true"></span>
|
||||||
|
<span>Ink {config.version} is out for Linux</span>
|
||||||
|
<span class="arr" aria-hidden="true">→</span>
|
||||||
|
</a>
|
||||||
|
<h1>
|
||||||
|
<span class="rise" style="--i:1">Your notes.</span>
|
||||||
|
<span class="rise" style="--i:2">Plain files.</span>
|
||||||
|
<span class="rise accent" style="--i:3">Yours.</span>
|
||||||
|
</h1>
|
||||||
|
<p class="lede rise" style="--i:4">
|
||||||
|
A calm, offline Markdown notes app for the desktop. Every note is a <code>.md</code> file in a folder you choose.
|
||||||
|
No account, nothing uploaded.
|
||||||
|
</p>
|
||||||
|
<div class="actions rise" style="--i:5">
|
||||||
|
<DownloadButton center />
|
||||||
|
<a class="btn ghost" href={href('/features/')}>Take the tour</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stage">
|
||||||
|
<Screenshot name="editor" priority sizes="(min-width: 1160px) 1120px, 100vw" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="ticker-wrap" aria-label="Feature list">
|
||||||
|
<Ticker />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Showcase -->
|
||||||
|
<section class="section wrap" aria-labelledby="sc-h">
|
||||||
|
<div class="head center reveal">
|
||||||
|
<p class="eyebrow">A closer look</p>
|
||||||
|
<h2 id="sc-h">One quiet app, five ways to think</h2>
|
||||||
|
</div>
|
||||||
|
<div class="reveal"><Showcase /></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Bento -->
|
||||||
|
<section class="section wrap bento-sec" aria-labelledby="bento-h">
|
||||||
|
<div class="head reveal">
|
||||||
|
<p class="eyebrow">Inside Ink</p>
|
||||||
|
<h2 id="bento-h">Small details, everywhere</h2>
|
||||||
|
<p class="lede">The things you reach for a hundred times a day, made to feel light.</p>
|
||||||
|
</div>
|
||||||
|
<div class="reveal"><Bento /></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Plain files -->
|
||||||
|
<section class="section files-sec" aria-labelledby="files-h">
|
||||||
|
<div class="wrap files-grid">
|
||||||
|
<div class="files-text reveal">
|
||||||
|
<p class="eyebrow">Plain files · offline · yours</p>
|
||||||
|
<h2 id="files-h">Open the folder. It's all there.</h2>
|
||||||
|
<p class="lede">No database you can't read, no export step. Ink works on a folder of Markdown files, and that folder is the whole truth.</p>
|
||||||
|
<ul class="points">
|
||||||
|
<li>
|
||||||
|
<span class="pi" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M7 3h7l5 5v13H7z" /><path d="M14 3v5h5" /></svg></span>
|
||||||
|
<div><h3>Plain files</h3><p>Open them in any editor, back them up with any tool, leave whenever you like.</p></div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="pi" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 9a14 14 0 0 1 18 0M6.5 12.5a9 9 0 0 1 11 0M10 16a4 4 0 0 1 4 0" /><circle cx="12" cy="19" r="1" /><path d="M3 3l18 18" /></svg></span>
|
||||||
|
<div><h3>Offline, always</h3><p>No server to talk to. Search is a local SQLite index; fonts are bundled.</p></div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="pi" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="10" rx="2.5" /><path d="M8 10V7a4 4 0 0 1 8 0v3" /></svg></span>
|
||||||
|
<div><h3>Yours</h3><p>No account, no sign-in, no analytics. Nothing is uploaded.</p></div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p class="obsidian">Already use Obsidian? Open the same folder. Links, boards and <code>.canvas</code> files work in both. <a href={href('/docs/#obsidian')}>How it works</a></p>
|
||||||
|
</div>
|
||||||
|
<div class="reveal"><Files /></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- CTA -->
|
||||||
|
<section class="wrap cta-sec">
|
||||||
|
<div class="cta reveal" data-spotlight>
|
||||||
|
<div class="cta-in">
|
||||||
|
<img src={href('/icon-192.png')} width="72" height="72" alt="" class="cta-icon" loading="lazy" decoding="async" />
|
||||||
|
<h2>Give your notes a quiet place to live.</h2>
|
||||||
|
<p class="lede">Ink {config.version} is available now for Linux. Windows and macOS builds are on the way.</p>
|
||||||
|
<DownloadButton note={false} center />
|
||||||
|
<p class="fine">Builds are unsigned for now. <a href={href('/download/')}>Details and checksum</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Base>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* ---------- hero ---------- */
|
||||||
|
.hero-bg { position: relative; isolation: isolate; overflow: hidden; }
|
||||||
|
/* Dot grid like the app's canvas, plus a blush copy of it that shows only around the pointer. */
|
||||||
|
.hero-bg::before, .hero-bg::after {
|
||||||
|
content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none;
|
||||||
|
background-size: 22px 22px; background-position: center top;
|
||||||
|
}
|
||||||
|
.hero-bg::before {
|
||||||
|
background-image: radial-gradient(circle, var(--line-2) 1px, transparent 1.3px);
|
||||||
|
-webkit-mask-image: radial-gradient(ellipse 80% 70% at 50% 30%, #000 30%, transparent 100%);
|
||||||
|
mask-image: radial-gradient(ellipse 80% 70% at 50% 30%, #000 30%, transparent 100%);
|
||||||
|
}
|
||||||
|
.hero-bg::after {
|
||||||
|
background-image: radial-gradient(circle, var(--blush) 1.2px, transparent 1.6px);
|
||||||
|
-webkit-mask-image: radial-gradient(220px circle at var(--x, -999px) var(--y, -999px), #000, transparent);
|
||||||
|
mask-image: radial-gradient(220px circle at var(--x, -999px) var(--y, -999px), #000, transparent);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.hero { padding-top: clamp(3.5rem, 9vw, 7rem); display: grid; gap: clamp(3rem, 7vw, 5rem); }
|
||||||
|
.intro { display: grid; gap: 1.5rem; justify-items: center; text-align: center; }
|
||||||
|
h1 { display: grid; font-size: clamp(2.6rem, 7vw + 0.5rem, 5.4rem); line-height: 1.02; letter-spacing: -0.045em; }
|
||||||
|
.accent {
|
||||||
|
color: var(--accent-text);
|
||||||
|
background: linear-gradient(100deg, var(--accent-text) 20%, #f7d3c9 50%, var(--accent-text) 80%);
|
||||||
|
background-size: 220% 100%; -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
.accent { background-image: linear-gradient(100deg, var(--accent-text) 20%, #c97a6d 50%, var(--accent-text) 80%); }
|
||||||
|
}
|
||||||
|
.lede { margin-inline: auto; }
|
||||||
|
.intro .lede { max-width: 46ch; }
|
||||||
|
.actions { display: flex; flex-wrap: wrap; justify-content: center; align-items: flex-start; gap: 0.75rem 1rem; }
|
||||||
|
.btn.ghost { background: transparent; }
|
||||||
|
|
||||||
|
.news {
|
||||||
|
display: inline-flex; align-items: center; gap: 0.6rem; padding: 0.4rem 0.85rem 0.4rem 0.7rem; border-radius: 99px;
|
||||||
|
border: 1px solid var(--line-2); background: color-mix(in srgb, var(--surface) 70%, transparent);
|
||||||
|
color: var(--ink-2); font-size: 0.86rem; text-decoration: none; transition: border-color 0.25s, color 0.25s;
|
||||||
|
}
|
||||||
|
.news:hover { color: var(--ink); border-color: var(--blush-line); }
|
||||||
|
.news .arr { transition: transform 0.25s var(--ease); }
|
||||||
|
.news:hover .arr { transform: translateX(3px); }
|
||||||
|
.pulse { position: relative; width: 8px; height: 8px; border-radius: 50%; background: var(--ok); }
|
||||||
|
|
||||||
|
.stage { position: relative; perspective: 1800px; }
|
||||||
|
/* A soft blush light under the frame. */
|
||||||
|
.stage::before {
|
||||||
|
content: ''; position: absolute; inset: 12% 8% -4%; z-index: -1; border-radius: 50%;
|
||||||
|
background: var(--blush); opacity: 0.16; filter: blur(70px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.rise { animation: rise 0.9s var(--ease) both; animation-delay: calc(var(--i) * 80ms + 60ms); }
|
||||||
|
@keyframes rise { from { opacity: 0.001; transform: translateY(14px); filter: blur(5px); } }
|
||||||
|
/* The headline is the largest paint: it comes into focus instead of fading in, so it shows at once. */
|
||||||
|
h1 .rise { animation-name: focus; }
|
||||||
|
@keyframes focus { from { transform: translateY(12px); filter: blur(10px); } }
|
||||||
|
h1 .accent { animation: focus 0.9s var(--ease) both, shine 7s ease-in-out 1.6s infinite; animation-delay: 0.3s, 1.6s; }
|
||||||
|
@keyframes shine { 0%, 60% { background-position: 100% 0; } 100% { background-position: -120% 0; } }
|
||||||
|
.pulse::after {
|
||||||
|
content: ''; position: absolute; inset: 0; border-radius: 50%; background: var(--ok);
|
||||||
|
animation: ping 2.4s var(--ease) infinite;
|
||||||
|
}
|
||||||
|
@keyframes ping { 0% { transform: scale(1); opacity: 0.6; } 80%, 100% { transform: scale(2.8); opacity: 0; } }
|
||||||
|
|
||||||
|
.stage > :global(.shot) { animation: lift 1.1s var(--ease) 0.45s both; transform-origin: 50% 0; }
|
||||||
|
@keyframes lift { from { transform: translateY(40px) rotateX(14deg) scale(0.96); } }
|
||||||
|
/* Where supported, the frame starts tilted back and lies flat as you scroll. */
|
||||||
|
@supports (animation-timeline: view()) {
|
||||||
|
.stage > :global(.shot) {
|
||||||
|
animation: lift 1.1s var(--ease) 0.45s both, flatten linear both;
|
||||||
|
animation-timeline: auto, view();
|
||||||
|
animation-range: normal, entry 0% cover 50%;
|
||||||
|
}
|
||||||
|
@keyframes flatten { from { transform: rotateX(10deg) scale(0.97); } to { transform: none; } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- ticker ---------- */
|
||||||
|
.ticker-wrap { padding-top: clamp(3rem, 7vw, 5rem); }
|
||||||
|
|
||||||
|
/* ---------- sections ---------- */
|
||||||
|
.head { display: grid; gap: 0.9rem; margin-bottom: clamp(2rem, 4vw, 3rem); max-width: 40rem; }
|
||||||
|
.head.center { text-align: center; justify-items: center; margin-inline: auto; }
|
||||||
|
|
||||||
|
/* ---------- files ---------- */
|
||||||
|
.files-sec { background: linear-gradient(to bottom, transparent, var(--surface-2) 30%, var(--surface-2) 70%, transparent); }
|
||||||
|
.files-grid { display: grid; gap: 3rem; align-items: center; }
|
||||||
|
.files-text { display: grid; gap: 1.1rem; }
|
||||||
|
.files-text .lede { margin-inline: 0; }
|
||||||
|
.points { list-style: none; margin: 0.5rem 0 0; padding: 0; display: grid; gap: 1.1rem; }
|
||||||
|
.points li { display: flex; gap: 0.9rem; align-items: flex-start; }
|
||||||
|
.points h3 { font-size: 1rem; margin-bottom: 0.15rem; }
|
||||||
|
.points p { color: var(--ink-2); font-size: 0.95rem; }
|
||||||
|
.pi { display: grid; place-items: center; width: 38px; height: 38px; flex: none; border-radius: 11px; background: var(--blush-soft); border: 1px solid var(--blush-line); color: var(--accent-text); }
|
||||||
|
.pi svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
|
||||||
|
.obsidian { color: var(--ink-2); font-size: 0.95rem; padding-top: 1rem; border-top: 1px solid var(--line); }
|
||||||
|
@media (min-width: 960px) { .files-grid { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: 4rem; } }
|
||||||
|
|
||||||
|
/* ---------- CTA ---------- */
|
||||||
|
.cta-sec { padding-block: clamp(2rem, 5vw, 4rem) clamp(3rem, 6vw, 5rem); }
|
||||||
|
.cta {
|
||||||
|
position: relative; isolation: isolate; overflow: hidden; border-radius: 24px;
|
||||||
|
border: 1px solid var(--blush-line); background: var(--surface);
|
||||||
|
}
|
||||||
|
.cta::before, .cta::after {
|
||||||
|
content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none; background-size: 22px 22px;
|
||||||
|
}
|
||||||
|
.cta::before {
|
||||||
|
background-image: radial-gradient(circle, var(--line-2) 1px, transparent 1.3px);
|
||||||
|
-webkit-mask-image: radial-gradient(ellipse 70% 80% at 50% 50%, #000 20%, transparent 90%);
|
||||||
|
mask-image: radial-gradient(ellipse 70% 80% at 50% 50%, #000 20%, transparent 90%);
|
||||||
|
}
|
||||||
|
.cta::after {
|
||||||
|
background-image: radial-gradient(circle, var(--blush) 1.2px, transparent 1.6px);
|
||||||
|
-webkit-mask-image: radial-gradient(200px circle at var(--x, -999px) var(--y, -999px), #000, transparent);
|
||||||
|
mask-image: radial-gradient(200px circle at var(--x, -999px) var(--y, -999px), #000, transparent);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.cta-in { display: grid; justify-items: center; text-align: center; gap: 1.1rem; padding: clamp(3rem, 8vw, 5.5rem) 1.5rem; background: radial-gradient(ellipse at 50% 0%, var(--blush-soft), transparent 70%); }
|
||||||
|
.cta-icon { width: 72px; height: 72px; filter: drop-shadow(0 12px 24px rgba(0, 0, 0, 0.35)); }
|
||||||
|
.cta h2 { max-width: 20ch; font-size: clamp(1.8rem, 3vw + 1rem, 2.8rem); letter-spacing: -0.035em; }
|
||||||
|
.fine { font-size: 0.88rem; color: var(--muted); }
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.cta-icon { animation: bob 5s ease-in-out infinite; }
|
||||||
|
@keyframes bob { 50% { transform: translateY(-6px); } }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// Small, site-wide enhancements. Every page reads fine without them.
|
||||||
|
|
||||||
|
const reduce = matchMedia('(prefers-reduced-motion: reduce)');
|
||||||
|
const finePointer = matchMedia('(hover: hover) and (pointer: fine)');
|
||||||
|
|
||||||
|
/* Fade/slide-in on scroll. Elements are only hidden once `.js` is on <html> (see global.css). */
|
||||||
|
const reveal = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
for (const e of entries) if (e.isIntersecting) { e.target.classList.add('in'); reveal.unobserve(e.target); }
|
||||||
|
},
|
||||||
|
{ rootMargin: '0px 0px -8% 0px', threshold: 0.08 },
|
||||||
|
);
|
||||||
|
document.querySelectorAll('.reveal').forEach((el) => reveal.observe(el));
|
||||||
|
|
||||||
|
/* Demos only animate while on screen (saves battery, and they start fresh when you arrive). */
|
||||||
|
const play = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
for (const e of entries) {
|
||||||
|
e.target.classList.toggle('playing', e.isIntersecting && !reduce.matches);
|
||||||
|
const svgs = e.target.querySelectorAll<SVGSVGElement>('svg[data-smil]');
|
||||||
|
svgs.forEach((s) => (e.isIntersecting && !reduce.matches ? s.unpauseAnimations() : s.pauseAnimations()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.2 },
|
||||||
|
);
|
||||||
|
document.querySelectorAll('[data-play]').forEach((el) => play.observe(el));
|
||||||
|
|
||||||
|
/* Spotlight: cards and dot grids light up around the pointer. One listener for the whole page. */
|
||||||
|
if (finePointer.matches) {
|
||||||
|
let frame = 0;
|
||||||
|
let last: PointerEvent | null = null;
|
||||||
|
let lit: HTMLElement | null = null;
|
||||||
|
const set = (el: HTMLElement, x: string, y: string) => {
|
||||||
|
el.style.setProperty('--x', x);
|
||||||
|
el.style.setProperty('--y', y);
|
||||||
|
};
|
||||||
|
document.addEventListener(
|
||||||
|
'pointermove',
|
||||||
|
(e) => {
|
||||||
|
last = e;
|
||||||
|
if (frame) return;
|
||||||
|
frame = requestAnimationFrame(() => {
|
||||||
|
frame = 0;
|
||||||
|
const ev = last!;
|
||||||
|
const el = (ev.target as Element | null)?.closest<HTMLElement>('.spot, [data-spotlight]') ?? null;
|
||||||
|
if (lit && lit !== el) set(lit, '-999px', '-999px');
|
||||||
|
lit = el;
|
||||||
|
if (!el) return;
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
set(el, `${ev.clientX - r.left}px`, `${ev.clientY - r.top}px`);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ passive: true },
|
||||||
|
);
|
||||||
|
document.documentElement.addEventListener('pointerleave', () => { if (lit) set(lit, '-999px', '-999px'); lit = null; });
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy buttons on code blocks. */
|
||||||
|
document.querySelectorAll<HTMLPreElement>('pre[data-copy], .prose pre').forEach((pre) => {
|
||||||
|
if (!navigator.clipboard) return;
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 'copy';
|
||||||
|
btn.textContent = 'Copy';
|
||||||
|
btn.setAttribute('aria-label', 'Copy to clipboard');
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText((pre.querySelector('code') ?? pre).textContent?.trim() ?? '');
|
||||||
|
btn.textContent = 'Copied';
|
||||||
|
} catch {
|
||||||
|
btn.textContent = 'Press Ctrl+C';
|
||||||
|
}
|
||||||
|
setTimeout(() => (btn.textContent = 'Copy'), 1600);
|
||||||
|
});
|
||||||
|
pre.classList.add('has-copy');
|
||||||
|
pre.append(btn);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Show ⌘ instead of Ctrl on Apple machines. */
|
||||||
|
if (/mac|iphone|ipad/i.test(navigator.platform || navigator.userAgent)) {
|
||||||
|
document.querySelectorAll('[data-mod]').forEach((k) => (k.textContent = '⌘'));
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
@import '@fontsource-variable/inter/index.css';
|
||||||
|
@import '@fontsource/jetbrains-mono/400.css';
|
||||||
|
@import '@fontsource/jetbrains-mono/500.css';
|
||||||
|
|
||||||
|
/* Palette taken from the app (src/app.css). Dark first; light is its daytime twin.
|
||||||
|
--accent-text / --btn are slightly deeper than the app's light blush so text and buttons pass AA. */
|
||||||
|
:root {
|
||||||
|
--bg: #0e0e0f;
|
||||||
|
--surface: #161617;
|
||||||
|
--surface-2: #1c1c1e;
|
||||||
|
--raise: #222224;
|
||||||
|
--line: rgba(255, 255, 255, 0.08);
|
||||||
|
--line-2: rgba(255, 255, 255, 0.14);
|
||||||
|
--hover: rgba(255, 255, 255, 0.055);
|
||||||
|
--ink: #ececed;
|
||||||
|
--ink-2: #a1a1a6;
|
||||||
|
--muted: #8f8f95;
|
||||||
|
--blush: #e4ada5;
|
||||||
|
--accent-text: #e4ada5;
|
||||||
|
--btn: #e4ada5;
|
||||||
|
--btn-ink: #2a1714;
|
||||||
|
--blush-soft: rgba(228, 173, 165, 0.12);
|
||||||
|
--blush-line: rgba(228, 173, 165, 0.4);
|
||||||
|
--ok: #93b98d;
|
||||||
|
--code-bg: #111112;
|
||||||
|
--shadow: 0 30px 70px -20px rgba(0, 0, 0, 0.7), 0 2px 12px rgba(0, 0, 0, 0.35);
|
||||||
|
--shadow-sm: 0 8px 24px -10px rgba(0, 0, 0, 0.6);
|
||||||
|
--grain: 0.045;
|
||||||
|
--r: 12px;
|
||||||
|
--r-lg: 16px;
|
||||||
|
--f-ui: 'Inter Variable', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
||||||
|
--f-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
||||||
|
--ease: cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
|
--measure: 66ch;
|
||||||
|
--wrap: 1120px;
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
/* Light theme: follows the system setting (there is no manual switch). */
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
--bg: #f5f5f6; --surface: #ffffff; --surface-2: #f3f3f4; --raise: #ffffff;
|
||||||
|
--line: rgba(0, 0, 0, 0.09); --line-2: rgba(0, 0, 0, 0.16); --hover: rgba(0, 0, 0, 0.045);
|
||||||
|
--ink: #1c1c1e; --ink-2: #55555a; --muted: #66666b;
|
||||||
|
--blush: #b8695f; --accent-text: #9c5249; --btn: #a45a50; --btn-ink: #fff8f5;
|
||||||
|
--blush-soft: rgba(184, 105, 95, 0.1); --blush-line: rgba(184, 105, 95, 0.42);
|
||||||
|
--ok: #3d6b38; --code-bg: #f2f2f3;
|
||||||
|
--shadow: 0 30px 70px -24px rgba(0, 0, 0, 0.28), 0 2px 12px rgba(0, 0, 0, 0.07);
|
||||||
|
--shadow-sm: 0 8px 24px -12px rgba(0, 0, 0, 0.22);
|
||||||
|
--grain: 0.06;
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*, *::before, *::after { box-sizing: border-box; }
|
||||||
|
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: 88px; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--ink);
|
||||||
|
font: 400 1rem/1.65 var(--f-ui);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
/* The same fine grain the app lays over its background. */
|
||||||
|
body::after {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 100;
|
||||||
|
opacity: var(--grain);
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .5 0 0 0 0 .5 0 0 0 0 .5 0 0 0 .9 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
main { flex: 1; }
|
||||||
|
img, svg { display: block; max-width: 100%; }
|
||||||
|
::selection { background: rgba(228, 173, 165, 0.32); }
|
||||||
|
|
||||||
|
h1, h2, h3 { line-height: 1.15; letter-spacing: -0.02em; margin: 0; font-weight: 600; text-wrap: balance; }
|
||||||
|
h1 { font-size: clamp(2.3rem, 5.5vw + 0.5rem, 4rem); letter-spacing: -0.035em; font-weight: 650; }
|
||||||
|
h2 { font-size: clamp(1.6rem, 2.4vw + 0.8rem, 2.35rem); }
|
||||||
|
h3 { font-size: 1.15rem; letter-spacing: -0.01em; }
|
||||||
|
p { margin: 0; text-wrap: pretty; }
|
||||||
|
.lede { font-size: clamp(1.05rem, 0.6vw + 0.95rem, 1.25rem); color: var(--ink-2); max-width: 52ch; }
|
||||||
|
.muted { color: var(--muted); }
|
||||||
|
.eyebrow { font: 500 0.78rem/1 var(--f-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent-text); }
|
||||||
|
|
||||||
|
a { color: var(--accent-text); text-underline-offset: 3px; text-decoration-thickness: 1px; }
|
||||||
|
a:hover { text-decoration-thickness: 2px; }
|
||||||
|
:focus-visible { outline: 2px solid var(--blush); outline-offset: 3px; border-radius: 6px; }
|
||||||
|
|
||||||
|
code, kbd, pre { font-family: var(--f-mono); }
|
||||||
|
code { font-size: 0.88em; background: var(--surface-2); border: 1px solid var(--line); padding: 0.1em 0.38em; border-radius: 6px; }
|
||||||
|
pre {
|
||||||
|
margin: 0; padding: 1rem 1.15rem; background: var(--code-bg); border: 1px solid var(--line);
|
||||||
|
border-radius: var(--r); overflow-x: auto; font-size: 0.86rem; line-height: 1.7; tab-size: 2;
|
||||||
|
}
|
||||||
|
pre code { background: none; border: 0; padding: 0; font-size: inherit; }
|
||||||
|
kbd {
|
||||||
|
font-size: 0.78rem; line-height: 1; padding: 0.3rem 0.45rem; border-radius: 6px;
|
||||||
|
background: var(--surface-2); border: 1px solid var(--line-2); box-shadow: inset 0 -1px 0 var(--line-2);
|
||||||
|
color: var(--ink); white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap { width: min(100% - 2.5rem, var(--wrap)); margin-inline: auto; }
|
||||||
|
.narrow { width: min(100% - 2.5rem, 760px); margin-inline: auto; }
|
||||||
|
.section { padding-block: clamp(3.5rem, 8vw, 7rem); }
|
||||||
|
.stack > * + * { margin-top: var(--gap, 1rem); }
|
||||||
|
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
||||||
|
|
||||||
|
.skip {
|
||||||
|
position: absolute; left: 1rem; top: -4rem; z-index: 200; padding: 0.6rem 1rem; border-radius: 10px;
|
||||||
|
background: var(--btn); color: var(--btn-ink); font-weight: 600; text-decoration: none; transition: top 0.15s;
|
||||||
|
}
|
||||||
|
.skip:focus { top: 1rem; }
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
display: inline-flex; align-items: center; justify-content: center; gap: 0.55rem;
|
||||||
|
padding: 0.8rem 1.35rem; border-radius: 12px; font: 600 1rem/1.2 var(--f-ui);
|
||||||
|
text-decoration: none; border: 1px solid var(--line-2); color: var(--ink); background: var(--surface);
|
||||||
|
transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease), background-color 0.2s, border-color 0.2s;
|
||||||
|
}
|
||||||
|
.btn:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); text-decoration: none; border-color: var(--blush-line); }
|
||||||
|
.btn.primary { background: var(--btn); color: var(--btn-ink); border-color: transparent; }
|
||||||
|
.btn.primary:hover { box-shadow: 0 10px 28px -10px var(--blush); }
|
||||||
|
.btn svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
.card {
|
||||||
|
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 1.5rem;
|
||||||
|
transition: transform 0.3s var(--ease), border-color 0.3s, box-shadow 0.3s;
|
||||||
|
}
|
||||||
|
a.card { color: inherit; text-decoration: none; display: block; }
|
||||||
|
.card.lift:hover, a.card:hover { transform: translateY(-3px); border-color: var(--line-2); box-shadow: var(--shadow-sm); }
|
||||||
|
|
||||||
|
/* Scroll reveal: only hidden when JS is running and motion is OK. */
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.js .reveal { opacity: 0; transform: translateY(16px); transition: opacity 0.7s var(--ease), transform 0.7s var(--ease); }
|
||||||
|
.js .reveal.in { opacity: 1; transform: none; }
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
html { scroll-behavior: auto; }
|
||||||
|
*, *::before, *::after { transition-duration: 0.001ms !important; animation-duration: 0.001ms !important; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prose pages (docs, changelog bodies) */
|
||||||
|
.prose { max-width: var(--measure); }
|
||||||
|
.prose h2 { margin-top: 3rem; margin-bottom: 0.75rem; }
|
||||||
|
.prose h3 { margin-top: 1.75rem; margin-bottom: 0.4rem; }
|
||||||
|
.prose p, .prose ul, .prose ol, .prose pre, .prose table { margin-top: 0.9rem; }
|
||||||
|
.prose ul, .prose ol { padding-left: 1.3rem; }
|
||||||
|
.prose li + li { margin-top: 0.35rem; }
|
||||||
|
.prose li::marker { color: var(--muted); }
|
||||||
|
|
||||||
|
/* Cross-page fade (CSS view transitions; browsers without support just navigate).
|
||||||
|
Don't give .site-header a view-transition-name: it becomes a backdrop root and kills the nav's blur. */
|
||||||
|
@view-transition { navigation: auto; }
|
||||||
|
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 0.28s; }
|
||||||
|
|
||||||
|
/* Spotlight: a soft blush rim follows the pointer around a card (see scripts/site.ts). */
|
||||||
|
.spot { position: relative; isolation: isolate; }
|
||||||
|
.spot::before {
|
||||||
|
content: ''; position: absolute; inset: -1px; border-radius: inherit; padding: 1px; pointer-events: none; z-index: 2;
|
||||||
|
background: radial-gradient(240px circle at var(--x, -999px) var(--y, -999px), var(--blush-line), transparent 70%);
|
||||||
|
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
||||||
|
-webkit-mask-composite: xor;
|
||||||
|
mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
|
||||||
|
opacity: 0; transition: opacity 0.35s var(--ease);
|
||||||
|
}
|
||||||
|
.spot::after {
|
||||||
|
content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: -1;
|
||||||
|
background: radial-gradient(360px circle at var(--x, -999px) var(--y, -999px), var(--blush-soft), transparent 70%);
|
||||||
|
opacity: 0; transition: opacity 0.35s var(--ease);
|
||||||
|
}
|
||||||
|
.spot:hover::before, .spot:hover::after { opacity: 1; }
|
||||||
|
|
||||||
|
/* Code blocks with a copy button */
|
||||||
|
pre.has-copy { position: relative; padding-right: 4.5rem; }
|
||||||
|
.copy {
|
||||||
|
position: absolute; top: 0.55rem; right: 0.55rem; font: 500 0.74rem/1 var(--f-ui); color: var(--ink-2);
|
||||||
|
background: var(--surface); border: 1px solid var(--line-2); border-radius: 8px; padding: 0.4rem 0.6rem; cursor: pointer;
|
||||||
|
transition: color 0.2s, border-color 0.2s;
|
||||||
|
}
|
||||||
|
.copy:hover { color: var(--ink); border-color: var(--blush-line); }
|
||||||
|
|
||||||
|
/* Dot grid, same recipe as the app's canvas */
|
||||||
|
.dots-bg {
|
||||||
|
background-image: radial-gradient(circle, var(--line-2) 1px, transparent 1.3px);
|
||||||
|
background-size: 22px 22px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
|
"include": [".astro/types.d.ts", "**/*"],
|
||||||
|
"exclude": ["dist"]
|
||||||
|
}
|
||||||