diff --git a/astro.config.mjs b/astro.config.mjs index 47c92c4..36f401f 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -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'], + }, + }, +}); \ No newline at end of file diff --git a/keystatic.config.ts b/keystatic.config.ts index c902ebc..7fcb3af 100644 --- a/keystatic.config.ts +++ b/keystatic.config.ts @@ -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, }, }); diff --git a/markdoc.config.mjs b/markdoc.config.mjs new file mode 100644 index 0000000..faa11db --- /dev/null +++ b/markdoc.config.mjs @@ -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 }, + }, + }, + }, +}); diff --git a/package-lock.json b/package-lock.json index d556e0c..53b5d4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index af45212..b0d7fee 100644 --- a/package.json +++ b/package.json @@ -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" } diff --git a/postcss.config.mjs b/postcss.config.mjs index 6a5a932..f62a830 100644 --- a/postcss.config.mjs +++ b/postcss.config.mjs @@ -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({ diff --git a/src/components/content/ElementSymbol.astro b/src/components/content/ElementSymbol.astro new file mode 100644 index 0000000..160a42c --- /dev/null +++ b/src/components/content/ElementSymbol.astro @@ -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' && ( + + {(symbol as FontSymbol).value.character} + + ) +} + +{ + symbol?.discriminant === 'svg' && ( + + ) +} + + diff --git a/src/components/layout/CMDPalette/CMDPalette.module.css b/src/components/layout/CMDPalette/CMDPalette.module.css new file mode 100644 index 0000000..994fce2 --- /dev/null +++ b/src/components/layout/CMDPalette/CMDPalette.module.css @@ -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); + } +} diff --git a/src/components/layout/CMDPalette/index.tsx b/src/components/layout/CMDPalette/index.tsx new file mode 100644 index 0000000..2e57d23 --- /dev/null +++ b/src/components/layout/CMDPalette/index.tsx @@ -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 = { + 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([]); + const [selectedIndex, setSelectedIndex] = useState(-1); + + const selectedRef = useRef(-1); + const loadingRef = useRef(false); + const inputRef = useRef(null); + const resultsRef = useRef(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 ( + <> + + Navigate… + ⌘K + + {isOpen && } + {isOpen && ( + + + ⟩ + setQuery((e.target as HTMLInputElement).value)} + onKeyDown={onInputKeydown} + /> + esc + + + {rows.map((row) => + row.kind === 'label' ? ( + + {row.group} + + ) : ( + + + {TYPE_LABELS[row.entry!.type] ?? row.entry!.type} + + {row.entry!.label} + {row.entry!.path} + → + + ), + )} + {resultCount === 0 && ( + No results found + )} + + + + )} + > + ); +} diff --git a/src/components/layout/MastHead.astro b/src/components/layout/MastHead.astro new file mode 100644 index 0000000..769b405 --- /dev/null +++ b/src/components/layout/MastHead.astro @@ -0,0 +1,129 @@ +--- +import CommandPalette from "./CMDPalette" +--- + + + + + ◬ + + dave + [ + dmg + ] + + + + + + + diff --git a/src/components/layout/PostHeader/Cover.astro b/src/components/layout/PostHeader/Cover.astro new file mode 100644 index 0000000..5cb5a19 --- /dev/null +++ b/src/components/layout/PostHeader/Cover.astro @@ -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; +--- + + + { + image ? ( + + ) : ( + + ) + } + + + { + caption ? ( + {caption} + ) : ( + {src} + ) + } + + + + + diff --git a/src/components/layout/PostHeader/Meta.astro b/src/components/layout/PostHeader/Meta.astro new file mode 100644 index 0000000..2ec58a2 --- /dev/null +++ b/src/components/layout/PostHeader/Meta.astro @@ -0,0 +1,78 @@ +--- +interface Props { + tags: string[]; + updateDate?: string; +} + +const { tags, updateDate } = Astro.props; +--- + + + + Author + Dave Damage + + { + tags && tags.length > 0 && ( + + Tags + + {tags.map((tag) => ( + {tag} + ))} + + + ) + } + { + updateDate && ( + + Last Update + {updateDate} + + ) + } + + diff --git a/src/components/layout/PostHeader/Overline.astro b/src/components/layout/PostHeader/Overline.astro new file mode 100644 index 0000000..2b22d05 --- /dev/null +++ b/src/components/layout/PostHeader/Overline.astro @@ -0,0 +1,68 @@ +--- +import type { BreadcrumbSegment } from '@lib/types/content'; + +interface Props { + breadcrumbs: BreadcrumbSegment[]; + publicationDate: string; +} + +const { breadcrumbs, publicationDate } = Astro.props; +--- + + + + + + + dave-dmg.de + + { + breadcrumbs.map((crumb) => ( + + / + + {crumb.label} + + + )) + } + + + + ⟫ {publicationDate} + + + + + diff --git a/src/components/layout/PostHeader/Title.astro b/src/components/layout/PostHeader/Title.astro new file mode 100644 index 0000000..04f918e --- /dev/null +++ b/src/components/layout/PostHeader/Title.astro @@ -0,0 +1,38 @@ +--- +interface Props { + title: string; + subtitle?: string; +} + +const { title, subtitle } = Astro.props; + +console.log(subtitle); +--- + + + {title} + {subtitle && {subtitle}} + + + diff --git a/src/components/layout/PostHeader/index.astro b/src/components/layout/PostHeader/index.astro new file mode 100644 index 0000000..6ff3807 --- /dev/null +++ b/src/components/layout/PostHeader/index.astro @@ -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); +--- + + + + + { + showCover && ( + + ) + } + + + + diff --git a/src/content/articles/alchemical-materialism/cover/src.png b/src/content/articles/alchemical-materialism/cover/src.png new file mode 100644 index 0000000..a380b98 Binary files /dev/null and b/src/content/articles/alchemical-materialism/cover/src.png differ diff --git a/src/content/articles/alchemical-materialism/index.mdoc b/src/content/articles/alchemical-materialism/index.mdoc new file mode 100644 index 0000000..b88c106 --- /dev/null +++ b/src/content/articles/alchemical-materialism/index.mdoc @@ -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 %} diff --git a/src/content/articles/awq/index.mdoc b/src/content/articles/awq/index.mdoc new file mode 100644 index 0000000..386d651 --- /dev/null +++ b/src/content/articles/awq/index.mdoc @@ -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 +--- diff --git a/src/content/articles/chainbreaker/index.mdoc b/src/content/articles/chainbreaker/index.mdoc new file mode 100644 index 0000000..04905a3 --- /dev/null +++ b/src/content/articles/chainbreaker/index.mdoc @@ -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 +--- diff --git a/src/content/articles/elements/index.mdoc b/src/content/articles/elements/index.mdoc new file mode 100644 index 0000000..c9efe8d --- /dev/null +++ b/src/content/articles/elements/index.mdoc @@ -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 +--- diff --git a/src/content/articles/framework/index.mdoc b/src/content/articles/framework/index.mdoc new file mode 100644 index 0000000..0d09b86 --- /dev/null +++ b/src/content/articles/framework/index.mdoc @@ -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 diff --git a/src/content/articles/materia/index.mdoc b/src/content/articles/materia/index.mdoc new file mode 100644 index 0000000..013f5b6 --- /dev/null +++ b/src/content/articles/materia/index.mdoc @@ -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 diff --git a/src/content/articles/prima-materia/index.mdoc b/src/content/articles/prima-materia/index.mdoc new file mode 100644 index 0000000..f20dc93 --- /dev/null +++ b/src/content/articles/prima-materia/index.mdoc @@ -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 +--- diff --git a/src/content/articles/references/index.mdoc b/src/content/articles/references/index.mdoc new file mode 100644 index 0000000..5575108 --- /dev/null +++ b/src/content/articles/references/index.mdoc @@ -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 +--- diff --git a/src/content/articles/the-crucible/index.mdoc b/src/content/articles/the-crucible/index.mdoc index e6f6f7e..98b95d0 100644 --- a/src/content/articles/the-crucible/index.mdoc +++ b/src/content/articles/the-crucible/index.mdoc @@ -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** diff --git a/src/content/config.ts b/src/content/config.ts index c4b5008..56740b1 100644 --- a/src/content/config.ts +++ b/src/content/config.ts @@ -1,45 +1,76 @@ import { defineCollection, z } from 'astro:content'; +import { glob } from 'astro/loaders'; + +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(); + +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'), + isFeatured: z.boolean().default(false), + parent: z.string().optional(), + tags: z.array(z.string()).default([]), + relatedArticles: z.array(z.string()).default([]), + seo: seoSchema, +}); const articles = defineCollection({ - schema: z.object({ - title: z.string(), - summary: z.string(), - cover: z - .object({ - src: z.string().optional(), - alt: z.string().optional(), - caption: z.string().optional(), - showInHeader: z.boolean().default(false), - }) - .optional(), - publishDate: z.date(), - updateDate: z.date().optional(), - status: z.enum(['draft', 'published', 'archived']).default('draft'), - isFeatured: z.boolean().default(false), - 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(), + 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 }; diff --git a/src/content/crucible/elements/aether/index.mdoc b/src/content/crucible/elements/aether/index.mdoc new file mode 100644 index 0000000..c5e222b --- /dev/null +++ b/src/content/crucible/elements/aether/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/air/index.mdoc b/src/content/crucible/elements/air/index.mdoc new file mode 100644 index 0000000..dc9012c --- /dev/null +++ b/src/content/crucible/elements/air/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/art/index.mdoc b/src/content/crucible/elements/art/index.mdoc new file mode 100644 index 0000000..db5e575 --- /dev/null +++ b/src/content/crucible/elements/art/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/body/index.mdoc b/src/content/crucible/elements/body/index.mdoc new file mode 100644 index 0000000..44ce9c1 --- /dev/null +++ b/src/content/crucible/elements/body/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/earth/index.mdoc b/src/content/crucible/elements/earth/index.mdoc new file mode 100644 index 0000000..80d916e --- /dev/null +++ b/src/content/crucible/elements/earth/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/entities/index.mdoc b/src/content/crucible/elements/entities/index.mdoc new file mode 100644 index 0000000..f6704c3 --- /dev/null +++ b/src/content/crucible/elements/entities/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/fire/index.mdoc b/src/content/crucible/elements/fire/index.mdoc new file mode 100644 index 0000000..dbc5cdd --- /dev/null +++ b/src/content/crucible/elements/fire/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/forces/index.mdoc b/src/content/crucible/elements/forces/index.mdoc new file mode 100644 index 0000000..fc73519 --- /dev/null +++ b/src/content/crucible/elements/forces/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/matter/index.mdoc b/src/content/crucible/elements/matter/index.mdoc new file mode 100644 index 0000000..4af6342 --- /dev/null +++ b/src/content/crucible/elements/matter/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/mind/index.mdoc b/src/content/crucible/elements/mind/index.mdoc new file mode 100644 index 0000000..c1d3ce8 --- /dev/null +++ b/src/content/crucible/elements/mind/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/mysteries/index.mdoc b/src/content/crucible/elements/mysteries/index.mdoc new file mode 100644 index 0000000..3206fc1 --- /dev/null +++ b/src/content/crucible/elements/mysteries/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/society/index.mdoc b/src/content/crucible/elements/society/index.mdoc new file mode 100644 index 0000000..96b8f22 --- /dev/null +++ b/src/content/crucible/elements/society/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/soul/index.mdoc b/src/content/crucible/elements/soul/index.mdoc new file mode 100644 index 0000000..1dc1402 --- /dev/null +++ b/src/content/crucible/elements/soul/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/spirit/index.mdoc b/src/content/crucible/elements/spirit/index.mdoc new file mode 100644 index 0000000..51732c6 --- /dev/null +++ b/src/content/crucible/elements/spirit/index.mdoc @@ -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 diff --git a/src/content/crucible/elements/water/index.mdoc b/src/content/crucible/elements/water/index.mdoc new file mode 100644 index 0000000..c7d962a --- /dev/null +++ b/src/content/crucible/elements/water/index.mdoc @@ -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 diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-Bold.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-Bold.woff2 new file mode 100644 index 0000000..ac97321 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-Bold.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-BoldItalic.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-BoldItalic.woff2 new file mode 100644 index 0000000..d41d0ba Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-BoldItalic.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-BoldOblique.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-BoldOblique.woff2 new file mode 100644 index 0000000..f956cb2 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-BoldOblique.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBold.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBold.woff2 new file mode 100644 index 0000000..c28e70f Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBold.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBoldItalic.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBoldItalic.woff2 new file mode 100644 index 0000000..7841c61 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBoldItalic.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBoldOblique.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBoldOblique.woff2 new file mode 100644 index 0000000..597bea3 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBoldOblique.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraLight.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraLight.woff2 new file mode 100644 index 0000000..166f3dd Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraLight.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraLightItalic.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraLightItalic.woff2 new file mode 100644 index 0000000..a5438fa Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraLightItalic.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraLightOblique.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraLightOblique.woff2 new file mode 100644 index 0000000..49f0be3 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraLightOblique.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-Heavy.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-Heavy.woff2 new file mode 100644 index 0000000..514a688 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-Heavy.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-HeavyItalic.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-HeavyItalic.woff2 new file mode 100644 index 0000000..1ac4539 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-HeavyItalic.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-HeavyOblique.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-HeavyOblique.woff2 new file mode 100644 index 0000000..a9cc305 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-HeavyOblique.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-Italic.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-Italic.woff2 new file mode 100644 index 0000000..90cae67 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-Italic.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-Light.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-Light.woff2 new file mode 100644 index 0000000..231e6d2 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-Light.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-LightItalic.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-LightItalic.woff2 new file mode 100644 index 0000000..da5fc35 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-LightItalic.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-LightOblique.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-LightOblique.woff2 new file mode 100644 index 0000000..3e8d977 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-LightOblique.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-Medium.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-Medium.woff2 new file mode 100644 index 0000000..e622a3a Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-Medium.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-MediumItalic.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-MediumItalic.woff2 new file mode 100644 index 0000000..fee1589 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-MediumItalic.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-MediumOblique.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-MediumOblique.woff2 new file mode 100644 index 0000000..e2c14df Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-MediumOblique.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-Oblique.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-Oblique.woff2 new file mode 100644 index 0000000..ff0dc21 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-Oblique.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-Regular.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-Regular.woff2 new file mode 100644 index 0000000..a84ba56 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-Regular.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBold.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBold.woff2 new file mode 100644 index 0000000..65807ea Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBold.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBoldItalic.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBoldItalic.woff2 new file mode 100644 index 0000000..409e704 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBoldItalic.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBoldOblique.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBoldOblique.woff2 new file mode 100644 index 0000000..ad33d14 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-SemiBoldOblique.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-Thin.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-Thin.woff2 new file mode 100644 index 0000000..d1faf65 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-Thin.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-ThinItalic.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-ThinItalic.woff2 new file mode 100644 index 0000000..735fa91 Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-ThinItalic.woff2 differ diff --git a/src/fonts/IosevkaSansMono/IosevkaSansMono-ThinOblique.woff2 b/src/fonts/IosevkaSansMono/IosevkaSansMono-ThinOblique.woff2 new file mode 100644 index 0000000..4c8c0dc Binary files /dev/null and b/src/fonts/IosevkaSansMono/IosevkaSansMono-ThinOblique.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Bold.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Bold.woff2 new file mode 100644 index 0000000..bd8b92f Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Bold.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-BoldItalic.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-BoldItalic.woff2 new file mode 100644 index 0000000..88ef906 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-BoldItalic.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-BoldOblique.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-BoldOblique.woff2 new file mode 100644 index 0000000..31d9f5f Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-BoldOblique.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBold.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBold.woff2 new file mode 100644 index 0000000..047e3e5 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBold.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBoldItalic.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBoldItalic.woff2 new file mode 100644 index 0000000..70b1504 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBoldItalic.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBoldOblique.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBoldOblique.woff2 new file mode 100644 index 0000000..a34c72d Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraBoldOblique.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLight.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLight.woff2 new file mode 100644 index 0000000..da6e3fe Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLight.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLightItalic.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLightItalic.woff2 new file mode 100644 index 0000000..1129594 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLightItalic.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLightOblique.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLightOblique.woff2 new file mode 100644 index 0000000..bd53c9f Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ExtraLightOblique.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Heavy.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Heavy.woff2 new file mode 100644 index 0000000..936fd3d Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Heavy.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-HeavyItalic.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-HeavyItalic.woff2 new file mode 100644 index 0000000..c118c2f Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-HeavyItalic.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-HeavyOblique.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-HeavyOblique.woff2 new file mode 100644 index 0000000..3281b61 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-HeavyOblique.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Italic.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Italic.woff2 new file mode 100644 index 0000000..0a3d287 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Italic.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Light.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Light.woff2 new file mode 100644 index 0000000..f44915d Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Light.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-LightItalic.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-LightItalic.woff2 new file mode 100644 index 0000000..e32cfd0 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-LightItalic.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-LightOblique.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-LightOblique.woff2 new file mode 100644 index 0000000..3864398 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-LightOblique.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Medium.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Medium.woff2 new file mode 100644 index 0000000..8716b83 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Medium.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-MediumItalic.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-MediumItalic.woff2 new file mode 100644 index 0000000..efefd88 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-MediumItalic.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-MediumOblique.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-MediumOblique.woff2 new file mode 100644 index 0000000..0193004 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-MediumOblique.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Oblique.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Oblique.woff2 new file mode 100644 index 0000000..8cf6863 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Oblique.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Regular.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Regular.woff2 new file mode 100644 index 0000000..fed199e Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Regular.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBold.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBold.woff2 new file mode 100644 index 0000000..769f8a5 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBold.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBoldItalic.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBoldItalic.woff2 new file mode 100644 index 0000000..314df52 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBoldItalic.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBoldOblique.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBoldOblique.woff2 new file mode 100644 index 0000000..5b2fd3e Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-SemiBoldOblique.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Thin.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Thin.woff2 new file mode 100644 index 0000000..e298671 Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-Thin.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ThinItalic.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ThinItalic.woff2 new file mode 100644 index 0000000..4f516df Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ThinItalic.woff2 differ diff --git a/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ThinOblique.woff2 b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ThinOblique.woff2 new file mode 100644 index 0000000..f9b8e8b Binary files /dev/null and b/src/fonts/IosevkaSlabQp/IosevkaSlabQp-ThinOblique.woff2 differ diff --git a/src/keystatic/collections/articles.ts b/src/keystatic/collections/articles.ts index b0d919a..e3a7036 100644 --- a/src/keystatic/collections/articles.ts +++ b/src/keystatic/collections/articles.ts @@ -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', diff --git a/src/keystatic/collections/crucible/elements.ts b/src/keystatic/collections/crucible/elements.ts new file mode 100644 index 0000000..413e3b8 --- /dev/null +++ b/src/keystatic/collections/crucible/elements.ts @@ -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', + }), + }), + }, + ), + }, +}); diff --git a/src/keystatic/collections/crucible/index.ts b/src/keystatic/collections/crucible/index.ts new file mode 100644 index 0000000..599cdba --- /dev/null +++ b/src/keystatic/collections/crucible/index.ts @@ -0,0 +1,7 @@ +import { elements } from './elements'; + +const crucibleCollections = { + cr_elements: elements, +}; + +export default crucibleCollections; diff --git a/src/keystatic/collections/pages.ts b/src/keystatic/collections/pages.ts index 183f875..78c962c 100644 --- a/src/keystatic/collections/pages.ts +++ b/src/keystatic/collections/pages.ts @@ -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', diff --git a/src/keystatic/components/element.ts b/src/keystatic/components/element.ts new file mode 100644 index 0000000..6692d6e --- /dev/null +++ b/src/keystatic/components/element.ts @@ -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; diff --git a/src/keystatic/components/index.ts b/src/keystatic/components/index.ts new file mode 100644 index 0000000..63a02fc --- /dev/null +++ b/src/keystatic/components/index.ts @@ -0,0 +1,5 @@ +import elementComponent from './element.ts'; + +export const generalComponents = { + ...elementComponent, +}; diff --git a/src/keystatic/fields/base-article.ts b/src/keystatic/fields/base-article.ts index 5c87e95..6ec40c7 100644 --- a/src/keystatic/fields/base-article.ts +++ b/src/keystatic/fields/base-article.ts @@ -2,8 +2,11 @@ import { fields } from '@keystatic/core'; import { createSEOField } from './seo.ts'; import { createCoverField } from './cover.ts'; -export const createBaseArticleFields = () => ({ +export const createBaseArticleFields = ( + parentCollection: string = 'articles', +) => ({ title: fields.slug({ name: { label: 'Title' } }), + subtitle: fields.text({ label: 'Subtitle' }), summary: fields.text({ label: 'Summary', multiline: true, @@ -33,7 +36,7 @@ export const createBaseArticleFields = () => ({ }), parent: fields.relationship({ label: 'Parent', - collection: 'articles', + collection: parentCollection, }), tags: fields.array(fields.text({ label: 'Tag' }), { label: 'Tags', diff --git a/src/keystatic/fields/content.ts b/src/keystatic/fields/content.ts index 6cd9a63..bef597b 100644 --- a/src/keystatic/fields/content.ts +++ b/src/keystatic/fields/content.ts @@ -1,7 +1,9 @@ import { fields } from '@keystatic/core'; import type { ContentComponent } from '@keystatic/core/content-components'; +import { generalComponents } from '../components'; -export const sharedComponents: Record = {}; +export const sharedComponents: Record = + generalComponents; export const createContentField = ( additionalComponents?: Record, diff --git a/src/layouts/ArticleLayout.astro b/src/layouts/ArticleLayout.astro index 14a91b5..aff5836 100644 --- a/src/layouts/ArticleLayout.astro +++ b/src/layouts/ArticleLayout.astro @@ -1,22 +1,12 @@ --- import type { CollectionEntry } from 'astro:content'; -import Base from './Base.astro'; +import ContentLayout from './ContentLayout.astro'; interface Props { entry: CollectionEntry<'articles'>; } const { entry } = Astro.props; -const { Content } = await entry.render(); -const { title, summary, publishDate, updateDate, cover, tags, seo } = - entry.data; --- - - - - {title} - - - - + diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index c81219f..10befbc 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -1,5 +1,6 @@ --- -import '../styles.css'; +import '@styles/global.css'; +import MastHead from '@compontents/layout/MastHead.astro'; interface Props { title: string; @@ -20,11 +21,12 @@ const pageDescription = seo?.description || ''; - {title} + {pageTitle} {pageDescription && } {seo?.noIndex && } +
{subtitle}