Added Masthead, CMDPalette and PostHeader Components
This commit is contained in:
@@ -4,14 +4,22 @@ import markdoc from '@astrojs/markdoc';
|
||||
import keystatic from '@keystatic/astro';
|
||||
import node from '@astrojs/node';
|
||||
|
||||
|
||||
import preact from '@astrojs/preact';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
output: 'static',
|
||||
integrations: [react(), markdoc(), keystatic()],
|
||||
integrations: [react(), markdoc(), keystatic(), preact()],
|
||||
|
||||
adapter: node({
|
||||
mode: 'standalone',
|
||||
}),
|
||||
vite: {
|
||||
ssr: {
|
||||
noExternal: ['lodash'],
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['react', 'react-dom', 'react/jsx-runtime'],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,13 +1,21 @@
|
||||
import { config } from '@keystatic/core';
|
||||
import { articles } from './src/keystatic/collections/articles';
|
||||
import { pages } from './src/keystatic/collections/pages';
|
||||
import crucibleCollections from './src/keystatic/collections/crucible';
|
||||
|
||||
export default config({
|
||||
storage: {
|
||||
kind: 'local',
|
||||
},
|
||||
ui: {
|
||||
navigation: {
|
||||
General: ['articles', 'pages'],
|
||||
Crucible: ['cr_elements'],
|
||||
},
|
||||
},
|
||||
collections: {
|
||||
articles,
|
||||
pages,
|
||||
...crucibleCollections,
|
||||
},
|
||||
});
|
||||
|
||||
14
markdoc.config.mjs
Normal file
14
markdoc.config.mjs
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineMarkdocConfig, component } from '@astrojs/markdoc/config';
|
||||
|
||||
export default defineMarkdocConfig({
|
||||
tags: {
|
||||
ElementSymbol: {
|
||||
render: component('./src/components/content/ElementSymbol.astro'),
|
||||
attributes: {
|
||||
element: { type: String },
|
||||
size: { type: String },
|
||||
color: { type: String },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
520
package-lock.json
generated
520
package-lock.json
generated
@@ -11,15 +11,18 @@
|
||||
"dependencies": {
|
||||
"@astrojs/markdoc": "^0.12.9",
|
||||
"@astrojs/node": "^9.5.4",
|
||||
"@astrojs/preact": "^4.1.3",
|
||||
"@astrojs/react": "^4.2.0",
|
||||
"@fontsource-variable/geist": "^5.2.8",
|
||||
"@fontsource-variable/geist-mono": "^5.2.7",
|
||||
"@fontsource/blaka": "^5.2.7",
|
||||
"@keystar/ui": "^0.7.19",
|
||||
"@keystatic/astro": "^5.0.6",
|
||||
"@keystatic/core": "^0.5.48",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"astro": "^5.2.5",
|
||||
"preact": "^10.28.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
@@ -29,11 +32,16 @@
|
||||
"@typescript-eslint/parser": "^8.56.0",
|
||||
"eslint": "^10.0.0",
|
||||
"eslint-plugin-astro": "^1.6.0",
|
||||
"postcss-html": "^1.8.1",
|
||||
"postcss-import": "^16.1.1",
|
||||
"postcss-mixins": "^12.1.2",
|
||||
"postcss-preset-env": "^11.1.3",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"stylelint": "^17.3.0",
|
||||
"stylelint-config-astro": "^2.0.0",
|
||||
"stylelint-config-clean-order": "^8.0.1",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-standard": "^40.0.0",
|
||||
"stylelint-order": "^7.0.1"
|
||||
}
|
||||
@@ -134,6 +142,24 @@
|
||||
"integrity": "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@astrojs/preact": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/preact/-/preact-4.1.3.tgz",
|
||||
"integrity": "sha512-Ph416wbgyumkmYr7erZ83l/d+LXdZethlHRRCbgoKEn8wo3Rkq13shKFp0QYXYSDYxVaA6UBdkdimeowy/lMLQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@preact/preset-vite": "^2.10.2",
|
||||
"@preact/signals": "^2.3.1",
|
||||
"preact-render-to-string": "^6.6.1",
|
||||
"vite": "^6.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18.20.8 || ^20.3.0 || >=22.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"preact": "^10.6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/prism": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.2.0.tgz",
|
||||
@@ -259,6 +285,18 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-annotate-as-pure": {
|
||||
"version": "7.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
|
||||
"integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.27.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
|
||||
@@ -387,6 +425,55 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-jsx": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
|
||||
"integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-react-jsx": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz",
|
||||
"integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.27.3",
|
||||
"@babel/helper-module-imports": "^7.28.6",
|
||||
"@babel/helper-plugin-utils": "^7.28.6",
|
||||
"@babel/plugin-syntax-jsx": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-react-jsx-development": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz",
|
||||
"integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-react-jsx": "^7.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-react-jsx-self": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
|
||||
@@ -3667,6 +3754,121 @@
|
||||
"url": "https://opencollective.com/pkgr"
|
||||
}
|
||||
},
|
||||
"node_modules/@preact/preset-vite": {
|
||||
"version": "2.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.10.3.tgz",
|
||||
"integrity": "sha512-1SiS+vFItpkNdBs7q585PSAIln0wBeBdcpJYbzPs1qipsb/FssnkUioNXuRsb8ZnU8YEQHr+3v8+/mzWSnTQmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-react-jsx": "^7.27.1",
|
||||
"@babel/plugin-transform-react-jsx-development": "^7.27.1",
|
||||
"@prefresh/vite": "^2.4.11",
|
||||
"@rollup/pluginutils": "^5.0.0",
|
||||
"babel-plugin-transform-hook-names": "^1.0.2",
|
||||
"debug": "^4.4.3",
|
||||
"picocolors": "^1.1.1",
|
||||
"vite-prerender-plugin": "^0.5.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "7.x",
|
||||
"vite": "2.x || 3.x || 4.x || 5.x || 6.x || 7.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@preact/signals": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@preact/signals/-/signals-2.8.1.tgz",
|
||||
"integrity": "sha512-wX6U0SpcCukZTJBs5ChljvBZb3XmYzA5gd4vKHgX8wZZKaQCo2WHtmThdLx+mcVvlBa5u3XShC7ffbETJD4BiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@preact/signals-core": "^1.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"preact": ">= 10.25.0 || >=11.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@preact/signals-core": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.13.0.tgz",
|
||||
"integrity": "sha512-slT6XeTCAbdql61GVLlGU4x7XHI7kCZV5Um5uhE4zLX4ApgiiXc0UYFvVOKq06xcovzp7p+61l68oPi563ARKg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/@prefresh/babel-plugin": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@prefresh/babel-plugin/-/babel-plugin-0.5.3.tgz",
|
||||
"integrity": "sha512-57LX2SHs4BX2s1IwCjNzTE2OJeEepRCNf1VTEpbNcUyHfMO68eeOWGDIt4ob9aYlW6PEWZ1SuwNikuoIXANDtQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@prefresh/core": {
|
||||
"version": "1.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@prefresh/core/-/core-1.5.9.tgz",
|
||||
"integrity": "sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"preact": "^10.0.0 || ^11.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prefresh/utils": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@prefresh/utils/-/utils-1.2.1.tgz",
|
||||
"integrity": "sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@prefresh/vite": {
|
||||
"version": "2.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@prefresh/vite/-/vite-2.4.12.tgz",
|
||||
"integrity": "sha512-FY1fzXpUjiuosznMV0YM7XAOPZjB5FIdWS0W24+XnlxYkt9hNAwwsiKYn+cuTEoMtD/ZVazS5QVssBr9YhpCQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.22.1",
|
||||
"@prefresh/babel-plugin": "^0.5.2",
|
||||
"@prefresh/core": "^1.5.0",
|
||||
"@prefresh/utils": "^1.2.0",
|
||||
"@rollup/pluginutils": "^4.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"preact": "^10.4.0 || ^11.0.0-0",
|
||||
"vite": ">=2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prefresh/vite/node_modules/@rollup/pluginutils": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
|
||||
"integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"estree-walker": "^2.0.1",
|
||||
"picomatch": "^2.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prefresh/vite/node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@prefresh/vite/node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/actiongroup": {
|
||||
"version": "3.7.23",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/actiongroup/-/actiongroup-3.7.23.tgz",
|
||||
@@ -7369,6 +7571,15 @@
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-transform-hook-names": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz",
|
||||
"integrity": "sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.12.10"
|
||||
}
|
||||
},
|
||||
"node_modules/bail": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
||||
@@ -7528,6 +7739,16 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase-css": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001770",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz",
|
||||
@@ -9333,6 +9554,15 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/hookified": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz",
|
||||
@@ -9832,6 +10062,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/kolorist": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
|
||||
"integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
@@ -11170,6 +11406,16 @@
|
||||
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-html-parser": {
|
||||
"version": "6.1.13",
|
||||
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz",
|
||||
"integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-select": "^5.1.0",
|
||||
"he": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-mock-http": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz",
|
||||
@@ -11923,6 +12169,79 @@
|
||||
"postcss": "^8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-html": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.8.1.tgz",
|
||||
"integrity": "sha512-OLF6P7qctfAWayOhLpcVnTGqVeJzu2W3WpIYelfz2+JV5oGxfkcEvweN9U4XpeqE0P98dcD9ssusGwlF0TK0uQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"htmlparser2": "^8.0.0",
|
||||
"js-tokens": "^9.0.0",
|
||||
"postcss": "^8.5.0",
|
||||
"postcss-safe-parser": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-html/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-html/node_modules/htmlparser2": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
||||
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1",
|
||||
"entities": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-html/node_modules/js-tokens": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
|
||||
"integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/postcss-html/node_modules/postcss-safe-parser": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz",
|
||||
"integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-image-set-function": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-8.0.0.tgz",
|
||||
@@ -11968,6 +12287,32 @@
|
||||
"postcss": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-js": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
|
||||
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"camelcase-css": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12 || ^14 || >= 16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.4.21"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-lab-function": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-8.0.1.tgz",
|
||||
@@ -12024,6 +12369,35 @@
|
||||
"postcss": "^8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-mixins": {
|
||||
"version": "12.1.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-mixins/-/postcss-mixins-12.1.2.tgz",
|
||||
"integrity": "sha512-90pSxmZVfbX9e5xCv7tI5RV1mnjdf16y89CJKbf/hD7GyOz1FCxcYMl8ZYA8Hc56dbApTKKmU9HfvgfWdCxlwg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-js": "^4.0.1",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"sugarss": "^5.0.0",
|
||||
"tinyglobby": "^0.2.14"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.0 || ^22.0 || >=24.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.2.14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-nesting": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-14.0.0.tgz",
|
||||
@@ -12337,6 +12711,23 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-simple-vars": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz",
|
||||
"integrity": "sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-sorting": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-9.1.0.tgz",
|
||||
@@ -12372,6 +12763,25 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/preact": {
|
||||
"version": "10.28.4",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.28.4.tgz",
|
||||
"integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/preact-render-to-string": {
|
||||
"version": "6.6.6",
|
||||
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.6.6.tgz",
|
||||
"integrity": "sha512-EfqZJytnjJldV+YaaqhthU2oXsEf5e+6rDv957p+zxAvNfFLQOPfvBOTncscQ+akzu6Wrl7s3Pa0LjUQmWJsGQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"preact": ">=10 || >= 11.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
@@ -13205,6 +13615,15 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-code-frame": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-code-frame/-/simple-code-frame-1.3.0.tgz",
|
||||
"integrity": "sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"kolorist": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sisteransi": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||
@@ -13364,6 +13783,15 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-trace": {
|
||||
"version": "1.0.0-pre2",
|
||||
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz",
|
||||
"integrity": "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
@@ -13483,6 +13911,48 @@
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-config-astro": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-astro/-/stylelint-config-astro-2.0.0.tgz",
|
||||
"integrity": "sha512-BSl+wNEa3h1+GhHAfI3WO/fPylcVoePLIMd+JX1hz1Pt2cnqRswjfA4EqD6Wy2DqrariqYJE1xXZCnuJNrjb8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss-html": "^1.0.0",
|
||||
"stylelint": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-config-clean-order": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-clean-order/-/stylelint-config-clean-order-8.0.1.tgz",
|
||||
"integrity": "sha512-zKjp7BiINXRZOG9m0fE/6UKoM6clPekL+LoAiHMCiQU2hgirKL5G0mKc5Z0ygIhQXfb1+DTRDM0mu6Ecdv4q8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"stylelint": ">=16",
|
||||
"stylelint-order": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-config-html": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-html/-/stylelint-config-html-1.1.0.tgz",
|
||||
"integrity": "sha512-IZv4IVESjKLumUGi+HWeb7skgO6/g4VMuAYrJdlqQFndgbj6WJAXPhaysvBiXefX79upBdQVumgYcdd17gCpjQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12 || >=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ota-meshi"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss-html": "^1.0.0",
|
||||
"stylelint": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-config-recommended": {
|
||||
"version": "18.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-18.0.0.tgz",
|
||||
@@ -13651,6 +14121,29 @@
|
||||
"s.color": "0.0.15"
|
||||
}
|
||||
},
|
||||
"node_modules/sugarss": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sugarss/-/sugarss-5.0.1.tgz",
|
||||
"integrity": "sha512-ctS5RYCBVvPoZAnzIaX5QSShK8ZiZxD5HUqSxlusvEMC+QZQIPCPOIJg6aceFX+K2rf4+SH89eu++h1Zmsr2nw==",
|
||||
"devOptional": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/superstruct": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.4.tgz",
|
||||
@@ -13995,7 +14488,6 @@
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -14492,6 +14984,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-prerender-plugin": {
|
||||
"version": "0.5.12",
|
||||
"resolved": "https://registry.npmjs.org/vite-prerender-plugin/-/vite-prerender-plugin-0.5.12.tgz",
|
||||
"integrity": "sha512-EiwhbMn+flg14EysbLTmZSzq8NGTxhytgK3bf4aGRF1evWLGwZiHiUJ1KZDvbxgKbMf2pG6fJWGEa3UZXOnR1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"kolorist": "^1.8.0",
|
||||
"magic-string": "0.x >= 0.26.0",
|
||||
"node-html-parser": "^6.1.12",
|
||||
"simple-code-frame": "^1.3.0",
|
||||
"source-map": "^0.7.4",
|
||||
"stack-trace": "^1.0.0-pre2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "5.x || 6.x || 7.x"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-prerender-plugin/node_modules/source-map": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
||||
"integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/vitefu": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz",
|
||||
|
||||
@@ -10,15 +10,18 @@
|
||||
"dependencies": {
|
||||
"@astrojs/markdoc": "^0.12.9",
|
||||
"@astrojs/node": "^9.5.4",
|
||||
"@astrojs/preact": "^4.1.3",
|
||||
"@astrojs/react": "^4.2.0",
|
||||
"@fontsource-variable/geist": "^5.2.8",
|
||||
"@fontsource-variable/geist-mono": "^5.2.7",
|
||||
"@fontsource/blaka": "^5.2.7",
|
||||
"@keystar/ui": "^0.7.19",
|
||||
"@keystatic/astro": "^5.0.6",
|
||||
"@keystatic/core": "^0.5.48",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"astro": "^5.2.5",
|
||||
"preact": "^10.28.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
@@ -35,11 +38,16 @@
|
||||
"@typescript-eslint/parser": "^8.56.0",
|
||||
"eslint": "^10.0.0",
|
||||
"eslint-plugin-astro": "^1.6.0",
|
||||
"postcss-html": "^1.8.1",
|
||||
"postcss-import": "^16.1.1",
|
||||
"postcss-mixins": "^12.1.2",
|
||||
"postcss-preset-env": "^11.1.3",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"stylelint": "^17.3.0",
|
||||
"stylelint-config-astro": "^2.0.0",
|
||||
"stylelint-config-clean-order": "^8.0.1",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-standard": "^40.0.0",
|
||||
"stylelint-order": "^7.0.1"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import postcssGlobalData from '@csstools/postcss-global-data';
|
||||
import postcssImport from 'postcss-import';
|
||||
import postcssPresetEnv from 'postcss-preset-env';
|
||||
import postcssMixins from 'postcss-mixins';
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
postcssGlobalData({
|
||||
files: ['./src/styles/custom-media.css'],
|
||||
files: ['./src/styles/base/custom-media.css'],
|
||||
}),
|
||||
postcssMixins({
|
||||
mixinsDir: './src/styles/mixins/',
|
||||
}),
|
||||
postcssImport(),
|
||||
postcssPresetEnv({
|
||||
|
||||
72
src/components/content/ElementSymbol.astro
Normal file
72
src/components/content/ElementSymbol.astro
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
import { getEntry } from 'astro:content';
|
||||
|
||||
interface Props {
|
||||
element: string;
|
||||
size: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
interface FontSymbol {
|
||||
discriminant: 'font';
|
||||
value: {
|
||||
family: string;
|
||||
character: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface SVGSymbol {
|
||||
discriminant: 'svg';
|
||||
value: string;
|
||||
}
|
||||
|
||||
type Symbol = FontSymbol | SVGSymbol;
|
||||
|
||||
const { element, size, color } = Astro.props;
|
||||
const entry = await getEntry('elements', element);
|
||||
|
||||
if (!entry) {
|
||||
console.warn(`Element not found: ${element}`);
|
||||
}
|
||||
|
||||
const symbol = entry?.data.symbol as Symbol | undefined;
|
||||
---
|
||||
|
||||
{
|
||||
symbol?.discriminant === 'font' && (
|
||||
<span
|
||||
class="element font"
|
||||
style={`font-family: '${(symbol as FontSymbol).value.family}', sans-serif; font-size: ${size}; color: ${color};`}
|
||||
>
|
||||
{(symbol as FontSymbol).value.character}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
symbol?.discriminant === 'svg' && (
|
||||
<span
|
||||
class="element svg"
|
||||
style={`width: ${size}; height: ${size}; color: ${color}`}
|
||||
set:html={(symbol as SVGSymbol).value}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
<style>
|
||||
.font {
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.svg {
|
||||
display: inline-flex;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.svg :global(svg) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
211
src/components/layout/CMDPalette/CMDPalette.module.css
Normal file
211
src/components/layout/CMDPalette/CMDPalette.module.css
Normal file
@@ -0,0 +1,211 @@
|
||||
.trigger {
|
||||
@mixin ml auto;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
gap:var(--ui-spacing-comfortable);
|
||||
align-items: center;
|
||||
|
||||
padding: var(--ui-spacing-snug) var(--ui-spacing-spacious);
|
||||
border: var(--size-px) solid var(--color-border-normal);
|
||||
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--ui-typo-size-md);
|
||||
color: var(--text-color-disabled);
|
||||
|
||||
background: var(--color-palette-charcoal-gray);
|
||||
|
||||
transition: border-color 0.15s, color 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-text-inverse);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
& .kbd {
|
||||
padding: var(--ui-spacing-hairline) var( --ui-spacing-cozy);
|
||||
border: var(--size-px) solid var(--color-border-normal);
|
||||
border-radius: var(--size-05);
|
||||
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--ui-typo-size-xs);
|
||||
|
||||
background: var(--color-surface-inverse);
|
||||
}
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
z-index: 99;
|
||||
inset: 0;
|
||||
background: var(--color-overlay-heavy);
|
||||
}
|
||||
|
||||
.palette {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 20%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
width: min(var(--size-128), 90vw);
|
||||
padding: var(--ui-spacing-tight);
|
||||
border: var(--size-05) solid var(--color-primary);
|
||||
|
||||
background: var(--color-surface-inverse);
|
||||
box-shadow:
|
||||
0 0 0 var(--size-px) var(--color-border-strong),
|
||||
var(--size-1) var(--size-1) 0 var(--color-surface-inverse);
|
||||
}
|
||||
|
||||
.header {
|
||||
@mixin px var(--ui-spacing-generous);
|
||||
@mixin border-b var(--size-px), solid, var(--color-border-strong);
|
||||
|
||||
display: flex;
|
||||
gap: var(--ui-spacing-comfortable);
|
||||
align-items: center;
|
||||
|
||||
& .icon {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--ui-typo-size-lg);
|
||||
font-weight: 900;
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
& .input {
|
||||
@mixin py var(--ui-spacing-generous);
|
||||
|
||||
flex: 1;
|
||||
|
||||
border: none;
|
||||
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--ui-typo-size-lg);
|
||||
color: var(--color-text-inverse);
|
||||
|
||||
background: transparent;
|
||||
outline: none;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
}
|
||||
|
||||
& .esc {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--typo-size-xs);
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
}
|
||||
|
||||
.results {
|
||||
overflow-y: auto;
|
||||
max-height: var(--size-96);
|
||||
|
||||
& .groupLabel {
|
||||
padding: var(--ui-spacing-relaxed) var(--ui-spacing-generous) var(--ui-spacing-snug);
|
||||
|
||||
font-size: var(--ui-typo-size-2xs);
|
||||
font-weight: 700;
|
||||
color: var(--color-text-disabled);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--spacing-loosest);
|
||||
}
|
||||
|
||||
& .result {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
gap: var(--ui-spacing-relaxed);
|
||||
align-items: center;
|
||||
|
||||
padding: var(--ui-spacing-comfortable) var(--ui-spacing-generous);
|
||||
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--ui-typo-size-md);
|
||||
color: var(--color-text-inverse);
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--typo-spacing-relaxed);
|
||||
|
||||
transition: background 0.8s;
|
||||
|
||||
& .type {
|
||||
min-width: var(--size-16);
|
||||
|
||||
font-size: var(--ui-typo-size-2xs);
|
||||
color: var(--color-text-disabled);
|
||||
text-align: right;
|
||||
letter-spacing: var(--typo-spacing-looser);
|
||||
}
|
||||
|
||||
& .label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
& .path {
|
||||
font-size: var(--ui-typo-size-2xs);
|
||||
color: var(--color-text-disabled);
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
& .arrow {
|
||||
font-size: var(--ui-typo-size-xs);
|
||||
color: var(--color-text-disabled);
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.selected {
|
||||
background: var(--color-border-strong);
|
||||
|
||||
& .arrow {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: var(--ui-spacing-luxurious) var(--ui-spacing-generous);
|
||||
|
||||
font-size: var(--ui-typo-size-sm);
|
||||
color: var(--color-text-disabled);
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--typo-spacing-comfortable);
|
||||
}
|
||||
|
||||
.footer {
|
||||
@mixin border-t var(--size-px), solid, var(--color-border-strong);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: var(--ui-spacing-comfortable) var(--ui-spacing-generous);
|
||||
|
||||
font-size: var(--ui-typo-size-xs);
|
||||
color: var(--color-text-disabled);
|
||||
|
||||
& .group {
|
||||
display: flex;
|
||||
gap: var(--ui-spacing-relaxed);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
& kbd {
|
||||
@mixin mx var(--ui-spacing-tight);
|
||||
|
||||
padding: var(--ui-spacing-tight) var(--ui-spacing-snug);
|
||||
border: var(--size-px) solid var(--color-border-normal);
|
||||
border-radius: var(--size-05);
|
||||
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--ui-typo-size-2xs);
|
||||
}
|
||||
}
|
||||
287
src/components/layout/CMDPalette/index.tsx
Normal file
287
src/components/layout/CMDPalette/index.tsx
Normal file
@@ -0,0 +1,287 @@
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'preact/hooks';
|
||||
import type { PaletteEntry, ContentType } from '@lib/types/content';
|
||||
import styles from './CMDPalette.module.css';
|
||||
|
||||
/* CONSTANTS */
|
||||
const MAX_DEFAULT = 20;
|
||||
const MAX_SEARCH = 30;
|
||||
|
||||
const TYPE_LABELS: Record<ContentType, string> = {
|
||||
article: 'Article',
|
||||
element: 'Element',
|
||||
page: 'Page',
|
||||
};
|
||||
|
||||
/* INDEX ENTRY */
|
||||
interface IndexedEntry extends PaletteEntry {
|
||||
_label: string;
|
||||
_parent: string;
|
||||
_type: string;
|
||||
_path: string;
|
||||
}
|
||||
|
||||
const normalize = (str: string): string =>
|
||||
str.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||
|
||||
const indexEntry = (entry: PaletteEntry): IndexedEntry => ({
|
||||
...entry,
|
||||
_label: normalize(entry.label),
|
||||
_parent: normalize(entry.parent ?? ''),
|
||||
_type: normalize(entry.type),
|
||||
_path: normalize(entry.path),
|
||||
});
|
||||
|
||||
/* SCORING */
|
||||
const scoreEntry = (entry: IndexedEntry, q: string): number => {
|
||||
let score = 0;
|
||||
|
||||
if (entry._label === q) score += 100;
|
||||
else if (entry._label.startsWith(q)) score += 80;
|
||||
else if (entry._label.includes(q)) score += 60;
|
||||
if (entry._parent.includes(q)) score += 30;
|
||||
if (entry._type.includes(q)) score += 20;
|
||||
if (entry._path.includes(q)) score += 10;
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
/* RENDERABLE ROW – single-pass from filtered entries */
|
||||
interface RenderRow {
|
||||
kind: 'label' | 'result';
|
||||
key: string;
|
||||
group?: string;
|
||||
entry?: PaletteEntry;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
const buildRows = (entries: PaletteEntry[]): RenderRow[] => {
|
||||
const rows: RenderRow[] = [];
|
||||
let currentGroup = '';
|
||||
let idx = 0;
|
||||
|
||||
for (const entry of entries) {
|
||||
const group = entry.parent ?? TYPE_LABELS[entry.type] ?? 'Other';
|
||||
|
||||
if (group !== currentGroup) {
|
||||
currentGroup = group;
|
||||
rows.push({ kind: 'label', key: `label-${group}`, group });
|
||||
}
|
||||
rows.push({
|
||||
kind: 'result',
|
||||
key: entry.path,
|
||||
entry,
|
||||
index: idx++,
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
/* COMPONENT */
|
||||
export default function CommandPalette() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
const [index, setIndex] = useState<IndexedEntry[]>([]);
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
||||
|
||||
const selectedRef = useRef(-1);
|
||||
const loadingRef = useRef(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const resultsRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
/* Keep ref in sync for stable access in callbacks */
|
||||
useEffect(() => {
|
||||
selectedRef.current = selectedIndex;
|
||||
}, [selectedIndex]);
|
||||
|
||||
/* Load index (ref-guarded, no race conditions) */
|
||||
const loadIndex = useCallback(async () => {
|
||||
if (index.length > 0 || loadingRef.current) return;
|
||||
loadingRef.current = true;
|
||||
|
||||
try {
|
||||
const res = await fetch('/palette-index.json');
|
||||
const data: PaletteEntry[] = await res.json();
|
||||
setIndex(data.map(indexEntry));
|
||||
} catch (err) {
|
||||
console.error('Failed to load palette index:', err);
|
||||
loadingRef.current = false;
|
||||
}
|
||||
}, [index.length]);
|
||||
|
||||
/* OPEN / CLOSE */
|
||||
const open = useCallback(async () => {
|
||||
await loadIndex();
|
||||
setIsOpen(true);
|
||||
setQuery('');
|
||||
setSelectedIndex(-1);
|
||||
}, [loadIndex]);
|
||||
|
||||
const close = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
setSelectedIndex(-1);
|
||||
}, []);
|
||||
|
||||
/* GLOBAL KEYBOARD SHORTCUTS */
|
||||
useEffect(() => {
|
||||
const onKeydown = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
isOpen ? close() : open();
|
||||
}
|
||||
if (e.key === 'Escape' && isOpen) {
|
||||
e.preventDefault();
|
||||
close();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', onKeydown);
|
||||
return () => document.removeEventListener('keydown', onKeydown);
|
||||
}, [isOpen, open, close]);
|
||||
|
||||
/* FOCUS INPUT WHEN OPENING*/
|
||||
useEffect(() => {
|
||||
if (isOpen) requestAnimationFrame(() => inputRef.current?.focus());
|
||||
}, [isOpen]);
|
||||
|
||||
/* FILTERED ENTRIES -> RENDER ROWS */
|
||||
const filtered = useMemo(() => {
|
||||
if (!query.trim()) {
|
||||
return index.filter((e) => e.depth <= 2).slice(0, MAX_DEFAULT);
|
||||
}
|
||||
const q = normalize(query);
|
||||
return index
|
||||
.map((entry) => ({ entry, score: scoreEntry(entry, q) }))
|
||||
.filter((r) => r.score > 0)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, MAX_SEARCH)
|
||||
.map((r) => r.entry);
|
||||
}, [index, query]);
|
||||
|
||||
const rows = useMemo(() => buildRows(filtered), [filtered]);
|
||||
|
||||
/* COUNT OF NAVIGABLE RESULTS (excludes labels) */
|
||||
const resultCount = useMemo(
|
||||
() => rows.filter((r) => r.kind === 'result').length,
|
||||
[rows],
|
||||
);
|
||||
|
||||
/* RESET SELECTION ON QUERY CHANGE */
|
||||
useEffect(() => {
|
||||
setSelectedIndex(-1);
|
||||
}, [query]);
|
||||
|
||||
/*SCROLL SELECTED INTO VIEW */
|
||||
useEffect(() => {
|
||||
if (selectedIndex < 0) return;
|
||||
resultsRef.current
|
||||
?.querySelector(`[data-index="${selectedIndex}"]`)
|
||||
?.scrollIntoView({ block: 'nearest' });
|
||||
}, [selectedIndex]);
|
||||
|
||||
/* INPUT KEYBOARD NAVIGATION */
|
||||
const onInputKeydown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (!resultCount) return;
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
setSelectedIndex((i) => (i + 1) % resultCount);
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
setSelectedIndex((i) => (i <= 0 ? resultCount - 1 : i - 1));
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
const idx = selectedRef.current;
|
||||
const target = rows.find((r) => r.kind === 'result' && r.index === idx);
|
||||
if (target?.entry) {
|
||||
window.location.href = target.entry.path;
|
||||
}
|
||||
}
|
||||
},
|
||||
[resultCount, rows],
|
||||
);
|
||||
|
||||
/* RENDER */
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
class={styles.trigger}
|
||||
onClick={open}
|
||||
aria-label="Open Navigation"
|
||||
>
|
||||
<span>Navigate…</span>
|
||||
<kbd class={styles.kbd}>⌘K</kbd>
|
||||
</button>
|
||||
{isOpen && <div class={styles.backdrop} onClick={close} />}
|
||||
{isOpen && (
|
||||
<div
|
||||
class={styles.palette}
|
||||
role="dialog"
|
||||
aria-label="Navigation palette"
|
||||
>
|
||||
<div class={styles.header}>
|
||||
<span class={styles.icon}>⟩</span>
|
||||
<input
|
||||
ref={inputRef}
|
||||
class={styles.input}
|
||||
type="text"
|
||||
placeholder="Where do you want to go?"
|
||||
autocomplete="off"
|
||||
spellcheck={false}
|
||||
value={query}
|
||||
onInput={(e) => setQuery((e.target as HTMLInputElement).value)}
|
||||
onKeyDown={onInputKeydown}
|
||||
/>
|
||||
<kbd class={styles.esc}>esc</kbd>
|
||||
</div>
|
||||
<div class={styles.results} ref={resultsRef} role="listbox">
|
||||
{rows.map((row) =>
|
||||
row.kind === 'label' ? (
|
||||
<div key={row.key} class={styles.groupLabel}>
|
||||
{row.group}
|
||||
</div>
|
||||
) : (
|
||||
<a
|
||||
key={row.key}
|
||||
class={`${styles.result}${row.index === selectedIndex ? ` ${styles.selected}` : ''}`}
|
||||
href={row.entry!.path}
|
||||
role="option"
|
||||
data-index={row.index}
|
||||
aria-selected={row.index === selectedIndex}
|
||||
>
|
||||
<span class={styles.type}>
|
||||
{TYPE_LABELS[row.entry!.type] ?? row.entry!.type}
|
||||
</span>
|
||||
<span class={styles.label}>{row.entry!.label}</span>
|
||||
<span class={styles.path}>{row.entry!.path}</span>
|
||||
<span class={styles.arrow}>→</span>
|
||||
</a>
|
||||
),
|
||||
)}
|
||||
{resultCount === 0 && (
|
||||
<div class={styles.empty}>No results found</div>
|
||||
)}
|
||||
</div>
|
||||
<div class={styles.footer}>
|
||||
<div class={styles.group}>
|
||||
<span>
|
||||
<kbd>↑</kbd>
|
||||
<kbd>↓</kbd> navigate
|
||||
</span>
|
||||
<span>
|
||||
<kbd>↵</kbd> open
|
||||
</span>
|
||||
</div>
|
||||
<span>
|
||||
<kbd>esc</kbd> close
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
129
src/components/layout/MastHead.astro
Normal file
129
src/components/layout/MastHead.astro
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
import CommandPalette from "./CMDPalette"
|
||||
---
|
||||
|
||||
<header class="site-header">
|
||||
<div class="inner">
|
||||
<a href="/" class="link">
|
||||
<span class="site-logo">◬</span>
|
||||
<span class="site-name">
|
||||
dave
|
||||
<span class="bracket">[</span>
|
||||
dmg
|
||||
<span class="bracket">]</span>
|
||||
</span>
|
||||
</a>
|
||||
<CommandPalette client:load />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
.site-header {
|
||||
@mixin py var(--el-masthead-paddingY);
|
||||
|
||||
position: sticky;
|
||||
z-index: 9;
|
||||
top: 0;
|
||||
|
||||
width: 100%;
|
||||
|
||||
color: var(--color-text-inverse);
|
||||
|
||||
background-color: var(--color-surface-inverse);
|
||||
}
|
||||
|
||||
.inner {
|
||||
@mixin layout-wrapper;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--ui-spacing-cozy);
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
font-size: var(--el-masthead-font-size);
|
||||
line-height: var(--el-masthead-line-height);
|
||||
}
|
||||
|
||||
.site-logo {
|
||||
display: inline-block;
|
||||
font-family: var(--font-mono);
|
||||
animation:
|
||||
logo-pulse 5s cubic-bezier(0.4, 0, 0.6, 1) infinite,
|
||||
logo-glitch 13s step-end infinite;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.bracket {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
@keyframes logo-pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
25% {
|
||||
opacity: 0.66;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.33;
|
||||
}
|
||||
|
||||
75% {
|
||||
opacity: 0.66;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes logo-glitch {
|
||||
0%,
|
||||
84% {
|
||||
transform: translate(0, 0);
|
||||
filter: brightness(1);
|
||||
}
|
||||
|
||||
85% {
|
||||
transform: translate(-2px, 0);
|
||||
filter: brightness(0.4);
|
||||
}
|
||||
|
||||
86% {
|
||||
transform: translate(1px, -1px);
|
||||
filter: brightness(0.2);
|
||||
}
|
||||
|
||||
87% {
|
||||
transform: translate(-1px, 1px);
|
||||
filter: brightness(0.7);
|
||||
}
|
||||
|
||||
88% {
|
||||
transform: translate(2px, 0);
|
||||
filter: brightness(0.1);
|
||||
}
|
||||
|
||||
89% {
|
||||
transform: translate(-2px, 1px);
|
||||
filter: brightness(0.5);
|
||||
}
|
||||
|
||||
90% {
|
||||
transform: translate(0, -1px);
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
|
||||
91%,
|
||||
100% {
|
||||
transform: translate(0, 0);
|
||||
filter: brightness(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
75
src/components/layout/PostHeader/Cover.astro
Normal file
75
src/components/layout/PostHeader/Cover.astro
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
import { Image } from 'astro:assets';
|
||||
interface Props {
|
||||
src: string;
|
||||
alt: string;
|
||||
caption?: string;
|
||||
}
|
||||
const { src, alt, caption } = Astro.props;
|
||||
|
||||
const images = import.meta.glob<{ default: ImageMetadata }>(
|
||||
'/src/content/**/cover/*.{png,jpg,jpeg,webp,avif}',
|
||||
{ eager: true },
|
||||
);
|
||||
|
||||
const imagePath = `/src${src}`;
|
||||
const image = images[imagePath]?.default;
|
||||
---
|
||||
|
||||
<figure class="cover">
|
||||
{
|
||||
image ? (
|
||||
<Image
|
||||
class="image"
|
||||
src={image}
|
||||
alt={alt}
|
||||
widths={[600, 900, 1280, 1440, 1600, 1920]}
|
||||
sizes="100vw"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<img class="image" src={src} alt={alt} loading="lazy" />
|
||||
)
|
||||
}
|
||||
<figcaption class="caption">
|
||||
<div class="wrapper">
|
||||
{
|
||||
caption ? (
|
||||
<span class="caption-text">{caption}</span>
|
||||
) : (
|
||||
<span class="caption-meta">{src}</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<style>
|
||||
.cover {
|
||||
@mixin border-t 12px, solid, var(--color-surface-inverse);
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 60vw;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.caption {
|
||||
padding: var(--spacing-snug);
|
||||
font-family: var(--font-mono);
|
||||
color: var(--color-text-inverse);
|
||||
background-color: var(--color-surface-inverse);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
font-size: var(font-size-responsive);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.caption-text,
|
||||
.caption-meta {
|
||||
font-size: var(--typo-size-sm);
|
||||
}
|
||||
</style>
|
||||
78
src/components/layout/PostHeader/Meta.astro
Normal file
78
src/components/layout/PostHeader/Meta.astro
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
interface Props {
|
||||
tags: string[];
|
||||
updateDate?: string;
|
||||
}
|
||||
|
||||
const { tags, updateDate } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="meta">
|
||||
<div class="section">
|
||||
<span class="label"> Author </span>
|
||||
<span class="author item">Dave Damage</span>
|
||||
</div>
|
||||
{
|
||||
tags && tags.length > 0 && (
|
||||
<div class="section">
|
||||
<span class="label">Tags</span>
|
||||
<ul class="taglist">
|
||||
{tags.map((tag) => (
|
||||
<li class="tag item">{tag}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
updateDate && (
|
||||
<div class="section">
|
||||
<span class="label">Last Update</span>
|
||||
<time class="updatedate item">{updateDate}</time>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<style>
|
||||
.meta {
|
||||
@mixin layout-wrapper;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--spacing-relaxed);
|
||||
}
|
||||
.section {
|
||||
min-width: 12ch;
|
||||
@mixin py var(--spacing-snug);
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
.label {
|
||||
@mixin mb var(--spacing-tight);
|
||||
@mixin pb var(--spacing-tight);
|
||||
@mixin border-b 2px, solid, var(--color-border-strong);
|
||||
display: block;
|
||||
font-size: var(--typo-size-xs);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: var(--typo-spacing-comfortable);
|
||||
}
|
||||
.item {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 700;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.taglist {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-tight);
|
||||
}
|
||||
.tag {
|
||||
@mixin py var(--spacing-tight);
|
||||
@mixin px var(--spacing-snug);
|
||||
border: 4px solid var(--color-palette-charcoal-gray);
|
||||
letter-spacing: var(--typo-spacing-relaxed);
|
||||
background: var(--color-palette-charcoal-gray);
|
||||
color: var(--color-palette-off-white);
|
||||
}
|
||||
</style>
|
||||
68
src/components/layout/PostHeader/Overline.astro
Normal file
68
src/components/layout/PostHeader/Overline.astro
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
import type { BreadcrumbSegment } from '@lib/types/content';
|
||||
|
||||
interface Props {
|
||||
breadcrumbs: BreadcrumbSegment[];
|
||||
publicationDate: string;
|
||||
}
|
||||
|
||||
const { breadcrumbs, publicationDate } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="overline">
|
||||
<div class="wrapper">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumbs">
|
||||
<li class="crumb">
|
||||
<a href="/" class="link">dave-dmg.de</a>
|
||||
</li>
|
||||
{
|
||||
breadcrumbs.map((crumb) => (
|
||||
<li class="crumb">
|
||||
<span class="separator">/</span>
|
||||
<a href={crumb.href} class="link">
|
||||
{crumb.label}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
</nav>
|
||||
<time class="publicationdate">
|
||||
⟫ {publicationDate}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.overline {
|
||||
background-color: var(--color-surface-inverse);
|
||||
}
|
||||
.wrapper {
|
||||
@mixin layout-wrapper;
|
||||
padding-block: var(--spacing-snug);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: var(--typo-size-responsive);
|
||||
}
|
||||
.breadcrumbs {
|
||||
display: flex;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--typo-size-sm);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
.link {
|
||||
color: var(--color-text-inverse);
|
||||
transition: color 0.5s ease-in-out;
|
||||
&:hover {
|
||||
color: var(--color-tertiary);
|
||||
}
|
||||
}
|
||||
.publicationdate {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--typo-size-xs);
|
||||
font-weight: var(--typo-weight-bold);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
</style>
|
||||
38
src/components/layout/PostHeader/Title.astro
Normal file
38
src/components/layout/PostHeader/Title.astro
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
interface Props {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
const { title, subtitle } = Astro.props;
|
||||
|
||||
console.log(subtitle);
|
||||
---
|
||||
|
||||
<div class="wrapper">
|
||||
<h1 class="title">{title}</h1>
|
||||
{subtitle && <p class="subtitle">{subtitle}</p>}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
@mixin layout-wrapper;
|
||||
@mixin py var(--spacing-relaxed);
|
||||
}
|
||||
.title {
|
||||
font-size: var(--el-h1-color);
|
||||
font-family: var(--el-h1-font-family);
|
||||
font-size: var(--el-h1-font-size);
|
||||
line-height: var(--typo-leading-tight);
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
@mixin mt var(--spacing-snug);
|
||||
font-family: var(--font-header);
|
||||
font-size: var(--typo-size-lg);
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.075em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
46
src/components/layout/PostHeader/index.astro
Normal file
46
src/components/layout/PostHeader/index.astro
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
import type { BreadcrumbSegment } from '@lib/types/content';
|
||||
import Overline from './Overline.astro';
|
||||
import Title from './Title.astro';
|
||||
import Meta from './Meta.astro';
|
||||
import Cover from './Cover.astro';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
cover?: {
|
||||
src: string;
|
||||
alt: string;
|
||||
caption: string;
|
||||
showInHeader: boolean;
|
||||
};
|
||||
subtitle?: string;
|
||||
tags: string[];
|
||||
publishDate: string;
|
||||
updateDate?: string;
|
||||
breadcrumbs: BreadcrumbSegment[];
|
||||
}
|
||||
|
||||
const { title, cover, subtitle, tags, publishDate, updateDate, breadcrumbs } =
|
||||
Astro.props;
|
||||
const showCover = cover?.src && cover?.showInHeader;
|
||||
|
||||
console.log(Astro.props);
|
||||
---
|
||||
|
||||
<header class="wrapper">
|
||||
<Overline breadcrumbs={breadcrumbs} publicationDate={publishDate} />
|
||||
<Title title={title} subtitle={subtitle} />
|
||||
{
|
||||
showCover && (
|
||||
<Cover alt={cover.alt} src={cover.src} caption={cover?.caption} />
|
||||
)
|
||||
}
|
||||
<Meta tags={tags} updateDate={updateDate} />
|
||||
</header>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
@mixin border-b 8px, solid, var(--color-border-strong);
|
||||
background-color: var(--color-surface-base);
|
||||
}
|
||||
</style>
|
||||
BIN
src/content/articles/alchemical-materialism/cover/src.png
Normal file
BIN
src/content/articles/alchemical-materialism/cover/src.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 155 KiB |
309
src/content/articles/alchemical-materialism/index.mdoc
Normal file
309
src/content/articles/alchemical-materialism/index.mdoc
Normal file
@@ -0,0 +1,309 @@
|
||||
---
|
||||
title: Alchemical Materialism
|
||||
subtitle: A Systematic Framework for Worldbuilding Through Elemental Combination
|
||||
summary: A Systematic Framework for Worldbuilding Through Elemental Combination
|
||||
cover:
|
||||
src: /content/articles/alchemical-materialism/cover/src.png
|
||||
alt: Man approach a volcano
|
||||
caption: The Eshian God-Alchemists claimed this was the face of Monad
|
||||
showInHeader: true
|
||||
publishDate: 2026-02-20T11:17:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: framework
|
||||
tags:
|
||||
- Was ist Was?
|
||||
- Framework
|
||||
- Worldbuilding
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
## Overview
|
||||
|
||||
- **Foundational system** of the crucible
|
||||
- Generates **entities** – cultures, religions, states, vessels, magical traditions – through **systematic elemental combination,** not arbitrary assignment
|
||||
- Two interlocking element sets
|
||||
- **Three Primes =>** Soul, Body, Spirit; dynamic forces in tension
|
||||
- **Five Essences =>** Earth, Fire, Water, Air, Aether; multivalent elemental character
|
||||
- **Elemental Pool Mechanic =>** add tokens from material conditions and history, tally, spend on capabilities, traits, and relationships
|
||||
- **One Engine =>** elemental pool drives everything; capabilities, identity, and cultural character all flow from the same source
|
||||
|
||||
## The Three Primes
|
||||
|
||||
- *Dynamic Forces in Tension* within each entity
|
||||
- Correspond to the three alchemical principles observed when substance is placed in the crucible:
|
||||
- Something **burns** (Sulfur)
|
||||
- Something **remains** (Salt)
|
||||
- Something **transforms** (Mercury)
|
||||
- Each entity contains all three; dominance determines character.
|
||||
- Material conditions determine which Prime dominates; not cultural preference or collective personality
|
||||
- **Interdependence:**
|
||||
- Without **Soul,** nothing starts; structure sits inert, nothing transforms
|
||||
- Without **Body,** nothing holds; fire has no fuel, transforming has no material
|
||||
- Without **Spirit,** nothing changes; fire burns the same thing forever, structure never adapts
|
||||
|
||||
{% table %}
|
||||
- Prime
|
||||
- Glyph
|
||||
- Alchemical
|
||||
- Core
|
||||
- Keyword
|
||||
- Organization
|
||||
- Failure
|
||||
---
|
||||
- [Body](/the-crucible/references/elements/body)
|
||||
- {% ElementSymbol
|
||||
element="body"
|
||||
size="var(--typo-size-2xl)"
|
||||
color="inherit" /%}
|
||||
- Salt
|
||||
- Structure; the form
|
||||
- »It endures«
|
||||
- Institutional strength
|
||||
- Calcifies everything
|
||||
---
|
||||
- [Soul](/the-crucible/references/elements/soul)
|
||||
- {% ElementSymbol
|
||||
element="soul"
|
||||
size="var(--typo-size-2xl)"
|
||||
color="inherit" /%}
|
||||
- Sulfur
|
||||
- Agency; the Spark
|
||||
- »I burn«
|
||||
- Individual excellence
|
||||
- Consumes everything
|
||||
---
|
||||
- [Spirit](/the-crucible/references/elements/spirit)
|
||||
- {% ElementSymbol
|
||||
element="spirit"
|
||||
size="var(--typo-size-2xl)"
|
||||
color="inherit" /%}
|
||||
- Mercury
|
||||
- Transformation; the flux
|
||||
- »Nothing stays«
|
||||
- Adaptive innovation
|
||||
- Dissolves everything
|
||||
{% /table %}
|
||||
|
||||
## The Five Essences
|
||||
|
||||
- Describe the **material character** of an entity
|
||||
- Operates on two tiers simultaneously
|
||||
- **Character =>** Material expression; how the essence manifests across any domain of society
|
||||
- **Symbolic =>** Thematic resonance; enriches religion, mythology, cultural flavour
|
||||
- Each essence has a **primary capability affinity** and two **secondary affinities;** connecting them to the five universal capability tracks
|
||||
- Essences are multivalent → it can express across multiple domains but it has a home
|
||||
|
||||
{% table %}
|
||||
- Essence
|
||||
- Symbol
|
||||
- Properties
|
||||
- IS
|
||||
- Image
|
||||
---
|
||||
- [Aether](/the-crucible/references/elements/aether)
|
||||
- {% ElementSymbol
|
||||
element="aether"
|
||||
size="var(--typo-size-2xl)"
|
||||
color="inherit" /%}
|
||||
- Quintessence
|
||||
- Transcendent, ordered, numinous
|
||||
- The Stars
|
||||
---
|
||||
- [Air](/the-crucible/references/elements/air)
|
||||
- {% ElementSymbol element="air" size="var(--typo-size-2xl)" color="inherit" /%}
|
||||
- Hot & Wet
|
||||
- Invisible, expansive, permeating
|
||||
- The Storm
|
||||
---
|
||||
- [Earth](/the-crucible/references/elements/earth)
|
||||
- {% ElementSymbol
|
||||
element="earth"
|
||||
size="var(--typo-size-2xl)"
|
||||
color="inherit" /%}
|
||||
- Cold & Dry
|
||||
- Heavy, material, foundational
|
||||
- The Mountain
|
||||
---
|
||||
- [Fire](/the-crucible/references/elements/fire)
|
||||
- {% ElementSymbol
|
||||
element="fire"
|
||||
size="var(--typo-size-2xl)"
|
||||
color="inherit" /%}
|
||||
- Hot & Dry
|
||||
- Bright, consuming, refining
|
||||
- The Volcano
|
||||
---
|
||||
- [Water](/the-crucible/references/elements/water)
|
||||
- {% ElementSymbol
|
||||
element="water"
|
||||
size="var(--typo-size-2xl)"
|
||||
color="inherit" /%}
|
||||
- Cold & Wet
|
||||
- Flowing, deep, dissolving
|
||||
- The River
|
||||
{% /table %}
|
||||
|
||||
### The Five Capability Tracks
|
||||
|
||||
{% table %}
|
||||
- Domain
|
||||
- Covers
|
||||
---
|
||||
- **Prosperity**
|
||||
- Labor, production, construction, infrastructure
|
||||
---
|
||||
- **Warfare**
|
||||
- Armies, defense, fortifications, armaments
|
||||
---
|
||||
- **Statecraft**
|
||||
- Governance, administration, law, diplomacy
|
||||
---
|
||||
- **Lore**
|
||||
- Knowledge, education, medicine, philosophy, sciences
|
||||
---
|
||||
- **Rites**
|
||||
- Religion, ritual, sacred practice, arcane arts, cosmology
|
||||
{% /table %}
|
||||
|
||||
#### Essence-to-Capability Affinity
|
||||
|
||||
{% table %}
|
||||
- Essence
|
||||
- Primary
|
||||
- Secondary 1
|
||||
- Secondary 2
|
||||
---
|
||||
- **Aether**
|
||||
- Rites
|
||||
- Statecraft
|
||||
- Lore
|
||||
---
|
||||
- **Air**
|
||||
- Lore
|
||||
- Prosperity
|
||||
- Warfare
|
||||
---
|
||||
- **Earth**
|
||||
- Prosperity
|
||||
- Warfare
|
||||
- Rites
|
||||
---
|
||||
- **Fire**
|
||||
- Warfare
|
||||
- Lore
|
||||
- Statecraft
|
||||
---
|
||||
- **Water**
|
||||
- Statecraft
|
||||
- Rites
|
||||
- Prosperity
|
||||
{% /table %}
|
||||
|
||||
### The Transformation Triangle
|
||||
|
||||
- Three elements involve Transformation → distinguished by mode
|
||||
- **Fire =>** Transformation is *destructive –* purification through consumption
|
||||
- **Water =>** Transformation is *gradual* – erosion, dissolution, blending
|
||||
- **Spirit =>** Transformation is *synthetic* – *solve et coagula,* a new thing born from recombination
|
||||
|
||||
{% table %}
|
||||
- Element
|
||||
- What happens
|
||||
- Process
|
||||
- Image
|
||||
- Reversible
|
||||
---
|
||||
- **Fire**
|
||||
- Destroys to create
|
||||
- Input consumed; new thing exists
|
||||
- The Forge
|
||||
- No – Ore is gone, steel remains
|
||||
---
|
||||
- **Water**
|
||||
- Dissolves to mix
|
||||
- Boundaries erode; things blend
|
||||
- The Solvent
|
||||
- Partially – Salt in Water is still salt-and-water
|
||||
---
|
||||
- **Spirit**
|
||||
- Recombines to become
|
||||
- Inputs loose identity; genuinely new things emerge
|
||||
- The Alembic
|
||||
- No – the product is neither ingredient
|
||||
{% /table %}
|
||||
|
||||
## The Seven Aspects
|
||||
|
||||
- Provide additional specificity
|
||||
- Corresponding to the 7 classical metals and their planetary associations
|
||||
- Categorise what exists and operates within the World
|
||||
|
||||
{% table %}
|
||||
- Aspects
|
||||
- Symbol
|
||||
- Metal
|
||||
- Planet
|
||||
- Association
|
||||
---
|
||||
- *Entities*
|
||||
- {% ElementSymbol
|
||||
element="entities"
|
||||
size="var(--typo-size-2xl)"
|
||||
color="inherit" /%}
|
||||
- Silver
|
||||
- Moon
|
||||
- Living beings; flesh, beasts, plants, spirits
|
||||
---
|
||||
- *Matter*
|
||||
- {% ElementSymbol
|
||||
element="matter"
|
||||
size="var(--typo-size-2xl)"
|
||||
color="inherit" /%}
|
||||
- Lead
|
||||
- Saturn
|
||||
- Physical substances; materials, terrain, stone, foundations
|
||||
---
|
||||
- *Mind*
|
||||
- {% ElementSymbol
|
||||
element="mind"
|
||||
size="var(--typo-size-2xl)"
|
||||
color="inherit" /%}
|
||||
- Quicksilver
|
||||
- Mercury
|
||||
- Thought, consciousness; intellect, emotion, skills
|
||||
---
|
||||
- *Society*
|
||||
- {% ElementSymbol
|
||||
element="society"
|
||||
size="var(--typo-size-2xl)"
|
||||
color="inherit" /%}
|
||||
- Tin
|
||||
- Jupiter
|
||||
- Collective organisation; governance, law, institutions
|
||||
---
|
||||
- *Art*
|
||||
- {% ElementSymbol element="art" size="var(--typo-size-2xl)" color="inherit" /%}
|
||||
- Copper
|
||||
- Venus
|
||||
- Creation, expression; artistry, rituals, craft
|
||||
---
|
||||
- *Mysteries*
|
||||
- {% ElementSymbol
|
||||
element="mysteries"
|
||||
size="var(--typo-size-2xl)"
|
||||
color="inherit" /%}
|
||||
- Gold
|
||||
- Sun
|
||||
- Hidden, occult; magic, destiny, secrets, *residua*
|
||||
---
|
||||
- *Forces*
|
||||
- {% ElementSymbol
|
||||
element="forces"
|
||||
size="var(--typo-size-2xl)"
|
||||
color="inherit" /%}
|
||||
- Iron
|
||||
- Mars
|
||||
- Energies, natural laws; power, conflict, dynamics
|
||||
{% /table %}
|
||||
13
src/content/articles/awq/index.mdoc
Normal file
13
src/content/articles/awq/index.mdoc
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: Advanced Warhammer Quest
|
||||
summary: A dungeoncrawler for the last millenium!
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-27T14:39:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
tags: []
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
13
src/content/articles/chainbreaker/index.mdoc
Normal file
13
src/content/articles/chainbreaker/index.mdoc
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: Chainbreaker
|
||||
summary: Last blood in a world gone mad
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-27T14:40:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
tags: []
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
14
src/content/articles/elements/index.mdoc
Normal file
14
src/content/articles/elements/index.mdoc
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Elements
|
||||
summary: References for all the Elements
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-24T09:44:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: references
|
||||
tags: []
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
23
src/content/articles/framework/index.mdoc
Normal file
23
src/content/articles/framework/index.mdoc
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: The Framework
|
||||
summary: Conceptual Foundation of Crucible
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-20T11:14:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: the-crucible
|
||||
tags: []
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
- Alchemical Materialism
|
||||
- The Primes
|
||||
- The Essences
|
||||
- Affinity
|
||||
- Pool
|
||||
- Design Principles
|
||||
- Tiers
|
||||
- Domains
|
||||
- Sin Engine
|
||||
20
src/content/articles/materia/index.mdoc
Normal file
20
src/content/articles/materia/index.mdoc
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: Materia
|
||||
subtitle: The Elemental Pool
|
||||
summary: Foundation of the Crucible, the elemental pool
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-25T23:19:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: framework
|
||||
tags: []
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
- Pool is both **identity** and **budget**
|
||||
1. **Generate Materia =>** from *material conditions* (environment, subsistence, mythology, history)
|
||||
1. **Read Identity =>** From a grid depending on the entity's nature (e.g. *Ethos, Theology, Polity)*
|
||||
1. **Spend tokens**
|
||||
- Leftover tokens are not carried over
|
||||
17
src/content/articles/prima-materia/index.mdoc
Normal file
17
src/content/articles/prima-materia/index.mdoc
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Prima Materia
|
||||
subtitle: Where we read the earth and learn what it provides
|
||||
summary: Where we read the earth and learn what it provides
|
||||
cover:
|
||||
showInHeader: true
|
||||
publishDate: 2026-02-25T23:28:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: the-crucible
|
||||
tags:
|
||||
- Crucible Stage
|
||||
- Generation
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
14
src/content/articles/references/index.mdoc
Normal file
14
src/content/articles/references/index.mdoc
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: References
|
||||
summary: Where we collect all the references
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-24T09:43:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: the-crucible
|
||||
tags: []
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
---
|
||||
@@ -29,16 +29,16 @@ seo:
|
||||
|
||||
## The Seven Stages
|
||||
|
||||
1. **Prima Materia. – Land**
|
||||
- Generate biomes and resources
|
||||
1. **Prima Materia. – Material Conditions**
|
||||
- Generates land and kin
|
||||
1. **Calcination – Kin**
|
||||
- Generates kindred, heritage, and ancestry
|
||||
- Generates heritage and ancestry
|
||||
1. **Fermentation – Belief**
|
||||
- Generates religion and belief
|
||||
- Generates religion and faith
|
||||
1. **Sublimation – Witchcraft**
|
||||
- Generates magical traditions and crafts
|
||||
1. **Coagulation – Vessels**
|
||||
- Generates institutions and factions
|
||||
- Generates disciplines and crafts
|
||||
1. **Coagulation – Factions**
|
||||
- Generates vessels
|
||||
1. **Conjunction – Realms**
|
||||
- Generates states, tribes, and settlements
|
||||
1. **Dissolution**
|
||||
|
||||
@@ -1,17 +1,42 @@
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
import { glob } from 'astro/loaders';
|
||||
|
||||
const articles = defineCollection({
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
summary: z.string(),
|
||||
cover: z
|
||||
const symbolSchema = z.discriminatedUnion('discriminant', [
|
||||
z.object({
|
||||
discriminant: z.literal('font'),
|
||||
value: z.object({
|
||||
family: z.string(),
|
||||
character: z.string(),
|
||||
}),
|
||||
}),
|
||||
z.object({
|
||||
discriminant: z.literal('svg'),
|
||||
value: z.string(),
|
||||
}),
|
||||
]);
|
||||
|
||||
const seoSchema = z
|
||||
.object({
|
||||
title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
noIndex: z.boolean().default(false),
|
||||
})
|
||||
.optional();
|
||||
|
||||
const coverSchema = z
|
||||
.object({
|
||||
src: z.string().optional(),
|
||||
alt: z.string().optional(),
|
||||
caption: z.string().optional(),
|
||||
showInHeader: z.boolean().default(false),
|
||||
})
|
||||
.optional(),
|
||||
.optional();
|
||||
|
||||
const baseArticleSchema = z.object({
|
||||
title: z.string(),
|
||||
summary: z.string(),
|
||||
subtitle: z.string().optional(),
|
||||
cover: coverSchema,
|
||||
publishDate: z.date(),
|
||||
updateDate: z.date().optional(),
|
||||
status: z.enum(['draft', 'published', 'archived']).default('draft'),
|
||||
@@ -19,27 +44,33 @@ const articles = defineCollection({
|
||||
parent: z.string().optional(),
|
||||
tags: z.array(z.string()).default([]),
|
||||
relatedArticles: z.array(z.string()).default([]),
|
||||
seo: z
|
||||
.object({
|
||||
title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
noIndex: z.boolean().default(false),
|
||||
})
|
||||
.optional(),
|
||||
seo: seoSchema,
|
||||
});
|
||||
|
||||
const articles = defineCollection({
|
||||
loader: glob({
|
||||
pattern: '**/index.mdoc',
|
||||
base: './src/content/articles',
|
||||
}),
|
||||
schema: baseArticleSchema,
|
||||
});
|
||||
|
||||
const pages = defineCollection({
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
seo: z
|
||||
.object({
|
||||
title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
noIndex: z.boolean().default(false),
|
||||
})
|
||||
.optional(),
|
||||
seo: seoSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { articles, pages };
|
||||
const elements = defineCollection({
|
||||
loader: glob({
|
||||
pattern: '**/index.mdoc',
|
||||
base: './src/content/crucible/elements',
|
||||
}),
|
||||
schema: baseArticleSchema.extend({
|
||||
category: z.enum(['prime', 'essence', 'aspect']).default('prime'),
|
||||
symbol: symbolSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { articles, pages, elements };
|
||||
|
||||
52
src/content/crucible/elements/aether/index.mdoc
Normal file
52
src/content/crucible/elements/aether/index.mdoc
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Aether
|
||||
subtitle: The Transcendent, The Ordered, The Numinous
|
||||
summary: Exploring the Essence »Aether« in the Alchemical Materialism framework
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-24T13:02:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags:
|
||||
- Essences
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
category: essence
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: D
|
||||
---
|
||||
- **Alchemical Properties =>** the fifth element; beyond the four mundane; celestial, incorruptible
|
||||
- **Primary Affinity =>** Rites
|
||||
- **Associations**
|
||||
- What lies beyond the material; the fifth thing
|
||||
- Cosmic order → the pattern behind apparent chaos
|
||||
- The numinous → the experience of something greater
|
||||
- The bridge between mortal and divine; threshold substance
|
||||
- Meaning itself; the answer to »why does this matter?«
|
||||
- **Material Character**
|
||||
- *Rites =>* Priesthoods, temples, divine mandate; fate, destiny, the inescapable
|
||||
- *Statecraft =>* Divine legitimacy, sacred law, oaths before gods, hierarchy sanctified from above
|
||||
- *Lore =>* Revelation, prophecy, mystical insight; knowledge from beyond; mystery traditions
|
||||
- *Warfare =>* Holy war, divine champions, sacred weapons; morale & terror; the fear of the supernatural
|
||||
- *Prosperity =>* Sacred crafts, ritual objects; things made for purposes beyond the materials;
|
||||
- **Symbolic**
|
||||
- Transcendence
|
||||
- Order
|
||||
- Mystery
|
||||
- Meaning
|
||||
- The Sacred
|
||||
- What make mundane things matter
|
||||
- The pattern behind the noise
|
||||
- Incorruptible and therefore terrifying
|
||||
- The divine gaze
|
||||
- **Failures**
|
||||
- Disconnection from reality
|
||||
- Cosmic order that crushes mortal will
|
||||
- Meaning so heavy it makes life unbearable
|
||||
- Madness from contact with the transcendent
|
||||
- **Key Image =>** the Stars
|
||||
50
src/content/crucible/elements/air/index.mdoc
Normal file
50
src/content/crucible/elements/air/index.mdoc
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Air
|
||||
subtitle: The Invisible, The Expansive, The Permeating
|
||||
summary: Exploring the Prime »Air« in the Alchemical Materialism framework
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-24T13:01:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags:
|
||||
- Essences
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
category: essence
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: H
|
||||
---
|
||||
- **Alchemical Properties =>** Hot & Wet; ascending, expanding, permeating
|
||||
- **Primary Affinity =>** Lore
|
||||
- **Associations**
|
||||
- The invisible medium; what fills all space
|
||||
- Breath literally, life itself; pneuma
|
||||
- Movement, speed, freedom; what cannot be grasped
|
||||
- The space between things; the medium of sound and speech
|
||||
- Expansion without limit; the storm that scatters
|
||||
- **Material Character**
|
||||
- *Lore →* Speech, rhetoric, philosophy, abstract thought; pneuma as intellect; the word as power
|
||||
- *Statecraft →* Communication, rumour, reputation on the wind; freedom from obligation: the ungovernable
|
||||
- *Warfare →* Cavalry, archery, speed & mobility; hit-&-run; the charge; storms as divine wrath
|
||||
- *Prosperity →* Windmills, sailing, ventilation of mines; the medium through which trade moves
|
||||
- *Rites →* Sky-gods, storm deities, breath-of-life; divine voice; oracles; the heavens
|
||||
- **Symbolic**
|
||||
- Freedom
|
||||
- Invisibility
|
||||
- Speed
|
||||
- Communication
|
||||
- Expansion
|
||||
- **Failures**
|
||||
- Scattering
|
||||
- Nothing holds
|
||||
- All form dispersed
|
||||
- Rootlessness
|
||||
- The storm that destroys all structure
|
||||
- Empty air → nothing there at all
|
||||
- **Key Image =>** the Storm
|
||||
23
src/content/crucible/elements/art/index.mdoc
Normal file
23
src/content/crucible/elements/art/index.mdoc
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Art
|
||||
summary: Exploring the Aspect »Art« in the Alchemical Materialism framework
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-25T21:56:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags: []
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
category: aspect
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: F
|
||||
---
|
||||
- **Planet =>** Venus
|
||||
- **Metal =>** Copper
|
||||
- Creation, expression; artistry, rituals, craft
|
||||
49
src/content/crucible/elements/body/index.mdoc
Normal file
49
src/content/crucible/elements/body/index.mdoc
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Body
|
||||
summary: Exploring the Prime »Body« in the Alchemical Materialism framework
|
||||
cover:
|
||||
alt: Salt – »What remains«
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-24T12:27:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags:
|
||||
- Primes
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
category: prime
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: M
|
||||
---
|
||||
- **Alchemical principle =>** the fixed residue; what's left after burning; the stable principle
|
||||
- **Core =>** Structure; form; persistence; what endures
|
||||
- **Keyword =>** »It endures«
|
||||
- **Principles**
|
||||
- The principle of **structural persistence;** the substrate
|
||||
- The institution that outlives its founders; the tradition that continues because it continues
|
||||
- *Body* doesn't drive action: it's what's already there when action occurs
|
||||
- Soul ignites, spirit transforms, Body is the **medium and the resistance**
|
||||
- **Organisational expressions:** Institutional production: organised labor, standardised input, persistent infrastructure
|
||||
- **Expressions**
|
||||
- The bureaucracy processing forms centuries after anyone remembers why
|
||||
- The kinship system determining marriage partners before birth
|
||||
- The city walls that still stand
|
||||
- The language that shapes though before you think
|
||||
- The caste that tells you who you are before you are born
|
||||
- The road network that dictates where trade flows
|
||||
- **Failures**
|
||||
- Calcification
|
||||
- Rigidity
|
||||
- the structure that cannot bend and therefore breaks
|
||||
- the dead institution
|
||||
- Tradition that crushes all life from what it holds
|
||||
- **Quality**
|
||||
- Fixed
|
||||
- Crystalline
|
||||
- Enduring
|
||||
- Structural
|
||||
50
src/content/crucible/elements/earth/index.mdoc
Normal file
50
src/content/crucible/elements/earth/index.mdoc
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Earth
|
||||
summary: Exploring the Prime »Earth« in the Alchemical Materialism framework
|
||||
cover:
|
||||
alt: The Material, The Heavy, The Foundation
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-24T12:56:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags:
|
||||
- Essences
|
||||
relatedArticles:
|
||||
- alchemical-materialism
|
||||
seo:
|
||||
noIndex: false
|
||||
category: essence
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: X
|
||||
---
|
||||
- **Alchemical Properties =>** Cold & Dry; descending, condensing, solidifying
|
||||
- **Primary affinity =>** Prosperity
|
||||
- **Associations**
|
||||
- Weight, density, solidity; the things that's *there*
|
||||
- The ground beneath; territory, boundary, the physical
|
||||
- Resistance to movement; inertia; mass
|
||||
- The body itself: flesh, bone, muscle
|
||||
- What can be held, measured, counted, divided
|
||||
- **Material Character**
|
||||
- *Prosperity →* Extraction, agriculture, physical labour; working the land; masonry, road-building
|
||||
- *Warfare →* Heavy infantry, siege, fortification; holding ground; crushing weight
|
||||
- *Statecraft →* Territorial administration; »this is MY land«;
|
||||
- *Lore →* Practical craft, material science; knowing by doing and touching
|
||||
- *Rites →* Sacred ground, burial rites, fertility rituals; the body as sacred; chthonic power
|
||||
- **Symbolic**
|
||||
- Rootedness
|
||||
- Stubbornness
|
||||
- Endurance
|
||||
- Possession
|
||||
- Gravity
|
||||
- The foundational
|
||||
- **Failures**
|
||||
- Immoveable
|
||||
- Crushing
|
||||
- Suffocating
|
||||
- The weight that buries
|
||||
- **Key Image:** the Mountain
|
||||
23
src/content/crucible/elements/entities/index.mdoc
Normal file
23
src/content/crucible/elements/entities/index.mdoc
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Entities
|
||||
summary: Exploring the Aspect »Entities« in the Alchemical Materialism framework
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-25T21:42:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags: []
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
category: aspect
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: O
|
||||
---
|
||||
- **Metal =>** Silver
|
||||
- **Planet =>** Moon
|
||||
- Living beings; flesh, beasts, plants, spirits
|
||||
47
src/content/crucible/elements/fire/index.mdoc
Normal file
47
src/content/crucible/elements/fire/index.mdoc
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Fire
|
||||
subtitle: The Bright, The Consuming, The Refining
|
||||
summary: Exploring the Prime »Fire« in the Alchemical Materialism framework
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-24T12:58:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags:
|
||||
- Essences
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
category: essence
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: C
|
||||
---
|
||||
- **Alchemical Properties =>** Hot & Dry; ascending, illuminating, consuming
|
||||
- **Primary affinity =>** Warfare
|
||||
- **Associations**
|
||||
- Light that reveals; heat that transforms
|
||||
- Consumption → Fire needs fuel and destroys what it burns
|
||||
- The Forge → Destruction that creates something better
|
||||
- Illuminations and blindness simultaneously
|
||||
- Purification through burning away impurity
|
||||
- **Material Character**
|
||||
- *Warfare →* scorched earth, purges, burning the heretic; destroying to cleanse; Greek Fire; purification through destruction
|
||||
- *Prosperity →* the forge, the kiln; smelting, glasswork, ceramics; refining the raw into finished
|
||||
- *Statecraft →* passionate bonds, burning loyalty; consuming rivalries; bonds that burn bright and burn out
|
||||
- *Lore →* revelation, illumination, mastery; orthodoxy as the »one true light«
|
||||
- *Rites →* sacred flames, purification rites, trial by fire; burnt offerings; the sun as god
|
||||
- **Symbolic**
|
||||
- Brilliance
|
||||
- Hunger
|
||||
- Purification
|
||||
- Consumption
|
||||
- the hearth that warms AND the wildfire that destroys
|
||||
- **Failures**
|
||||
- Consumes all fuel
|
||||
- Scorches what it meant to warm
|
||||
- Purification that leaves nothing alive
|
||||
- **Key Image:** the Forge
|
||||
23
src/content/crucible/elements/forces/index.mdoc
Normal file
23
src/content/crucible/elements/forces/index.mdoc
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Forces
|
||||
summary: Exploring the Aspect »Forces« in the Alchemical Materialism framework
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-25T22:01:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags: []
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
category: aspect
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: B
|
||||
---
|
||||
- **Planet =>** Mars
|
||||
- **Metal =>** Iron
|
||||
- Energies, natural laws; power, conflict, dynamics
|
||||
23
src/content/crucible/elements/matter/index.mdoc
Normal file
23
src/content/crucible/elements/matter/index.mdoc
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Matter
|
||||
summary: Exploring the Aspect »Matter« in the Alchemical Materialism framework
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-25T21:49:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags: []
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
category: aspect
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: S
|
||||
---
|
||||
- **Metal =>** Lead
|
||||
- **Planet =>** Saturn
|
||||
- Physical substances; materials, terrain, stone, foundations
|
||||
23
src/content/crucible/elements/mind/index.mdoc
Normal file
23
src/content/crucible/elements/mind/index.mdoc
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Mind
|
||||
summary: Exploring the Aspect »Mind« in the Alchemical Materialism framework
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-25T21:51:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags: []
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
category: aspect
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: I
|
||||
---
|
||||
- **Metal =>** Quicksilver
|
||||
- **Planet =>** Mercury
|
||||
- Thought, consciousness; intellect, emotion, skills
|
||||
23
src/content/crucible/elements/mysteries/index.mdoc
Normal file
23
src/content/crucible/elements/mysteries/index.mdoc
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Mysteries
|
||||
summary: Exploring the Aspect »Mysteries« in the Alchemical Materialism framework
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-25T21:59:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags: []
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
category: aspect
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: R
|
||||
---
|
||||
- **Planet =>** Sun
|
||||
- **Metal =>** Gold
|
||||
- Hidden, occult; magic, destiny; secrets, realms beyond
|
||||
23
src/content/crucible/elements/society/index.mdoc
Normal file
23
src/content/crucible/elements/society/index.mdoc
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Society
|
||||
summary: Exploring the Aspect »Society« in the Alchemical Materialism framework
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-25T21:54:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags: []
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
category: aspect
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: L
|
||||
---
|
||||
- **Metal =>** Tin
|
||||
- **Planet =>** Jupiter
|
||||
- Collective organisation; governance, institutions, law
|
||||
46
src/content/crucible/elements/soul/index.mdoc
Normal file
46
src/content/crucible/elements/soul/index.mdoc
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: Soul
|
||||
summary: Exploring the Prime »Soul« in the Alchemical Materialism framework
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-24T11:26:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags:
|
||||
- Primes
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
category: prime
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: A
|
||||
---
|
||||
- **Alchemical Principle =>** the combustible essence; what makes fire catch; the active principle
|
||||
- **Core =>** Agency; drive; the spark that initiates action
|
||||
- **Keyword =>** »I burn«
|
||||
- **Principles**
|
||||
- The principle of **individual agency and animation**
|
||||
- Greed is one expression; so is passion, obsession, curiosity, protective fury, creative drive
|
||||
- What unites Soul expressions: the fire originates in *a person,* not a structure or process
|
||||
- **Organisational expression:** Individual excellence; master craftsmen, personal glory, competitive innovation
|
||||
- **Expressions**
|
||||
- The merchant hoarding silver
|
||||
- The sculptor destroying a statue because of a imperfection only they can see
|
||||
- The parent killing to protect their child
|
||||
- The explorer walking into the unknown
|
||||
- The warrior seeking glory
|
||||
- The inventor breaking every rule
|
||||
- **Failures:**
|
||||
- Consumes everything
|
||||
- Burns out
|
||||
- Burns what it touches
|
||||
- Individual drive that destroys because it cannot moderate
|
||||
- **Qualities:**
|
||||
- Combustible
|
||||
- Animating
|
||||
- Individual
|
||||
- Igniting
|
||||
57
src/content/crucible/elements/spirit/index.mdoc
Normal file
57
src/content/crucible/elements/spirit/index.mdoc
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Spirit
|
||||
subtitle: Mercury – What transforms
|
||||
summary: Exploring the Prime »Spirit« in the Alchemical Materialism framework
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-24T12:42:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags:
|
||||
- Primes
|
||||
relatedArticles:
|
||||
- alchemical-materialism
|
||||
seo:
|
||||
noIndex: false
|
||||
category: prime
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: E
|
||||
---
|
||||
- **Alchemical Principle =>** the volatile mediator; what escapes, dissolves, and recombines; the transformation principle
|
||||
- **Core =>** Transformation, synthesis; *becoming*
|
||||
- **Keyword =>** »Nothing stays, everything becomes«
|
||||
- **Principles**
|
||||
- The principle of **transformation as a positive force;** not mere reaction or survival
|
||||
- *»Solve et coagula«* → dissolve and recombine
|
||||
- Mediation, adaption, and change-as-drive are all expressions of transformation
|
||||
- Mercury is the *most alchemically important* of the three → the philosopher's stone ingredient
|
||||
- **Organisational Expression**
|
||||
- Adaptive innovation
|
||||
- Cross-pollination
|
||||
- Hybrid techniques
|
||||
- Syncretic methods
|
||||
- **Expressions**
|
||||
- Syncretic cultures absorbing and remaking what they encounter
|
||||
- Trade-diaspora peoples existing between cultures; transforming both
|
||||
- Revolutionary movements dissolving old structures
|
||||
- Liminal societies at crossroads, borders, thresholds
|
||||
- Alchemists literally
|
||||
- Seasonal/cyclical peoples participating in cycles of transformation
|
||||
- Mediators who create new arrangements, not just broker peace
|
||||
- **Failures**
|
||||
- Dissolves everything
|
||||
- Nothing holds
|
||||
- Identity lost
|
||||
- Perpetual revolution devouring its own
|
||||
- Formlessness
|
||||
- Mercury slipping through every grasp
|
||||
- **Quality**
|
||||
- Volatile
|
||||
- Liminal
|
||||
- Mercurial
|
||||
- Syncretic
|
||||
- Transformative
|
||||
50
src/content/crucible/elements/water/index.mdoc
Normal file
50
src/content/crucible/elements/water/index.mdoc
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Water
|
||||
subtitle: The Flowing, The Deep, The Dissolving
|
||||
summary: Exploring the Essence »Water« in the Alchemical Materialism framework
|
||||
cover:
|
||||
showInHeader: false
|
||||
publishDate: 2026-02-24T12:59:00.000Z
|
||||
status: published
|
||||
isFeatured: false
|
||||
parent: elements
|
||||
tags:
|
||||
- Essences
|
||||
relatedArticles: []
|
||||
seo:
|
||||
noIndex: false
|
||||
category: essence
|
||||
symbol:
|
||||
discriminant: font
|
||||
value:
|
||||
family: Unigrim Dee
|
||||
character: Q
|
||||
---
|
||||
- **Alchemical Properties =>** Cold & Wet; descending, flowing, dissolving
|
||||
- **Primary Affinity =>** Statecraft
|
||||
- **Associations**
|
||||
- The universal solvent -> what dissolves boundaries
|
||||
- Flow, connection → the medium of exchange
|
||||
- Depth and concealment → What's hidden beneath the surface
|
||||
- Life-giving AND drowning: rain AND Flood
|
||||
- Takes the shape of its container; adapt to form
|
||||
- **Material Character**
|
||||
- *Statecraft →* Networks, exchange, reciprocity; debts that flow; diplomacy; status as current
|
||||
- *Prosperity →* Irrigation, fishing, brewing, dyeing; the river as economic artery
|
||||
- *Arms →* Naval power, coastal raiding, poison; erosion/attrition warfare
|
||||
- *Lore →* Hidden knowledge, mysteries of the deep; intuition over analysis; the murky and the clear
|
||||
- *Rites →* Baptism, sacred rivers, purification by washing; sea-gods; the abyss
|
||||
- **Symbolic**
|
||||
- Fluidity
|
||||
- Depth
|
||||
- Concealment
|
||||
- Connection
|
||||
- Erosion
|
||||
- What flows between
|
||||
- What lies beneath
|
||||
- Patience that wears stone away
|
||||
- **Failures**
|
||||
- Drowning
|
||||
- Erosion
|
||||
- Formlessness
|
||||
- **Key Image:** the River
|
||||
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Bold.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Bold.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-BoldItalic.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-BoldOblique.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-BoldOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBold.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBold.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBoldItalic.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBoldItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBoldOblique.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBoldOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraLight.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraLight.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraLightItalic.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraLightItalic.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Heavy.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Heavy.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-HeavyItalic.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-HeavyItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-HeavyOblique.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-HeavyOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Italic.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Italic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Light.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Light.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-LightItalic.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-LightItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-LightOblique.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-LightOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Medium.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Medium.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-MediumItalic.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-MediumItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-MediumOblique.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-MediumOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Oblique.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Oblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Regular.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Regular.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBold.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBold.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBoldItalic.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBoldItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBoldOblique.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBoldOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Thin.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-Thin.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ThinItalic.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ThinItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ThinOblique.woff2
Normal file
BIN
src/fonts/IosevkaSansMono/IosevkaSansMono-ThinOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Bold.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Bold.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-BoldItalic.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-BoldOblique.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-BoldOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBold.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBold.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBoldItalic.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBoldItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBoldOblique.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBoldOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLight.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLight.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLightItalic.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLightItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLightOblique.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLightOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Heavy.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Heavy.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-HeavyItalic.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-HeavyItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-HeavyOblique.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-HeavyOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Italic.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Italic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Light.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Light.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-LightItalic.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-LightItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-LightOblique.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-LightOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Medium.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Medium.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-MediumItalic.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-MediumItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-MediumOblique.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-MediumOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Oblique.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Oblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Regular.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Regular.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBold.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBold.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBoldItalic.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBoldItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBoldOblique.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBoldOblique.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Thin.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-Thin.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ThinItalic.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ThinItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ThinOblique.woff2
Normal file
BIN
src/fonts/IosevkaSlabQp/IosevkaSlabQp-ThinOblique.woff2
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
import { collection } from '@keystatic/core';
|
||||
import { createBaseArticleFields } from '../fields/base-article.ts';
|
||||
import { createContentField } from '../fields/content.ts';
|
||||
import { createBaseArticleFields } from '@fields/base-article.ts';
|
||||
import { createContentField } from '@fields/content.ts';
|
||||
|
||||
export const articles = collection({
|
||||
label: 'Articles',
|
||||
|
||||
66
src/keystatic/collections/crucible/elements.ts
Normal file
66
src/keystatic/collections/crucible/elements.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { collection, fields } from '@keystatic/core';
|
||||
import { createBaseArticleFields } from '@fields/base-article.ts';
|
||||
import { createContentField } from '@fields/content.ts';
|
||||
|
||||
export const elements = collection({
|
||||
label: 'Elements',
|
||||
slugField: 'title',
|
||||
path: 'src/content/crucible/elements/*/',
|
||||
columns: ['category'],
|
||||
|
||||
format: {
|
||||
contentField: 'body',
|
||||
},
|
||||
schema: {
|
||||
...createBaseArticleFields(),
|
||||
body: createContentField(),
|
||||
category: fields.select({
|
||||
label: 'Category',
|
||||
options: [
|
||||
{
|
||||
label: 'Prime',
|
||||
value: 'prime',
|
||||
},
|
||||
{
|
||||
label: 'Essence',
|
||||
value: 'essence',
|
||||
},
|
||||
{
|
||||
label: 'Aspect',
|
||||
value: 'aspect',
|
||||
},
|
||||
],
|
||||
defaultValue: 'prime',
|
||||
}),
|
||||
symbol: fields.conditional(
|
||||
fields.select({
|
||||
label: 'Type',
|
||||
defaultValue: 'font',
|
||||
options: [
|
||||
{ label: 'Font', value: 'font' },
|
||||
{ label: 'SVG', value: 'svg' },
|
||||
],
|
||||
}),
|
||||
{
|
||||
svg: fields.text({
|
||||
label: 'SVG Markup',
|
||||
multiline: true,
|
||||
}),
|
||||
font: fields.object({
|
||||
family: fields.select({
|
||||
label: 'Font Family',
|
||||
defaultValue: 'Unigrim Dee',
|
||||
options: [
|
||||
{ label: 'Unigrim Dee', value: 'Unigrim Dee' },
|
||||
{ label: 'Unigrim Hochenheim', value: 'Unigrim Hochenheim' },
|
||||
{ label: 'Unigrim Trithemius', value: 'Unigrim Trithemius' },
|
||||
],
|
||||
}),
|
||||
character: fields.text({
|
||||
label: 'Character',
|
||||
}),
|
||||
}),
|
||||
},
|
||||
),
|
||||
},
|
||||
});
|
||||
7
src/keystatic/collections/crucible/index.ts
Normal file
7
src/keystatic/collections/crucible/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { elements } from './elements';
|
||||
|
||||
const crucibleCollections = {
|
||||
cr_elements: elements,
|
||||
};
|
||||
|
||||
export default crucibleCollections;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { collection, fields } from '@keystatic/core';
|
||||
import { createSEOField } from '../fields/seo.ts';
|
||||
import { createContentField } from '../fields/content.ts';
|
||||
import { createSEOField } from '@fields/seo.ts';
|
||||
import { createContentField } from '@fields/content.ts';
|
||||
|
||||
export const pages = collection({
|
||||
label: 'Pages',
|
||||
|
||||
50
src/keystatic/components/element.ts
Normal file
50
src/keystatic/components/element.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { componentIcon } from '@keystar/ui/icon/icons/componentIcon';
|
||||
import { inline } from '@keystatic/core/content-components';
|
||||
import { fields } from '@keystatic/core';
|
||||
|
||||
const elementCompontent = {
|
||||
ElementSymbol: inline({
|
||||
label: 'Element Symbol',
|
||||
icon: componentIcon,
|
||||
schema: {
|
||||
element: fields.relationship({
|
||||
label: 'Element',
|
||||
collection: 'cr_elements',
|
||||
}),
|
||||
size: fields.select({
|
||||
label: 'Size',
|
||||
defaultValue: 'var(--typo-size-md)',
|
||||
options: [
|
||||
{ label: '2XS', value: 'var(--typo-size-2xs)' },
|
||||
{ label: 'XS', value: 'var(--typo-size-xs)' },
|
||||
{ label: 'SM', value: 'var(--typo-size-sm)' },
|
||||
{ label: 'MD', value: 'var(--typo-size-md)' },
|
||||
{ label: 'LG', value: 'var(--typo-size-lg)' },
|
||||
{ label: 'XL', value: 'var(--typo-size-xl)' },
|
||||
{ label: '2XL', value: 'var(--typo-size-2xl)' },
|
||||
{ label: '3XL', value: 'var(--typo-size-3xl)' },
|
||||
{ label: '4XL', value: 'var(--typo-size-4xl)' },
|
||||
{ label: '5XL', value: 'var(--typo-size-5xl)' },
|
||||
{ label: '6XL', value: 'var(--typo-size-6xl)' },
|
||||
{ label: '7XL', value: 'var(--typo-size-7xl)' },
|
||||
{ label: '8XL', value: 'var(--typo-size-8xl)' },
|
||||
],
|
||||
}),
|
||||
color: fields.select({
|
||||
label: 'Color',
|
||||
defaultValue: 'inherit',
|
||||
options: [
|
||||
{ label: 'Inherit', value: 'inherit' },
|
||||
{ label: 'primary', value: 'var(--color-primary)' },
|
||||
{ label: 'secondary', value: 'var(--color-secondary)' },
|
||||
{ label: 'tertiary', value: 'var(--color-tertiary)' },
|
||||
{ label: 'primary', value: 'var(--color-primary)' },
|
||||
{ label: 'Text', value: 'var(--color-text-primary)' },
|
||||
{ label: 'Text Inverse', value: 'var(--color-text-inverse)' },
|
||||
],
|
||||
}),
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default elementCompontent;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user