This commit is contained in:
parent
84769f9ce4
commit
fd4bb61b5f
30 changed files with 355 additions and 156 deletions
|
|
@ -40,16 +40,11 @@ const postLastmodLookup = new Map(
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: 'https://schmelczer.dev',
|
site: 'https://schmelczer.dev',
|
||||||
trailingSlash: 'ignore',
|
trailingSlash: 'ignore',
|
||||||
build: { inlineStylesheets: 'always' },
|
|
||||||
redirects: {
|
|
||||||
'/writing/': '/articles/',
|
|
||||||
'/writing/[slug]': '/articles/[slug]',
|
|
||||||
},
|
|
||||||
integrations: [
|
integrations: [
|
||||||
sitemap({
|
sitemap({
|
||||||
filter: (page) => {
|
filter: (page) => {
|
||||||
const path = new URL(page).pathname;
|
const path = new URL(page).pathname;
|
||||||
return !path.startsWith('/writing/') && path !== '/404/';
|
return !/^\/tags\/[^/]+\/?$/.test(path) && path !== '/404/';
|
||||||
},
|
},
|
||||||
serialize(item) {
|
serialize(item) {
|
||||||
const url = new URL(item.url);
|
const url = new URL(item.url);
|
||||||
|
|
@ -93,7 +88,6 @@ export default defineConfig({
|
||||||
behavior: 'append',
|
behavior: 'append',
|
||||||
properties: {
|
properties: {
|
||||||
className: ['heading-anchor'],
|
className: ['heading-anchor'],
|
||||||
ariaLabel: 'Permalink',
|
|
||||||
},
|
},
|
||||||
// Glyph rendered via CSS ::before so it doesn't leak into the TOC
|
// Glyph rendered via CSS ::before so it doesn't leak into the TOC
|
||||||
// when astro:content extracts heading.text from the rendered HTML.
|
// when astro:content extracts heading.text from the rendered HTML.
|
||||||
|
|
@ -110,11 +104,33 @@ export default defineConfig({
|
||||||
return (tree) => {
|
return (tree) => {
|
||||||
visit(tree, 'element', (node) => {
|
visit(tree, 'element', (node) => {
|
||||||
if (!SCROLLABLE.has(node.tagName)) return;
|
if (!SCROLLABLE.has(node.tagName)) return;
|
||||||
node.properties = node.properties ?? {};
|
|
||||||
node.properties.tabindex = '0';
|
node.properties.tabindex = '0';
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
function rehypeLabelHeadingPermalinks() {
|
||||||
|
function textOf(node) {
|
||||||
|
if (!node) return '';
|
||||||
|
if (node.type === 'text') return node.value ?? '';
|
||||||
|
return (node.children ?? []).map(textOf).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (tree) => {
|
||||||
|
visit(tree, 'element', (node) => {
|
||||||
|
if (!/^h[2-6]$/.test(node.tagName)) return;
|
||||||
|
const headingText = textOf(node).trim();
|
||||||
|
if (!headingText) return;
|
||||||
|
|
||||||
|
for (const child of node.children ?? []) {
|
||||||
|
const className = child.properties?.className;
|
||||||
|
const classes = Array.isArray(className) ? className : [className];
|
||||||
|
if (child.tagName === 'a' && classes.includes('heading-anchor')) {
|
||||||
|
child.properties.ariaLabel = `Permalink to ${headingText}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
105
package-lock.json
generated
105
package-lock.json
generated
|
|
@ -6,9 +6,6 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "schmelczer-dev",
|
"name": "schmelczer-dev",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
|
||||||
"sharp": "^0.34.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/check": "^0.9.9",
|
"@astrojs/check": "^0.9.9",
|
||||||
"@astrojs/rss": "^4.0.18",
|
"@astrojs/rss": "^4.0.18",
|
||||||
|
|
@ -19,6 +16,7 @@
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
|
"sharp": "^0.34.5",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"unist-util-visit": "^5.1.0"
|
"unist-util-visit": "^5.1.0"
|
||||||
},
|
},
|
||||||
|
|
@ -478,6 +476,7 @@
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -930,6 +929,7 @@
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||||
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
|
@ -942,6 +942,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -964,6 +965,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -986,6 +988,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1002,6 +1005,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1018,6 +1022,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1034,6 +1039,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1050,6 +1056,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1066,6 +1073,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1082,6 +1090,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1098,6 +1107,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1114,6 +1124,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1130,6 +1141,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1146,6 +1158,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1168,6 +1181,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1190,6 +1204,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1212,6 +1227,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1234,6 +1250,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1256,6 +1273,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1278,6 +1296,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1300,6 +1319,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1322,6 +1342,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"wasm32"
|
"wasm32"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1341,6 +1362,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1360,6 +1382,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1379,6 +1402,7 @@
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -2892,6 +2916,7 @@
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
|
@ -5240,6 +5265,7 @@
|
||||||
"version": "7.8.0",
|
"version": "7.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
||||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
|
|
@ -5252,6 +5278,7 @@
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
||||||
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
||||||
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -5524,6 +5551,7 @@
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/typesafe-path": {
|
"node_modules/typesafe-path": {
|
||||||
|
|
@ -6268,9 +6296,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "2.8.4",
|
"version": "2.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
|
||||||
"integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
|
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -6352,19 +6380,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/yaml-language-server/node_modules/yaml": {
|
|
||||||
"version": "2.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
|
|
||||||
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"yaml": "bin.mjs"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yargs": {
|
"node_modules/yargs": {
|
||||||
"version": "17.7.2",
|
"version": "17.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
|
|
@ -6630,7 +6645,7 @@
|
||||||
"integrity": "sha512-PJzRmgQzUxI2uwpdX2lXSHtP4G8ocp24/t+bZyf5Fy0SZLSF9f9KXZoMlFM/XCGue+B0nH/2IZ7FpBYQATBsCg==",
|
"integrity": "sha512-PJzRmgQzUxI2uwpdX2lXSHtP4G8ocp24/t+bZyf5Fy0SZLSF9f9KXZoMlFM/XCGue+B0nH/2IZ7FpBYQATBsCg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"yaml": "^2.8.2"
|
"yaml": "^2.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-string-parser": {
|
"@babel/helper-string-parser": {
|
||||||
|
|
@ -6756,6 +6771,7 @@
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
|
|
@ -6946,12 +6962,14 @@
|
||||||
"@img/colour": {
|
"@img/colour": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||||
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="
|
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"@img/sharp-darwin-arm64": {
|
"@img/sharp-darwin-arm64": {
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
|
||||||
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
|
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@img/sharp-libvips-darwin-arm64": "1.2.4"
|
"@img/sharp-libvips-darwin-arm64": "1.2.4"
|
||||||
|
|
@ -6961,6 +6979,7 @@
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
|
||||||
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
|
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@img/sharp-libvips-darwin-x64": "1.2.4"
|
"@img/sharp-libvips-darwin-x64": "1.2.4"
|
||||||
|
|
@ -6970,66 +6989,77 @@
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
|
||||||
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
|
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@img/sharp-libvips-darwin-x64": {
|
"@img/sharp-libvips-darwin-x64": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
|
||||||
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
|
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@img/sharp-libvips-linux-arm": {
|
"@img/sharp-libvips-linux-arm": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
|
||||||
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
|
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@img/sharp-libvips-linux-arm64": {
|
"@img/sharp-libvips-linux-arm64": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
|
||||||
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
|
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@img/sharp-libvips-linux-ppc64": {
|
"@img/sharp-libvips-linux-ppc64": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
|
||||||
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
|
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@img/sharp-libvips-linux-riscv64": {
|
"@img/sharp-libvips-linux-riscv64": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
|
||||||
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
|
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@img/sharp-libvips-linux-s390x": {
|
"@img/sharp-libvips-linux-s390x": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
|
||||||
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
|
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@img/sharp-libvips-linux-x64": {
|
"@img/sharp-libvips-linux-x64": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
|
||||||
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
|
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@img/sharp-libvips-linuxmusl-arm64": {
|
"@img/sharp-libvips-linuxmusl-arm64": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
|
||||||
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
|
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@img/sharp-libvips-linuxmusl-x64": {
|
"@img/sharp-libvips-linuxmusl-x64": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
|
||||||
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
|
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@img/sharp-linux-arm": {
|
"@img/sharp-linux-arm": {
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
|
||||||
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
|
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@img/sharp-libvips-linux-arm": "1.2.4"
|
"@img/sharp-libvips-linux-arm": "1.2.4"
|
||||||
|
|
@ -7039,6 +7069,7 @@
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
|
||||||
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
|
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@img/sharp-libvips-linux-arm64": "1.2.4"
|
"@img/sharp-libvips-linux-arm64": "1.2.4"
|
||||||
|
|
@ -7048,6 +7079,7 @@
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
|
||||||
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
|
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@img/sharp-libvips-linux-ppc64": "1.2.4"
|
"@img/sharp-libvips-linux-ppc64": "1.2.4"
|
||||||
|
|
@ -7057,6 +7089,7 @@
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
|
||||||
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
|
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@img/sharp-libvips-linux-riscv64": "1.2.4"
|
"@img/sharp-libvips-linux-riscv64": "1.2.4"
|
||||||
|
|
@ -7066,6 +7099,7 @@
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
|
||||||
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
|
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@img/sharp-libvips-linux-s390x": "1.2.4"
|
"@img/sharp-libvips-linux-s390x": "1.2.4"
|
||||||
|
|
@ -7075,6 +7109,7 @@
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
|
||||||
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
|
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@img/sharp-libvips-linux-x64": "1.2.4"
|
"@img/sharp-libvips-linux-x64": "1.2.4"
|
||||||
|
|
@ -7084,6 +7119,7 @@
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
|
||||||
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
|
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
|
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
|
||||||
|
|
@ -7093,6 +7129,7 @@
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
|
||||||
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
|
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
|
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
|
||||||
|
|
@ -7102,6 +7139,7 @@
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
|
||||||
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
|
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
|
||||||
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@emnapi/runtime": "^1.7.0"
|
"@emnapi/runtime": "^1.7.0"
|
||||||
|
|
@ -7111,18 +7149,21 @@
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
|
||||||
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
|
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@img/sharp-win32-ia32": {
|
"@img/sharp-win32-ia32": {
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
|
||||||
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
|
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@img/sharp-win32-x64": {
|
"@img/sharp-win32-x64": {
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
|
||||||
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
|
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@jridgewell/sourcemap-codec": {
|
"@jridgewell/sourcemap-codec": {
|
||||||
|
|
@ -8066,7 +8107,8 @@
|
||||||
"detect-libc": {
|
"detect-libc": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"devalue": {
|
"devalue": {
|
||||||
"version": "5.8.1",
|
"version": "5.8.1",
|
||||||
|
|
@ -9614,12 +9656,14 @@
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "7.8.0",
|
"version": "7.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
||||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="
|
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"sharp": {
|
"sharp": {
|
||||||
"version": "0.34.5",
|
"version": "0.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
||||||
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@img/colour": "^1.0.0",
|
"@img/colour": "^1.0.0",
|
||||||
"@img/sharp-darwin-arm64": "0.34.5",
|
"@img/sharp-darwin-arm64": "0.34.5",
|
||||||
|
|
@ -9800,6 +9844,7 @@
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
||||||
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"typesafe-path": {
|
"typesafe-path": {
|
||||||
|
|
@ -10248,9 +10293,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"yaml": {
|
"yaml": {
|
||||||
"version": "2.8.4",
|
"version": "2.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
|
||||||
"integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
|
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"yaml-language-server": {
|
"yaml-language-server": {
|
||||||
|
|
@ -10269,7 +10314,7 @@
|
||||||
"vscode-languageserver-textdocument": "^1.0.1",
|
"vscode-languageserver-textdocument": "^1.0.1",
|
||||||
"vscode-languageserver-types": "^3.16.0",
|
"vscode-languageserver-types": "^3.16.0",
|
||||||
"vscode-uri": "^3.0.2",
|
"vscode-uri": "^3.0.2",
|
||||||
"yaml": "2.7.1"
|
"yaml": "^2.9.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": {
|
"ajv": {
|
||||||
|
|
@ -10302,12 +10347,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz",
|
||||||
"integrity": "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==",
|
"integrity": "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
|
||||||
"yaml": {
|
|
||||||
"version": "2.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
|
|
||||||
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
|
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -49,5 +49,8 @@
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"unist-util-visit": "^5.1.0",
|
"unist-util-visit": "^5.1.0",
|
||||||
"sharp": "^0.34.5"
|
"sharp": "^0.34.5"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"yaml": "^2.9.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
/*
|
|
||||||
X-Content-Type-Options: nosniff
|
|
||||||
Referrer-Policy: strict-origin-when-cross-origin
|
|
||||||
|
|
||||||
/_astro/*
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
/fonts/*
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
/media/*
|
|
||||||
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
|
|
||||||
|
|
||||||
/favicon.ico
|
|
||||||
Cache-Control: public, max-age=604800
|
|
||||||
|
|
||||||
/*.xml
|
|
||||||
Cache-Control: public, max-age=300
|
|
||||||
|
|
||||||
/*.webmanifest
|
|
||||||
Cache-Control: public, max-age=300
|
|
||||||
Binary file not shown.
13
public/media/video/ad_astra.vtt
Normal file
13
public/media/video/ad_astra.vtt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
WEBVTT
|
||||||
|
|
||||||
|
00:00.000 --> 00:04.000
|
||||||
|
No spoken dialogue. Game audio only.
|
||||||
|
|
||||||
|
00:04.000 --> 00:35.000
|
||||||
|
The Ad Astra handheld board runs the game on a small OLED display.
|
||||||
|
|
||||||
|
00:35.000 --> 01:05.000
|
||||||
|
The player controls the game through the IR input while the engine updates the display in real time.
|
||||||
|
|
||||||
|
01:05.000 --> 01:34.600
|
||||||
|
The clip continues showing gameplay on the custom ATtiny85-based board.
|
||||||
Binary file not shown.
|
|
@ -56,7 +56,7 @@ try {
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = await walk(dist);
|
const files = await walk(dist);
|
||||||
const htmlAndXml = files.filter((file) => /\.(html|xml)$/.test(file));
|
const checkedFiles = files.filter((file) => /\.(html|xml|css|webmanifest)$/.test(file));
|
||||||
|
|
||||||
function pagePathname(file) {
|
function pagePathname(file) {
|
||||||
const rel = path.relative(dist, file).replaceAll(path.sep, '/');
|
const rel = path.relative(dist, file).replaceAll(path.sep, '/');
|
||||||
|
|
@ -65,14 +65,52 @@ function pagePathname(file) {
|
||||||
return `/${rel}`;
|
return `/${rel}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const file of htmlAndXml) {
|
function collectUrlReferences(body, rel) {
|
||||||
|
const urls = [];
|
||||||
|
|
||||||
|
for (const match of body.matchAll(/\b(?:href|src|poster)=["']([^"']+)["']/g)) {
|
||||||
|
urls.push(match[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const match of body.matchAll(/\bsrcset=["']([^"']+)["']/g)) {
|
||||||
|
for (const candidate of match[1].split(',')) {
|
||||||
|
const url = candidate.trim().split(/\s+/)[0];
|
||||||
|
if (url) urls.push(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rel.endsWith('.css')) {
|
||||||
|
for (const match of body.matchAll(/url\(\s*['"]?([^'")]+)['"]?\s*\)/g)) {
|
||||||
|
urls.push(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rel.endsWith('.webmanifest')) {
|
||||||
|
try {
|
||||||
|
const manifest = JSON.parse(body);
|
||||||
|
for (const key of ['start_url', 'scope']) {
|
||||||
|
if (typeof manifest[key] === 'string') urls.push(manifest[key]);
|
||||||
|
}
|
||||||
|
for (const icon of manifest.icons ?? []) {
|
||||||
|
if (typeof icon?.src === 'string') urls.push(icon.src);
|
||||||
|
}
|
||||||
|
for (const screenshot of manifest.screenshots ?? []) {
|
||||||
|
if (typeof screenshot?.src === 'string') urls.push(screenshot.src);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
failures.push(`${rel}: invalid web manifest JSON`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of checkedFiles) {
|
||||||
const body = await readFile(file, 'utf8');
|
const body = await readFile(file, 'utf8');
|
||||||
const rel = path.relative(dist, file);
|
const rel = path.relative(dist, file);
|
||||||
const baseUrl = new URL(pagePathname(file), 'https://schmelczer.dev');
|
const baseUrl = new URL(pagePathname(file), 'https://schmelczer.dev');
|
||||||
const matches = body.matchAll(/\b(?:href|src)=["']([^"'#?]+)(?:[?#][^"']*)?["']/g);
|
|
||||||
|
|
||||||
for (const match of matches) {
|
for (const raw of collectUrlReferences(body, rel)) {
|
||||||
const raw = match[1];
|
|
||||||
if (/^(mailto:|tel:|data:)/i.test(raw)) continue;
|
if (/^(mailto:|tel:|data:)/i.test(raw)) continue;
|
||||||
|
|
||||||
let parsed;
|
let parsed;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { createServer } from 'node:http';
|
import { createServer } from 'node:http';
|
||||||
import { readdir, readFile, stat } from 'node:fs/promises';
|
import { mkdir, readdir, readFile, rm, stat } from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { chromium } from 'playwright';
|
import { chromium } from 'playwright';
|
||||||
|
|
||||||
const dist = path.resolve('dist');
|
const dist = path.resolve('dist');
|
||||||
|
const browserTmp = path.resolve('.astro', 'playwright-overflow-tmp');
|
||||||
const INDEX_FILE = 'index.html';
|
const INDEX_FILE = 'index.html';
|
||||||
const MAX_NAV_RETRIES = 4;
|
const MAX_NAV_RETRIES = 4;
|
||||||
// Common device widths: iPhone SE / Galaxy S / iPhone 14 / iPad portrait /
|
// Common device widths: iPhone SE / Galaxy S / iPhone 14 / iPad portrait /
|
||||||
|
|
@ -30,6 +31,7 @@ const MIME = {
|
||||||
'.woff2': 'font/woff2',
|
'.woff2': 'font/woff2',
|
||||||
'.mp4': 'video/mp4',
|
'.mp4': 'video/mp4',
|
||||||
'.webm': 'video/webm',
|
'.webm': 'video/webm',
|
||||||
|
'.vtt': 'text/vtt; charset=utf-8',
|
||||||
'.pdf': 'application/pdf',
|
'.pdf': 'application/pdf',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -59,9 +61,6 @@ async function discoverRoutes() {
|
||||||
if (!file.endsWith('.html')) continue;
|
if (!file.endsWith('.html')) continue;
|
||||||
const rel = path.relative(dist, file).replaceAll(path.sep, '/');
|
const rel = path.relative(dist, file).replaceAll(path.sep, '/');
|
||||||
if (rel === '404.html') continue;
|
if (rel === '404.html') continue;
|
||||||
// /writing/* are meta-refresh redirect stubs to /articles/*, not real
|
|
||||||
// pages; measuring them would just remeasure /articles/.
|
|
||||||
if (rel.startsWith('writing/')) continue;
|
|
||||||
if (rel === INDEX_FILE) {
|
if (rel === INDEX_FILE) {
|
||||||
routes.add('/');
|
routes.add('/');
|
||||||
} else if (rel.endsWith(`/${INDEX_FILE}`)) {
|
} else if (rel.endsWith(`/${INDEX_FILE}`)) {
|
||||||
|
|
@ -104,6 +103,16 @@ try {
|
||||||
throw new Error('dist/ does not exist. Run npm run build first.');
|
throw new Error('dist/ does not exist. Run npm run build first.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some CI/dev containers mount /tmp as a very small tmpfs. Chromium uses the
|
||||||
|
// process temp directory for profiles and internal files; putting it under the
|
||||||
|
// already-ignored .astro/ directory keeps the overflow check reproducible even
|
||||||
|
// when the system temp mount is full.
|
||||||
|
await rm(browserTmp, { recursive: true, force: true });
|
||||||
|
await mkdir(browserTmp, { recursive: true });
|
||||||
|
process.env.TMPDIR = browserTmp;
|
||||||
|
process.env.TMP = browserTmp;
|
||||||
|
process.env.TEMP = browserTmp;
|
||||||
|
|
||||||
const routes = await discoverRoutes();
|
const routes = await discoverRoutes();
|
||||||
|
|
||||||
const server = createServer(async (req, res) => {
|
const server = createServer(async (req, res) => {
|
||||||
|
|
@ -125,6 +134,12 @@ const failures = [];
|
||||||
function launchBrowser() {
|
function launchBrowser() {
|
||||||
return chromium.launch({
|
return chromium.launch({
|
||||||
headless: true,
|
headless: true,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
TMPDIR: browserTmp,
|
||||||
|
TMP: browserTmp,
|
||||||
|
TEMP: browserTmp,
|
||||||
|
},
|
||||||
args: ['--disable-dev-shm-usage', '--disable-gpu', '--no-sandbox'],
|
args: ['--disable-dev-shm-usage', '--disable-gpu', '--no-sandbox'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -183,11 +198,11 @@ async function openBrowser() {
|
||||||
async function newMeasurementContext(browser, width) {
|
async function newMeasurementContext(browser, width) {
|
||||||
const context = await browser.newContext({
|
const context = await browser.newContext({
|
||||||
viewport: { width, height: 900 },
|
viewport: { width, height: 900 },
|
||||||
javaScriptEnabled: false,
|
javaScriptEnabled: true,
|
||||||
});
|
});
|
||||||
await context.route('**/*', (route) => {
|
await context.route('**/*', (route) => {
|
||||||
const type = route.request().resourceType();
|
const type = route.request().resourceType();
|
||||||
if (['font', 'image', 'media'].includes(type)) {
|
if (type === 'media') {
|
||||||
route.abort('blockedbyclient');
|
route.abort('blockedbyclient');
|
||||||
} else {
|
} else {
|
||||||
route.continue();
|
route.continue();
|
||||||
|
|
@ -281,6 +296,7 @@ try {
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
server.close();
|
server.close();
|
||||||
|
await rm(browserTmp, { recursive: true, force: true }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (failures.length > 0) {
|
if (failures.length > 0) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import { ARTICLE_THUMBNAIL, articlePath, formatDate, formatDateShort } from '../
|
||||||
interface Props {
|
interface Props {
|
||||||
posts: CollectionEntry<'posts'>[];
|
posts: CollectionEntry<'posts'>[];
|
||||||
showYear?: boolean;
|
showYear?: boolean;
|
||||||
currentTag?: string;
|
|
||||||
tagLimit?: number;
|
tagLimit?: number;
|
||||||
// Opt-in: eagerly load the first thumbnail. Only set when the list is
|
// Opt-in: eagerly load the first thumbnail. Only set when the list is
|
||||||
// reliably above the fold (home, tag pages). Lists below substantial
|
// reliably above the fold (home, tag pages). Lists below substantial
|
||||||
|
|
@ -15,13 +14,7 @@ interface Props {
|
||||||
eagerFirstThumbnail?: boolean;
|
eagerFirstThumbnail?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { posts, showYear = true, tagLimit = 3, eagerFirstThumbnail = false } = Astro.props;
|
||||||
posts,
|
|
||||||
showYear = true,
|
|
||||||
currentTag,
|
|
||||||
tagLimit = 3,
|
|
||||||
eagerFirstThumbnail = false,
|
|
||||||
} = Astro.props;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<ol class="article-list">
|
<ol class="article-list">
|
||||||
|
|
@ -31,11 +24,13 @@ const {
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
<article>
|
<article>
|
||||||
<a class="entry-title" href={href}>
|
<h3>
|
||||||
{post.data.title}
|
<a class="entry-title" href={href}>
|
||||||
</a>
|
{post.data.title}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
<p>{post.data.description}</p>
|
<p>{post.data.description}</p>
|
||||||
<TagList tags={post.data.tags} currentTag={currentTag} limit={tagLimit} />
|
<TagList tags={post.data.tags} limit={tagLimit} />
|
||||||
</article>
|
</article>
|
||||||
<time datetime={post.data.date.toISOString()}>
|
<time datetime={post.data.date.toISOString()}>
|
||||||
{showYear ? formatDate(post.data.date) : formatDateShort(post.data.date)}
|
{showYear ? formatDate(post.data.date) : formatDateShort(post.data.date)}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
---
|
---
|
||||||
import { navItems, site } from '../lib/site';
|
import { navItems, site } from '../lib/site';
|
||||||
|
|
||||||
const current = Astro.url.pathname;
|
const currentPath = Astro.url.pathname;
|
||||||
|
const current =
|
||||||
|
currentPath === '/' || currentPath.endsWith('/') || /\.[^/]+$/.test(currentPath)
|
||||||
|
? currentPath
|
||||||
|
: `${currentPath}/`;
|
||||||
|
|
||||||
// Exact match for the current page; section match (descendant URLs) for
|
// Exact match for the current page; section match (descendant URLs) for
|
||||||
// ancestor links. `aria-current="page"` is reserved for the exact page,
|
// ancestor links. `aria-current="page"` is reserved for the exact page,
|
||||||
|
|
@ -46,7 +50,13 @@ const headerNavItems = navItems.filter((item) => item.href !== '/' && !item.foot
|
||||||
</svg>
|
</svg>
|
||||||
<span class="sr-only">RSS feed</span>
|
<span class="sr-only">RSS feed</span>
|
||||||
</a>
|
</a>
|
||||||
<button id="theme-switcher" class="theme-switcher" type="button">
|
<button
|
||||||
|
id="theme-switcher"
|
||||||
|
class="theme-switcher"
|
||||||
|
type="button"
|
||||||
|
aria-label="Dark theme"
|
||||||
|
aria-pressed="false"
|
||||||
|
>
|
||||||
<span class="sr-only">Toggle theme</span>
|
<span class="sr-only">Toggle theme</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -68,7 +78,7 @@ const headerNavItems = navItems.filter((item) => item.href !== '/' && !item.foot
|
||||||
function sync(theme) {
|
function sync(theme) {
|
||||||
switcher.setAttribute('aria-pressed', String(theme === 'dark'));
|
switcher.setAttribute('aria-pressed', String(theme === 'dark'));
|
||||||
switcher.setAttribute(
|
switcher.setAttribute(
|
||||||
'aria-label',
|
'title',
|
||||||
theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'
|
theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'
|
||||||
);
|
);
|
||||||
for (var i = 0; i < themeColorMetas.length; i += 1) {
|
for (var i = 0; i < themeColorMetas.length; i += 1) {
|
||||||
|
|
|
||||||
|
|
@ -10,24 +10,23 @@ interface Props {
|
||||||
|
|
||||||
const { item } = Astro.props;
|
const { item } = Astro.props;
|
||||||
|
|
||||||
const fallbackFormatFor = (format: string | undefined): 'png' | 'jpg' =>
|
const videoWidth = item.type === 'video' ? (item.poster?.width ?? 1280) : undefined;
|
||||||
format === 'png' ? 'png' : 'jpg';
|
const videoHeight = item.type === 'video' ? (item.poster?.height ?? 720) : undefined;
|
||||||
---
|
---
|
||||||
|
|
||||||
<figure class="post-media">
|
<figure class="post-media">
|
||||||
{
|
{
|
||||||
item.type === 'video' ? (
|
item.type === 'video' ? (
|
||||||
// Decorative videos auto-play silently (like a GIF) and are hidden from
|
// Decorative videos stay inert and hidden from assistive tech. Meaningful
|
||||||
// assistive tech. Meaningful videos expose controls and an accessible
|
// videos expose controls, captions, and an accessible name.
|
||||||
// name so users can play and identify them.
|
|
||||||
item.decorative ? (
|
item.decorative ? (
|
||||||
<video
|
<video
|
||||||
autoplay
|
|
||||||
loop
|
|
||||||
muted
|
muted
|
||||||
playsinline
|
playsinline
|
||||||
preload="metadata"
|
preload="metadata"
|
||||||
poster={item.poster?.src}
|
poster={item.poster?.src}
|
||||||
|
width={videoWidth}
|
||||||
|
height={videoHeight}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
|
|
@ -35,9 +34,25 @@ const fallbackFormatFor = (format: string | undefined): 'png' | 'jpg' =>
|
||||||
{item.mp4 && <source src={item.mp4} type="video/mp4" />}
|
{item.mp4 && <source src={item.mp4} type="video/mp4" />}
|
||||||
</video>
|
</video>
|
||||||
) : (
|
) : (
|
||||||
<video controls preload="none" poster={item.poster?.src} aria-label={item.alt}>
|
<video
|
||||||
|
controls
|
||||||
|
preload="none"
|
||||||
|
poster={item.poster?.src}
|
||||||
|
width={videoWidth}
|
||||||
|
height={videoHeight}
|
||||||
|
aria-label={item.alt}
|
||||||
|
>
|
||||||
{item.webm && <source src={item.webm} type="video/webm" />}
|
{item.webm && <source src={item.webm} type="video/webm" />}
|
||||||
{item.mp4 && <source src={item.mp4} type="video/mp4" />}
|
{item.mp4 && <source src={item.mp4} type="video/mp4" />}
|
||||||
|
{item.captions && (
|
||||||
|
<track
|
||||||
|
kind="captions"
|
||||||
|
src={item.captions}
|
||||||
|
srclang="en"
|
||||||
|
label={item.captionsLabel}
|
||||||
|
default
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</video>
|
</video>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -46,8 +61,7 @@ const fallbackFormatFor = (format: string | undefined): 'png' | 'jpg' =>
|
||||||
src={item.src}
|
src={item.src}
|
||||||
alt={item.decorative ? '' : (item.alt ?? '')}
|
alt={item.decorative ? '' : (item.alt ?? '')}
|
||||||
formats={['avif', 'webp']}
|
formats={['avif', 'webp']}
|
||||||
fallbackFormat={fallbackFormatFor(item.src.format)}
|
widths={[480, 720, 960, 1280, 1600, 1920]}
|
||||||
widths={[480, 720, 960, 1280, 1600, 1920, 2400]}
|
|
||||||
sizes="(max-width: 700px) calc(100vw - 2 * clamp(20px, 4vw, 32px)), (max-width: 1100px) min(calc(100vw - 4rem), 56rem), 56rem"
|
sizes="(max-width: 700px) calc(100vw - 2 * clamp(20px, 4vw, 32px)), (max-width: 1100px) min(calc(100vw - 4rem), 56rem), 56rem"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
|
|
@ -56,5 +70,11 @@ const fallbackFormatFor = (format: string | undefined): 'png' | 'jpg' =>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{item.caption && !item.decorative && <figcaption>{item.caption}</figcaption>}
|
{item.caption && !item.decorative && <figcaption>{item.caption}</figcaption>}
|
||||||
{item.transcript && <p class="media-transcript">{item.transcript}</p>}
|
{
|
||||||
|
item.transcript && (
|
||||||
|
<p class="media-transcript">
|
||||||
|
<strong>Transcript:</strong> {item.transcript}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
</figure>
|
</figure>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ const remaining =
|
||||||
{
|
{
|
||||||
visibleTags.map((tag) => (
|
visibleTags.map((tag) => (
|
||||||
<li>
|
<li>
|
||||||
<a href={tagPath(tag)} aria-current={tag === currentTag ? 'true' : undefined}>
|
<a href={tagPath(tag)} aria-current={tag === currentTag ? 'page' : undefined}>
|
||||||
{tag}
|
{tag}
|
||||||
{counts && counts[tag] !== undefined && (
|
{counts && counts[tag] !== undefined && (
|
||||||
<span class="tag-count">{counts[tag]}</span>
|
<span class="tag-count">{counts[tag]}</span>
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,38 @@ import type { SchemaContext } from 'astro:content';
|
||||||
import { glob } from 'astro/loaders';
|
import { glob } from 'astro/loaders';
|
||||||
import { z } from 'astro/zod';
|
import { z } from 'astro/zod';
|
||||||
|
|
||||||
const safeUrl = z.string().refine(
|
function isRootRelativeUrl(url: string) {
|
||||||
|
return url.startsWith('/') && !url.startsWith('//');
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkUrl = z.string().refine(
|
||||||
(url) => {
|
(url) => {
|
||||||
if (url.startsWith('/')) return !url.startsWith('//');
|
if (isRootRelativeUrl(url)) return true;
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(url);
|
const parsed = new URL(url);
|
||||||
return ['http:', 'https:', 'mailto:'].includes(parsed.protocol);
|
return ['https:', 'mailto:'].includes(parsed.protocol);
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ message: 'URL must be an absolute http(s)/mailto URL or a root-relative path.' }
|
{ message: 'URL must be an absolute https/mailto URL or a root-relative path.' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const mediaUrl = z.string().refine(
|
||||||
|
(url) => {
|
||||||
|
if (isRootRelativeUrl(url)) return true;
|
||||||
|
try {
|
||||||
|
return new URL(url).protocol === 'https:';
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ message: 'Media URL must be an absolute https URL or a root-relative path.' }
|
||||||
);
|
);
|
||||||
|
|
||||||
const linkSchema = z.object({
|
const linkSchema = z.object({
|
||||||
label: z.string(),
|
label: z.string(),
|
||||||
url: safeUrl,
|
url: linkUrl,
|
||||||
download: z.boolean().optional(),
|
download: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -43,8 +59,10 @@ const mediaSchema = ({ image }: SchemaContext) =>
|
||||||
.object({
|
.object({
|
||||||
type: z.literal('video'),
|
type: z.literal('video'),
|
||||||
poster: image().optional(),
|
poster: image().optional(),
|
||||||
mp4: safeUrl.optional(),
|
mp4: mediaUrl.optional(),
|
||||||
webm: safeUrl.optional(),
|
webm: mediaUrl.optional(),
|
||||||
|
captions: mediaUrl.optional(),
|
||||||
|
captionsLabel: z.string().default('English captions'),
|
||||||
alt: z.string().optional(),
|
alt: z.string().optional(),
|
||||||
decorative: z.boolean().optional(),
|
decorative: z.boolean().optional(),
|
||||||
caption: z.string().optional(),
|
caption: z.string().optional(),
|
||||||
|
|
@ -56,7 +74,19 @@ const mediaSchema = ({ image }: SchemaContext) =>
|
||||||
])
|
])
|
||||||
.refine((item) => item.decorative || (Boolean(item.alt) && Boolean(item.caption)), {
|
.refine((item) => item.decorative || (Boolean(item.alt) && Boolean(item.caption)), {
|
||||||
message: 'Meaningful media needs both alt text and a caption.',
|
message: 'Meaningful media needs both alt text and a caption.',
|
||||||
});
|
})
|
||||||
|
.refine(
|
||||||
|
(item) => item.type !== 'video' || item.decorative || Boolean(item.captions),
|
||||||
|
{
|
||||||
|
message: 'Meaningful video needs captions.',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.refine(
|
||||||
|
(item) => item.type !== 'video' || item.decorative || Boolean(item.transcript),
|
||||||
|
{
|
||||||
|
message: 'Meaningful video needs a transcript.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const posts = defineCollection({
|
const posts = defineCollection({
|
||||||
loader: glob({ pattern: '**/*.md', base: './src/content/posts' }),
|
loader: glob({ pattern: '**/*.md', base: './src/content/posts' }),
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,10 @@ media:
|
||||||
poster: ./_assets/ad-astra.jpg
|
poster: ./_assets/ad-astra.jpg
|
||||||
webm: /media/video/ad_astra.webm
|
webm: /media/video/ad_astra.webm
|
||||||
mp4: /media/video/ad_astra.mp4
|
mp4: /media/video/ad_astra.mp4
|
||||||
|
captions: /media/video/ad_astra.vtt
|
||||||
alt: Video demonstration of the embedded game running on a small OLED display.
|
alt: Video demonstration of the embedded game running on a small OLED display.
|
||||||
caption: The game engine ran on an ATtiny85V with an OLED display and IR input.
|
caption: The game engine ran on an ATtiny85V with an OLED display and IR input.
|
||||||
|
transcript: No spoken dialogue. The demonstration shows the Ad Astra handheld board running its OLED game, with the player moving through the small display while the IR input controls gameplay.
|
||||||
---
|
---
|
||||||
|
|
||||||
Ad Astra came from wanting to combine graphics and microcontrollers without hiding behind a large development board. The result was a small embedded game engine and game built around an ATtiny85V, an OLED display, IR input, EEPROM persistence, and a custom PCB.
|
Ad Astra came from wanting to combine graphics and microcontrollers without hiding behind a large development board. The result was a small embedded game engine and game built around an ATtiny85V, an OLED display, IR input, EEPROM persistence, and a custom PCB.
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ audience: technical
|
||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
url: https://github.com/schmelczer/decla.red
|
url: https://github.com/schmelczer/decla.red
|
||||||
- label: Demo
|
|
||||||
url: https://decla.red
|
|
||||||
- label: BSc thesis
|
- label: BSc thesis
|
||||||
url: /media/downloads/sdf2d-andras-schmelczer.pdf
|
url: /media/downloads/sdf2d-andras-schmelczer.pdf
|
||||||
download: true
|
download: true
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,6 @@ audience: recruiter-relevant
|
||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
url: https://github.com/schmelczer/life-towers/
|
url: https://github.com/schmelczer/life-towers/
|
||||||
- label: Demo
|
|
||||||
url: https://towers.schmelczer.dev
|
|
||||||
media:
|
media:
|
||||||
- type: image
|
- type: image
|
||||||
src: ./_assets/towers.jpg
|
src: ./_assets/towers.jpg
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ role: Site generator author
|
||||||
stack: ['Webpack', 'Image processing', 'Static site generation']
|
stack: ['Webpack', 'Image processing', 'Static site generation']
|
||||||
outcome: A generated static photo site for publishing photography with responsive image output
|
outcome: A generated static photo site for publishing photography with responsive image output
|
||||||
audience: general
|
audience: general
|
||||||
links:
|
links: []
|
||||||
- label: Site
|
|
||||||
url: https://photo.schmelczer.dev
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Photos was a small webpage where you could view my photos.
|
Photos was a small webpage where you could view my photos.
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,6 @@ audience: recruiter-relevant
|
||||||
links:
|
links:
|
||||||
- label: NPM package
|
- label: NPM package
|
||||||
url: https://www.npmjs.com/package/sdf-2d
|
url: https://www.npmjs.com/package/sdf-2d
|
||||||
- label: Demo
|
|
||||||
url: https://sdf2d.schmelczer.dev
|
|
||||||
- label: Video
|
- label: Video
|
||||||
url: https://www.youtube.com/watch?v=K3cEtnZUNR0
|
url: https://www.youtube.com/watch?v=K3cEtnZUNR0
|
||||||
- label: BSc thesis
|
- label: BSc thesis
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ essay: declared-shared-simulation-code
|
||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
url: https://github.com/schmelczer/decla.red
|
url: https://github.com/schmelczer/decla.red
|
||||||
- label: Demo
|
|
||||||
url: https://decla.red
|
|
||||||
- label: BSc thesis
|
- label: BSc thesis
|
||||||
url: /media/downloads/sdf2d-andras-schmelczer.pdf
|
url: /media/downloads/sdf2d-andras-schmelczer.pdf
|
||||||
download: true
|
download: true
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,5 @@ sortDate: 2016-07-01
|
||||||
technologies: ['Webpack', 'Image processing', 'Static site generation']
|
technologies: ['Webpack', 'Image processing', 'Static site generation']
|
||||||
selected: false
|
selected: false
|
||||||
essay: photo-site-generator
|
essay: photo-site-generator
|
||||||
links:
|
links: []
|
||||||
- label: Site
|
|
||||||
url: https://photo.schmelczer.dev
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ essay: sdf-2d-ray-tracing
|
||||||
links:
|
links:
|
||||||
- label: NPM package
|
- label: NPM package
|
||||||
url: https://www.npmjs.com/package/sdf-2d
|
url: https://www.npmjs.com/package/sdf-2d
|
||||||
- label: Demo
|
|
||||||
url: https://sdf2d.schmelczer.dev
|
|
||||||
- label: Video
|
- label: Video
|
||||||
url: https://www.youtube.com/watch?v=K3cEtnZUNR0
|
url: https://www.youtube.com/watch?v=K3cEtnZUNR0
|
||||||
- label: BSc thesis
|
- label: BSc thesis
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,4 @@ essay: life-towers-immutable-tries
|
||||||
links:
|
links:
|
||||||
- label: Source
|
- label: Source
|
||||||
url: https://github.com/schmelczer/life-towers/
|
url: https://github.com/schmelczer/life-towers/
|
||||||
- label: Demo
|
|
||||||
url: https://towers.schmelczer.dev
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
adjacentPosts,
|
adjacentPosts,
|
||||||
articlePath,
|
articlePath,
|
||||||
buildBreadcrumbJsonLd,
|
buildBreadcrumbJsonLd,
|
||||||
|
buildPersonJsonLd,
|
||||||
buildBreadcrumbTrail,
|
buildBreadcrumbTrail,
|
||||||
formatDate,
|
formatDate,
|
||||||
getPublishedPosts,
|
getPublishedPosts,
|
||||||
|
|
@ -72,6 +73,7 @@ const blogPosting = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const breadcrumbJsonLd = buildBreadcrumbJsonLd(trail);
|
const breadcrumbJsonLd = buildBreadcrumbJsonLd(trail);
|
||||||
|
const personJsonLd = buildPersonJsonLd();
|
||||||
---
|
---
|
||||||
|
|
||||||
<Base
|
<Base
|
||||||
|
|
@ -89,7 +91,7 @@ const breadcrumbJsonLd = buildBreadcrumbJsonLd(trail);
|
||||||
modifiedTime: post.data.updated?.toISOString(),
|
modifiedTime: post.data.updated?.toISOString(),
|
||||||
tags: post.data.tags,
|
tags: post.data.tags,
|
||||||
}}
|
}}
|
||||||
jsonLd={[blogPosting, breadcrumbJsonLd]}
|
jsonLd={[blogPosting, breadcrumbJsonLd, personJsonLd]}
|
||||||
>
|
>
|
||||||
<article class="post">
|
<article class="post">
|
||||||
<header class="post-header">
|
<header class="post-header">
|
||||||
|
|
@ -126,7 +128,7 @@ const breadcrumbJsonLd = buildBreadcrumbJsonLd(trail);
|
||||||
alt={post.data.thumbnail.alt}
|
alt={post.data.thumbnail.alt}
|
||||||
formats={['avif', 'webp']}
|
formats={['avif', 'webp']}
|
||||||
fallbackFormat="jpg"
|
fallbackFormat="jpg"
|
||||||
widths={[640, 960, 1280, 1600, 1920, 2400]}
|
widths={[640, 960, 1280, 1600, 1920]}
|
||||||
sizes="(max-width: 700px) calc(100vw - 3rem), (max-width: 1100px) calc(100vw - 4rem), 56rem"
|
sizes="(max-width: 700px) calc(100vw - 3rem), (max-width: 1100px) calc(100vw - 4rem), 56rem"
|
||||||
loading="eager"
|
loading="eager"
|
||||||
fetchpriority="high"
|
fetchpriority="high"
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,11 @@ const jsonLd = [blogJsonLd, breadcrumbJsonLd];
|
||||||
return (
|
return (
|
||||||
<section class="archive-year">
|
<section class="archive-year">
|
||||||
<h2 id={`year-${year}`}>{year}</h2>
|
<h2 id={`year-${year}`}>{year}</h2>
|
||||||
<ArticleList posts={postsForYear} showYear={false} />
|
<ArticleList
|
||||||
|
posts={postsForYear}
|
||||||
|
showYear={false}
|
||||||
|
eagerFirstThumbnail={year === years[0]}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ const jsonLd = [collectionJsonLd, breadcrumbJsonLd];
|
||||||
<Page title="Projects" description={description} jsonLd={jsonLd}>
|
<Page title="Projects" description={description} jsonLd={jsonLd}>
|
||||||
<section class="project-section">
|
<section class="project-section">
|
||||||
<h2 id="selected-projects">Selected Projects</h2>
|
<h2 id="selected-projects">Selected Projects</h2>
|
||||||
<ProjectList projects={selected} />
|
<ProjectList projects={selected} eagerFirstThumbnail />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="project-section">
|
<section class="project-section">
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ function absolutizeUrls(html: string, baseUrl: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GET: APIRoute = async (context) => {
|
export const GET: APIRoute = async () => {
|
||||||
const posts = await getPublishedPosts();
|
const posts = await getPublishedPosts();
|
||||||
const feedUrl = absoluteUrl('/rss.xml');
|
const feedUrl = absoluteUrl('/rss.xml');
|
||||||
const channelImage = await optimizeOgImage(ogDefault);
|
const channelImage = await optimizeOgImage(ogDefault);
|
||||||
|
|
@ -82,7 +82,7 @@ export const GET: APIRoute = async (context) => {
|
||||||
return rss({
|
return rss({
|
||||||
title: site.name,
|
title: site.name,
|
||||||
description: site.description,
|
description: site.description,
|
||||||
site: context.site ?? site.url,
|
site: site.url,
|
||||||
xmlns: {
|
xmlns: {
|
||||||
atom: 'http://www.w3.org/2005/Atom',
|
atom: 'http://www.w3.org/2005/Atom',
|
||||||
content: 'http://purl.org/rss/1.0/modules/content/',
|
content: 'http://purl.org/rss/1.0/modules/content/',
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ const breadcrumbJsonLd = buildBreadcrumbJsonLd(trail);
|
||||||
title={title}
|
title={title}
|
||||||
description={`Project articles and technical notes filed under #${tag}.`}
|
description={`Project articles and technical notes filed under #${tag}.`}
|
||||||
jsonLd={breadcrumbJsonLd}
|
jsonLd={breadcrumbJsonLd}
|
||||||
|
noindex
|
||||||
>
|
>
|
||||||
<Breadcrumbs slot="breadcrumbs" items={visibleTrail} />
|
<Breadcrumbs slot="breadcrumbs" items={visibleTrail} />
|
||||||
<nav class="tag-filter" aria-label="Browse other tags">
|
<nav class="tag-filter" aria-label="Browse other tags">
|
||||||
|
|
@ -44,5 +45,5 @@ const breadcrumbJsonLd = buildBreadcrumbJsonLd(trail);
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<h2 class="sr-only">Articles</h2>
|
<h2 class="sr-only">Articles</h2>
|
||||||
<ArticleList posts={filteredPosts} currentTag={tag} eagerFirstThumbnail />
|
<ArticleList posts={filteredPosts} eagerFirstThumbnail />
|
||||||
</Page>
|
</Page>
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,11 @@
|
||||||
document.documentElement.classList.add('js');
|
document.documentElement.classList.add('js');
|
||||||
|
|
||||||
var STORAGE_KEY = 'theme';
|
var STORAGE_KEY = 'theme';
|
||||||
var LEGACY_KEY = 'dark-mode';
|
|
||||||
var THEME_BG = { light: '#fbfaf7', dark: '#151514' };
|
var THEME_BG = { light: '#fbfaf7', dark: '#151514' };
|
||||||
var saved = null;
|
var saved = null;
|
||||||
try {
|
try {
|
||||||
var value = localStorage.getItem(STORAGE_KEY);
|
var value = localStorage.getItem(STORAGE_KEY);
|
||||||
if (value === 'light' || value === 'dark') {
|
if (value === 'light' || value === 'dark') saved = value;
|
||||||
saved = value;
|
|
||||||
} else {
|
|
||||||
var legacy = localStorage.getItem(LEGACY_KEY);
|
|
||||||
if (legacy !== null) saved = JSON.parse(legacy) ? 'dark' : 'light';
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
var theme = saved || (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
var theme = saved || (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||||
document.documentElement.dataset.theme = theme;
|
document.documentElement.dataset.theme = theme;
|
||||||
|
|
|
||||||
|
|
@ -358,6 +358,13 @@
|
||||||
margin-inline: calc(-1 * var(--space-1));
|
margin-inline: calc(-1 * var(--space-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer-contact {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-2) var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
/* Page header (shared by .home-intro, .page-header, .post-header) */
|
/* Page header (shared by .home-intro, .page-header, .post-header) */
|
||||||
.home-intro {
|
.home-intro {
|
||||||
max-width: var(--measure-wide);
|
max-width: var(--measure-wide);
|
||||||
|
|
@ -470,10 +477,17 @@
|
||||||
font-size: var(--fs-caption);
|
font-size: var(--fs-caption);
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumbs li + li::before {
|
.breadcrumbs li {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: baseline;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumbs li:not(:last-child)::after {
|
||||||
content: '›';
|
content: '›';
|
||||||
margin-right: var(--space-2);
|
margin-left: var(--space-2);
|
||||||
color: var(--color-rule-medium);
|
color: var(--color-rule-medium);
|
||||||
|
flex: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumbs a {
|
.breadcrumbs a {
|
||||||
|
|
@ -538,7 +552,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-list a:hover,
|
.tag-list a:hover,
|
||||||
.tag-list a[aria-current='true'] {
|
.tag-list a[aria-current='page'] {
|
||||||
color: var(--color-fg);
|
color: var(--color-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -601,6 +615,12 @@
|
||||||
padding-right: var(--space-3);
|
padding-right: var(--space-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.article-list h3,
|
||||||
|
.project-list h3 {
|
||||||
|
font-size: var(--fs-base);
|
||||||
|
line-height: var(--leading-snug);
|
||||||
|
}
|
||||||
|
|
||||||
.article-list .entry-title,
|
.article-list .entry-title,
|
||||||
.project-list h3 a {
|
.project-list h3 a {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
@ -750,11 +770,6 @@
|
||||||
vertical-align: 0.15em;
|
vertical-align: 0.15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-list h3 {
|
|
||||||
font-size: var(--fs-base);
|
|
||||||
line-height: var(--leading-snug);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- Project links ---------------------------------------------------- */
|
/* -- Project links ---------------------------------------------------- */
|
||||||
|
|
||||||
.project-links {
|
.project-links {
|
||||||
|
|
@ -768,6 +783,7 @@
|
||||||
|
|
||||||
.project-links a {
|
.project-links a {
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
|
min-width: 44px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--color-link);
|
color: var(--color-link);
|
||||||
|
|
@ -791,7 +807,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-card .project-links a {
|
.project-card .project-links a {
|
||||||
min-height: 2.25rem;
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -- Post layout ------------------------------------------------------ */
|
/* -- Post layout ------------------------------------------------------ */
|
||||||
|
|
@ -914,7 +930,7 @@
|
||||||
font-weight: var(--weight-regular);
|
font-weight: var(--weight-regular);
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
opacity: 0.4;
|
opacity: 0;
|
||||||
transition: opacity 150ms ease;
|
transition: opacity 150ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -936,7 +952,7 @@
|
||||||
|
|
||||||
@media (hover: none) {
|
@media (hover: none) {
|
||||||
.prose .heading-anchor {
|
.prose .heading-anchor {
|
||||||
opacity: 0.6;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1151,6 +1167,11 @@
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-transcript strong {
|
||||||
|
color: var(--color-fg);
|
||||||
|
font-weight: var(--weight-semibold);
|
||||||
|
}
|
||||||
|
|
||||||
/* -- Post nav --------------------------------------------------------- */
|
/* -- Post nav --------------------------------------------------------- */
|
||||||
|
|
||||||
.post-nav {
|
.post-nav {
|
||||||
|
|
@ -1223,6 +1244,11 @@
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-card h3 a {
|
||||||
|
min-height: 44px;
|
||||||
|
min-width: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
.post-toc ol {
|
.post-toc ol {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
@ -1309,10 +1335,8 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: var(--switcher-w);
|
width: var(--switcher-w);
|
||||||
height: var(--switcher-h);
|
height: var(--switcher-h);
|
||||||
/* Vertical margin enlarges the comfortable click target to 44px while
|
/* Adjacent header targets remain at least 44px apart while the visual
|
||||||
keeping the visual track at 24px. Hit area is the button's box;
|
track stays compact. */
|
||||||
margin is not clickable, but combined with header gap it ensures
|
|
||||||
adequate spacing between adjacent targets. */
|
|
||||||
margin: max(var(--space-2), calc((44px - var(--switcher-h)) / 2)) 0;
|
margin: max(var(--space-2), calc((44px - var(--switcher-h)) / 2)) 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid var(--color-rule-medium);
|
border: 1px solid var(--color-rule-medium);
|
||||||
|
|
@ -1384,24 +1408,36 @@
|
||||||
.theme-switcher {
|
.theme-switcher {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
min-block-size: 44px;
|
||||||
|
min-inline-size: 44px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
padding: var(--space-1) var(--space-2);
|
padding: var(--space-1) var(--space-2);
|
||||||
|
overflow: visible;
|
||||||
background: ButtonFace;
|
background: ButtonFace;
|
||||||
color: ButtonText;
|
color: ButtonText;
|
||||||
border: 1px solid ButtonBorder;
|
border: 1px solid ButtonBorder;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-switcher::before,
|
|
||||||
.theme-switcher::after {
|
.theme-switcher::after {
|
||||||
content: none;
|
content: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-switcher::before {
|
.theme-switcher::before {
|
||||||
content: 'Light';
|
content: 'Light';
|
||||||
|
position: static;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
transform: none;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-switcher[aria-pressed='true']::before {
|
.theme-switcher[aria-pressed='true']::before {
|
||||||
content: 'Dark';
|
content: 'Dark';
|
||||||
|
transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1449,12 +1485,29 @@
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.article-list time {
|
||||||
|
text-align: start;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.article-list .entry-thumbnail {
|
.article-list .entry-thumbnail {
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-card {
|
.project-card {
|
||||||
--project-thumb-size: 7rem;
|
--project-thumb-size: 7rem;
|
||||||
|
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-areas:
|
||||||
|
'thumb'
|
||||||
|
'summary';
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-card .project-thumbnail {
|
||||||
|
height: auto;
|
||||||
|
border-right: 0;
|
||||||
|
border-bottom: 1px solid var(--color-rule);
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-card .project-meta {
|
.project-card .project-meta {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue