Added Masthead, CMDPalette and PostHeader Components

This commit is contained in:
2026-02-27 14:41:49 +01:00
parent f6eb9dd7e1
commit ee099346db
136 changed files with 3736 additions and 244 deletions

View File

@@ -0,0 +1,72 @@
---
import { getEntry } from 'astro:content';
interface Props {
element: string;
size: string;
color: string;
}
interface FontSymbol {
discriminant: 'font';
value: {
family: string;
character: string;
};
}
interface SVGSymbol {
discriminant: 'svg';
value: string;
}
type Symbol = FontSymbol | SVGSymbol;
const { element, size, color } = Astro.props;
const entry = await getEntry('elements', element);
if (!entry) {
console.warn(`Element not found: ${element}`);
}
const symbol = entry?.data.symbol as Symbol | undefined;
---
{
symbol?.discriminant === 'font' && (
<span
class="element font"
style={`font-family: '${(symbol as FontSymbol).value.family}', sans-serif; font-size: ${size}; color: ${color};`}
>
{(symbol as FontSymbol).value.character}
</span>
)
}
{
symbol?.discriminant === 'svg' && (
<span
class="element svg"
style={`width: ${size}; height: ${size}; color: ${color}`}
set:html={(symbol as SVGSymbol).value}
/>
)
}
<style>
.font {
display: inline-block;
line-height: 1;
vertical-align: middle;
}
.svg {
display: inline-flex;
line-height: 1;
vertical-align: middle;
}
.svg :global(svg) {
width: 100%;
height: 100%;
fill: currentColor;
}
</style>

View File

@@ -0,0 +1,211 @@
.trigger {
@mixin ml auto;
cursor: pointer;
display: flex;
gap:var(--ui-spacing-comfortable);
align-items: center;
padding: var(--ui-spacing-snug) var(--ui-spacing-spacious);
border: var(--size-px) solid var(--color-border-normal);
font-family: var(--font-mono);
font-size: var(--ui-typo-size-md);
color: var(--text-color-disabled);
background: var(--color-palette-charcoal-gray);
transition: border-color 0.15s, color 0.15s;
&:hover {
border-color: var(--color-text-inverse);
color: var(--color-text-inverse);
}
& .kbd {
padding: var(--ui-spacing-hairline) var( --ui-spacing-cozy);
border: var(--size-px) solid var(--color-border-normal);
border-radius: var(--size-05);
font-family: var(--font-mono);
font-size: var(--ui-typo-size-xs);
background: var(--color-surface-inverse);
}
}
.backdrop {
position: fixed;
z-index: 99;
inset: 0;
background: var(--color-overlay-heavy);
}
.palette {
position: fixed;
z-index: 100;
top: 20%;
left: 50%;
transform: translateX(-50%);
width: min(var(--size-128), 90vw);
padding: var(--ui-spacing-tight);
border: var(--size-05) solid var(--color-primary);
background: var(--color-surface-inverse);
box-shadow:
0 0 0 var(--size-px) var(--color-border-strong),
var(--size-1) var(--size-1) 0 var(--color-surface-inverse);
}
.header {
@mixin px var(--ui-spacing-generous);
@mixin border-b var(--size-px), solid, var(--color-border-strong);
display: flex;
gap: var(--ui-spacing-comfortable);
align-items: center;
& .icon {
font-family: var(--font-mono);
font-size: var(--ui-typo-size-lg);
font-weight: 900;
color: var(--color-text-disabled);
}
& .input {
@mixin py var(--ui-spacing-generous);
flex: 1;
border: none;
font-family: var(--font-mono);
font-size: var(--ui-typo-size-lg);
color: var(--color-text-inverse);
background: transparent;
outline: none;
&::placeholder {
color: var(--color-text-disabled);
}
}
& .esc {
font-family: var(--font-mono);
font-size: var(--typo-size-xs);
color: var(--color-text-disabled);
}
}
.results {
overflow-y: auto;
max-height: var(--size-96);
& .groupLabel {
padding: var(--ui-spacing-relaxed) var(--ui-spacing-generous) var(--ui-spacing-snug);
font-size: var(--ui-typo-size-2xs);
font-weight: 700;
color: var(--color-text-disabled);
text-transform: uppercase;
letter-spacing: var(--spacing-loosest);
}
& .result {
cursor: pointer;
display: flex;
gap: var(--ui-spacing-relaxed);
align-items: center;
padding: var(--ui-spacing-comfortable) var(--ui-spacing-generous);
font-family: var(--font-mono);
font-size: var(--ui-typo-size-md);
color: var(--color-text-inverse);
text-decoration: none;
text-transform: uppercase;
letter-spacing: var(--typo-spacing-relaxed);
transition: background 0.8s;
& .type {
min-width: var(--size-16);
font-size: var(--ui-typo-size-2xs);
color: var(--color-text-disabled);
text-align: right;
letter-spacing: var(--typo-spacing-looser);
}
& .label {
flex: 1;
}
& .path {
font-size: var(--ui-typo-size-2xs);
color: var(--color-text-disabled);
text-transform: none;
letter-spacing: 0;
}
& .arrow {
font-size: var(--ui-typo-size-xs);
color: var(--color-text-disabled);
opacity: 0;
transition: opacity 0.1s;
}
&:hover,
&.selected {
background: var(--color-border-strong);
& .arrow {
opacity: 1;
}
}
}
}
.empty {
padding: var(--ui-spacing-luxurious) var(--ui-spacing-generous);
font-size: var(--ui-typo-size-sm);
color: var(--color-text-disabled);
text-align: center;
text-transform: uppercase;
letter-spacing: var(--typo-spacing-comfortable);
}
.footer {
@mixin border-t var(--size-px), solid, var(--color-border-strong);
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--ui-spacing-comfortable) var(--ui-spacing-generous);
font-size: var(--ui-typo-size-xs);
color: var(--color-text-disabled);
& .group {
display: flex;
gap: var(--ui-spacing-relaxed);
align-items: center;
}
& kbd {
@mixin mx var(--ui-spacing-tight);
padding: var(--ui-spacing-tight) var(--ui-spacing-snug);
border: var(--size-px) solid var(--color-border-normal);
border-radius: var(--size-05);
font-family: var(--font-mono);
font-size: var(--ui-typo-size-2xs);
}
}

View File

@@ -0,0 +1,287 @@
import {
useState,
useEffect,
useRef,
useCallback,
useMemo,
} from 'preact/hooks';
import type { PaletteEntry, ContentType } from '@lib/types/content';
import styles from './CMDPalette.module.css';
/* CONSTANTS */
const MAX_DEFAULT = 20;
const MAX_SEARCH = 30;
const TYPE_LABELS: Record<ContentType, string> = {
article: 'Article',
element: 'Element',
page: 'Page',
};
/* INDEX ENTRY */
interface IndexedEntry extends PaletteEntry {
_label: string;
_parent: string;
_type: string;
_path: string;
}
const normalize = (str: string): string =>
str.toLowerCase().replace(/[^a-z0-9]/g, '');
const indexEntry = (entry: PaletteEntry): IndexedEntry => ({
...entry,
_label: normalize(entry.label),
_parent: normalize(entry.parent ?? ''),
_type: normalize(entry.type),
_path: normalize(entry.path),
});
/* SCORING */
const scoreEntry = (entry: IndexedEntry, q: string): number => {
let score = 0;
if (entry._label === q) score += 100;
else if (entry._label.startsWith(q)) score += 80;
else if (entry._label.includes(q)) score += 60;
if (entry._parent.includes(q)) score += 30;
if (entry._type.includes(q)) score += 20;
if (entry._path.includes(q)) score += 10;
return score;
};
/* RENDERABLE ROW single-pass from filtered entries */
interface RenderRow {
kind: 'label' | 'result';
key: string;
group?: string;
entry?: PaletteEntry;
index?: number;
}
const buildRows = (entries: PaletteEntry[]): RenderRow[] => {
const rows: RenderRow[] = [];
let currentGroup = '';
let idx = 0;
for (const entry of entries) {
const group = entry.parent ?? TYPE_LABELS[entry.type] ?? 'Other';
if (group !== currentGroup) {
currentGroup = group;
rows.push({ kind: 'label', key: `label-${group}`, group });
}
rows.push({
kind: 'result',
key: entry.path,
entry,
index: idx++,
});
}
return rows;
};
/* COMPONENT */
export default function CommandPalette() {
const [isOpen, setIsOpen] = useState(false);
const [query, setQuery] = useState('');
const [index, setIndex] = useState<IndexedEntry[]>([]);
const [selectedIndex, setSelectedIndex] = useState(-1);
const selectedRef = useRef(-1);
const loadingRef = useRef(false);
const inputRef = useRef<HTMLInputElement>(null);
const resultsRef = useRef<HTMLDivElement>(null);
/* Keep ref in sync for stable access in callbacks */
useEffect(() => {
selectedRef.current = selectedIndex;
}, [selectedIndex]);
/* Load index (ref-guarded, no race conditions) */
const loadIndex = useCallback(async () => {
if (index.length > 0 || loadingRef.current) return;
loadingRef.current = true;
try {
const res = await fetch('/palette-index.json');
const data: PaletteEntry[] = await res.json();
setIndex(data.map(indexEntry));
} catch (err) {
console.error('Failed to load palette index:', err);
loadingRef.current = false;
}
}, [index.length]);
/* OPEN / CLOSE */
const open = useCallback(async () => {
await loadIndex();
setIsOpen(true);
setQuery('');
setSelectedIndex(-1);
}, [loadIndex]);
const close = useCallback(() => {
setIsOpen(false);
setSelectedIndex(-1);
}, []);
/* GLOBAL KEYBOARD SHORTCUTS */
useEffect(() => {
const onKeydown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
isOpen ? close() : open();
}
if (e.key === 'Escape' && isOpen) {
e.preventDefault();
close();
}
};
document.addEventListener('keydown', onKeydown);
return () => document.removeEventListener('keydown', onKeydown);
}, [isOpen, open, close]);
/* FOCUS INPUT WHEN OPENING*/
useEffect(() => {
if (isOpen) requestAnimationFrame(() => inputRef.current?.focus());
}, [isOpen]);
/* FILTERED ENTRIES -> RENDER ROWS */
const filtered = useMemo(() => {
if (!query.trim()) {
return index.filter((e) => e.depth <= 2).slice(0, MAX_DEFAULT);
}
const q = normalize(query);
return index
.map((entry) => ({ entry, score: scoreEntry(entry, q) }))
.filter((r) => r.score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, MAX_SEARCH)
.map((r) => r.entry);
}, [index, query]);
const rows = useMemo(() => buildRows(filtered), [filtered]);
/* COUNT OF NAVIGABLE RESULTS (excludes labels) */
const resultCount = useMemo(
() => rows.filter((r) => r.kind === 'result').length,
[rows],
);
/* RESET SELECTION ON QUERY CHANGE */
useEffect(() => {
setSelectedIndex(-1);
}, [query]);
/*SCROLL SELECTED INTO VIEW */
useEffect(() => {
if (selectedIndex < 0) return;
resultsRef.current
?.querySelector(`[data-index="${selectedIndex}"]`)
?.scrollIntoView({ block: 'nearest' });
}, [selectedIndex]);
/* INPUT KEYBOARD NAVIGATION */
const onInputKeydown = useCallback(
(e: KeyboardEvent) => {
if (!resultCount) return;
if (e.key === 'ArrowDown') {
e.preventDefault();
setSelectedIndex((i) => (i + 1) % resultCount);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setSelectedIndex((i) => (i <= 0 ? resultCount - 1 : i - 1));
} else if (e.key === 'Enter') {
e.preventDefault();
const idx = selectedRef.current;
const target = rows.find((r) => r.kind === 'result' && r.index === idx);
if (target?.entry) {
window.location.href = target.entry.path;
}
}
},
[resultCount, rows],
);
/* RENDER */
return (
<>
<button
class={styles.trigger}
onClick={open}
aria-label="Open Navigation"
>
<span>Navigate</span>
<kbd class={styles.kbd}>K</kbd>
</button>
{isOpen && <div class={styles.backdrop} onClick={close} />}
{isOpen && (
<div
class={styles.palette}
role="dialog"
aria-label="Navigation palette"
>
<div class={styles.header}>
<span class={styles.icon}></span>
<input
ref={inputRef}
class={styles.input}
type="text"
placeholder="Where do you want to go?"
autocomplete="off"
spellcheck={false}
value={query}
onInput={(e) => setQuery((e.target as HTMLInputElement).value)}
onKeyDown={onInputKeydown}
/>
<kbd class={styles.esc}>esc</kbd>
</div>
<div class={styles.results} ref={resultsRef} role="listbox">
{rows.map((row) =>
row.kind === 'label' ? (
<div key={row.key} class={styles.groupLabel}>
{row.group}
</div>
) : (
<a
key={row.key}
class={`${styles.result}${row.index === selectedIndex ? ` ${styles.selected}` : ''}`}
href={row.entry!.path}
role="option"
data-index={row.index}
aria-selected={row.index === selectedIndex}
>
<span class={styles.type}>
{TYPE_LABELS[row.entry!.type] ?? row.entry!.type}
</span>
<span class={styles.label}>{row.entry!.label}</span>
<span class={styles.path}>{row.entry!.path}</span>
<span class={styles.arrow}></span>
</a>
),
)}
{resultCount === 0 && (
<div class={styles.empty}>No results found</div>
)}
</div>
<div class={styles.footer}>
<div class={styles.group}>
<span>
<kbd></kbd>
<kbd></kbd> navigate
</span>
<span>
<kbd></kbd> open
</span>
</div>
<span>
<kbd>esc</kbd> close
</span>
</div>
</div>
)}
</>
);
}

View File

@@ -0,0 +1,129 @@
---
import CommandPalette from "./CMDPalette"
---
<header class="site-header">
<div class="inner">
<a href="/" class="link">
<span class="site-logo">◬</span>
<span class="site-name">
dave
<span class="bracket">[</span>
dmg
<span class="bracket">]</span>
</span>
</a>
<CommandPalette client:load />
</div>
</header>
<style>
.site-header {
@mixin py var(--el-masthead-paddingY);
position: sticky;
z-index: 9;
top: 0;
width: 100%;
color: var(--color-text-inverse);
background-color: var(--color-surface-inverse);
}
.inner {
@mixin layout-wrapper;
display: flex;
flex-direction: row;
gap: var(--ui-spacing-cozy);
align-items: center;
justify-content: flex-start;
font-size: var(--el-masthead-font-size);
line-height: var(--el-masthead-line-height);
}
.site-logo {
display: inline-block;
font-family: var(--font-mono);
animation:
logo-pulse 5s cubic-bezier(0.4, 0, 0.6, 1) infinite,
logo-glitch 13s step-end infinite;
}
.site-name {
font-family: var(--font-mono);
}
.bracket {
color: var(--color-secondary);
}
@keyframes logo-pulse {
0% {
opacity: 1;
}
25% {
opacity: 0.66;
}
50% {
opacity: 0.33;
}
75% {
opacity: 0.66;
}
100% {
opacity: 1;
}
}
@keyframes logo-glitch {
0%,
84% {
transform: translate(0, 0);
filter: brightness(1);
}
85% {
transform: translate(-2px, 0);
filter: brightness(0.4);
}
86% {
transform: translate(1px, -1px);
filter: brightness(0.2);
}
87% {
transform: translate(-1px, 1px);
filter: brightness(0.7);
}
88% {
transform: translate(2px, 0);
filter: brightness(0.1);
}
89% {
transform: translate(-2px, 1px);
filter: brightness(0.5);
}
90% {
transform: translate(0, -1px);
filter: brightness(0.8);
}
91%,
100% {
transform: translate(0, 0);
filter: brightness(1);
}
}
</style>

View File

@@ -0,0 +1,75 @@
---
import { Image } from 'astro:assets';
interface Props {
src: string;
alt: string;
caption?: string;
}
const { src, alt, caption } = Astro.props;
const images = import.meta.glob<{ default: ImageMetadata }>(
'/src/content/**/cover/*.{png,jpg,jpeg,webp,avif}',
{ eager: true },
);
const imagePath = `/src${src}`;
const image = images[imagePath]?.default;
---
<figure class="cover">
{
image ? (
<Image
class="image"
src={image}
alt={alt}
widths={[600, 900, 1280, 1440, 1600, 1920]}
sizes="100vw"
loading="lazy"
/>
) : (
<img class="image" src={src} alt={alt} loading="lazy" />
)
}
<figcaption class="caption">
<div class="wrapper">
{
caption ? (
<span class="caption-text">{caption}</span>
) : (
<span class="caption-meta">{src}</span>
)
}
</div>
</figcaption>
</figure>
<style>
.cover {
@mixin border-t 12px, solid, var(--color-surface-inverse);
}
.image {
width: 100%;
height: auto;
max-height: 60vw;
object-fit: cover;
}
.caption {
padding: var(--spacing-snug);
font-family: var(--font-mono);
color: var(--color-text-inverse);
background-color: var(--color-surface-inverse);
}
.wrapper {
font-size: var(font-size-responsive);
text-align: center;
}
.caption-text,
.caption-meta {
font-size: var(--typo-size-sm);
}
</style>

View File

@@ -0,0 +1,78 @@
---
interface Props {
tags: string[];
updateDate?: string;
}
const { tags, updateDate } = Astro.props;
---
<div class="meta">
<div class="section">
<span class="label"> Author </span>
<span class="author item">Dave Damage</span>
</div>
{
tags && tags.length > 0 && (
<div class="section">
<span class="label">Tags</span>
<ul class="taglist">
{tags.map((tag) => (
<li class="tag item">{tag}</li>
))}
</ul>
</div>
)
}
{
updateDate && (
<div class="section">
<span class="label">Last Update</span>
<time class="updatedate item">{updateDate}</time>
</div>
)
}
</div>
<style>
.meta {
@mixin layout-wrapper;
flex-wrap: wrap;
display: flex;
align-items: baseline;
gap: var(--spacing-relaxed);
}
.section {
min-width: 12ch;
@mixin py var(--spacing-snug);
flex: 1 0 auto;
}
.label {
@mixin mb var(--spacing-tight);
@mixin pb var(--spacing-tight);
@mixin border-b 2px, solid, var(--color-border-strong);
display: block;
font-size: var(--typo-size-xs);
text-transform: uppercase;
letter-spacing: var(--typo-spacing-comfortable);
}
.item {
font-family: var(--font-mono);
font-size: var(--font-size-sm);
font-weight: 700;
color: var(--color-text-secondary);
text-transform: uppercase;
}
.taglist {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-tight);
}
.tag {
@mixin py var(--spacing-tight);
@mixin px var(--spacing-snug);
border: 4px solid var(--color-palette-charcoal-gray);
letter-spacing: var(--typo-spacing-relaxed);
background: var(--color-palette-charcoal-gray);
color: var(--color-palette-off-white);
}
</style>

View File

@@ -0,0 +1,68 @@
---
import type { BreadcrumbSegment } from '@lib/types/content';
interface Props {
breadcrumbs: BreadcrumbSegment[];
publicationDate: string;
}
const { breadcrumbs, publicationDate } = Astro.props;
---
<div class="overline">
<div class="wrapper">
<nav aria-label="breadcrumb">
<ol class="breadcrumbs">
<li class="crumb">
<a href="/" class="link">dave-dmg.de</a>
</li>
{
breadcrumbs.map((crumb) => (
<li class="crumb">
<span class="separator">/</span>
<a href={crumb.href} class="link">
{crumb.label}
</a>
</li>
))
}
</ol>
</nav>
<time class="publicationdate">
⟫ {publicationDate}
</time>
</div>
</div>
<style>
.overline {
background-color: var(--color-surface-inverse);
}
.wrapper {
@mixin layout-wrapper;
padding-block: var(--spacing-snug);
display: flex;
align-items: center;
justify-content: space-between;
font-size: var(--typo-size-responsive);
}
.breadcrumbs {
display: flex;
font-family: var(--font-mono);
font-size: var(--typo-size-sm);
color: var(--color-text-inverse);
}
.link {
color: var(--color-text-inverse);
transition: color 0.5s ease-in-out;
&:hover {
color: var(--color-tertiary);
}
}
.publicationdate {
font-family: var(--font-mono);
font-size: var(--typo-size-xs);
font-weight: var(--typo-weight-bold);
color: var(--color-text-inverse);
}
</style>

View File

@@ -0,0 +1,38 @@
---
interface Props {
title: string;
subtitle?: string;
}
const { title, subtitle } = Astro.props;
console.log(subtitle);
---
<div class="wrapper">
<h1 class="title">{title}</h1>
{subtitle && <p class="subtitle">{subtitle}</p>}
</div>
<style>
.wrapper {
@mixin layout-wrapper;
@mixin py var(--spacing-relaxed);
}
.title {
font-size: var(--el-h1-color);
font-family: var(--el-h1-font-family);
font-size: var(--el-h1-font-size);
line-height: var(--typo-leading-tight);
letter-spacing: -0.025em;
}
.subtitle {
@mixin mt var(--spacing-snug);
font-family: var(--font-header);
font-size: var(--typo-size-lg);
font-weight: 300;
letter-spacing: 0.075em;
text-transform: uppercase;
}
</style>

View File

@@ -0,0 +1,46 @@
---
import type { BreadcrumbSegment } from '@lib/types/content';
import Overline from './Overline.astro';
import Title from './Title.astro';
import Meta from './Meta.astro';
import Cover from './Cover.astro';
interface Props {
title: string;
cover?: {
src: string;
alt: string;
caption: string;
showInHeader: boolean;
};
subtitle?: string;
tags: string[];
publishDate: string;
updateDate?: string;
breadcrumbs: BreadcrumbSegment[];
}
const { title, cover, subtitle, tags, publishDate, updateDate, breadcrumbs } =
Astro.props;
const showCover = cover?.src && cover?.showInHeader;
console.log(Astro.props);
---
<header class="wrapper">
<Overline breadcrumbs={breadcrumbs} publicationDate={publishDate} />
<Title title={title} subtitle={subtitle} />
{
showCover && (
<Cover alt={cover.alt} src={cover.src} caption={cover?.caption} />
)
}
<Meta tags={tags} updateDate={updateDate} />
</header>
<style>
.wrapper {
@mixin border-b 8px, solid, var(--color-border-strong);
background-color: var(--color-surface-base);
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

View File

@@ -0,0 +1,309 @@
---
title: Alchemical Materialism
subtitle: A Systematic Framework for Worldbuilding Through Elemental Combination
summary: A Systematic Framework for Worldbuilding Through Elemental Combination
cover:
src: /content/articles/alchemical-materialism/cover/src.png
alt: Man approach a volcano
caption: The Eshian God-Alchemists claimed this was the face of Monad
showInHeader: true
publishDate: 2026-02-20T11:17:00.000Z
status: published
isFeatured: false
parent: framework
tags:
- Was ist Was?
- Framework
- Worldbuilding
relatedArticles: []
seo:
noIndex: false
---
## Overview
- **Foundational system** of the crucible
- Generates **entities** cultures, religions, states, vessels, magical traditions through **systematic elemental combination,** not arbitrary assignment
- Two interlocking element sets
- **Three Primes =>** Soul, Body, Spirit; dynamic forces in tension
- **Five Essences =>** Earth, Fire, Water, Air, Aether; multivalent elemental character
- **Elemental Pool Mechanic =>** add tokens from material conditions and history, tally, spend on capabilities, traits, and relationships
- **One Engine =>** elemental pool drives everything; capabilities, identity, and cultural character all flow from the same source
## The Three Primes
- *Dynamic Forces in Tension* within each entity
- Correspond to the three alchemical principles observed when substance is placed in the crucible:
- Something **burns** (Sulfur)
- Something **remains** (Salt)
- Something **transforms** (Mercury)
- Each entity contains all three; dominance determines character.
- Material conditions determine which Prime dominates; not cultural preference or collective personality
- **Interdependence:**
- Without **Soul,** nothing starts; structure sits inert, nothing transforms
- Without **Body,** nothing holds; fire has no fuel, transforming has no material
- Without **Spirit,** nothing changes; fire burns the same thing forever, structure never adapts
{% table %}
- Prime
- Glyph
- Alchemical
- Core
- Keyword
- Organization
- Failure
---
- [Body](/the-crucible/references/elements/body)
- {% ElementSymbol
element="body"
size="var(--typo-size-2xl)"
color="inherit" /%}
- Salt
- Structure; the form
- »It endures«
- Institutional strength
- Calcifies everything
---
- [Soul](/the-crucible/references/elements/soul)
- {% ElementSymbol
element="soul"
size="var(--typo-size-2xl)"
color="inherit" /%}
- Sulfur
- Agency; the Spark
- »I burn«
- Individual excellence
- Consumes everything
---
- [Spirit](/the-crucible/references/elements/spirit)
- {% ElementSymbol
element="spirit"
size="var(--typo-size-2xl)"
color="inherit" /%}
- Mercury
- Transformation; the flux
- »Nothing stays«
- Adaptive innovation
- Dissolves everything
{% /table %}
## The Five Essences
- Describe the **material character** of an entity
- Operates on two tiers simultaneously
- **Character =>** Material expression; how the essence manifests across any domain of society
- **Symbolic =>** Thematic resonance; enriches religion, mythology, cultural flavour
- Each essence has a **primary capability affinity** and two **secondary affinities;** connecting them to the five universal capability tracks
- Essences are multivalent → it can express across multiple domains but it has a home
{% table %}
- Essence
- Symbol
- Properties
- IS
- Image
---
- [Aether](/the-crucible/references/elements/aether)
- {% ElementSymbol
element="aether"
size="var(--typo-size-2xl)"
color="inherit" /%}
- Quintessence
- Transcendent, ordered, numinous
- The Stars
---
- [Air](/the-crucible/references/elements/air)
- {% ElementSymbol element="air" size="var(--typo-size-2xl)" color="inherit" /%}
- Hot & Wet
- Invisible, expansive, permeating
- The Storm
---
- [Earth](/the-crucible/references/elements/earth)
- {% ElementSymbol
element="earth"
size="var(--typo-size-2xl)"
color="inherit" /%}
- Cold & Dry
- Heavy, material, foundational
- The Mountain
---
- [Fire](/the-crucible/references/elements/fire)
- {% ElementSymbol
element="fire"
size="var(--typo-size-2xl)"
color="inherit" /%}
- Hot & Dry
- Bright, consuming, refining
- The Volcano
---
- [Water](/the-crucible/references/elements/water)
- {% ElementSymbol
element="water"
size="var(--typo-size-2xl)"
color="inherit" /%}
- Cold & Wet
- Flowing, deep, dissolving
- The River
{% /table %}
### The Five Capability Tracks
{% table %}
- Domain
- Covers
---
- **Prosperity**
- Labor, production, construction, infrastructure
---
- **Warfare**
- Armies, defense, fortifications, armaments
---
- **Statecraft**
- Governance, administration, law, diplomacy
---
- **Lore**
- Knowledge, education, medicine, philosophy, sciences
---
- **Rites**
- Religion, ritual, sacred practice, arcane arts, cosmology
{% /table %}
#### Essence-to-Capability Affinity
{% table %}
- Essence
- Primary
- Secondary 1
- Secondary 2
---
- **Aether**
- Rites
- Statecraft
- Lore
---
- **Air**
- Lore
- Prosperity
- Warfare
---
- **Earth**
- Prosperity
- Warfare
- Rites
---
- **Fire**
- Warfare
- Lore
- Statecraft
---
- **Water**
- Statecraft
- Rites
- Prosperity
{% /table %}
### The Transformation Triangle
- Three elements involve Transformation → distinguished by mode
- **Fire =>** Transformation is *destructive * purification through consumption
- **Water =>** Transformation is *gradual* erosion, dissolution, blending
- **Spirit =>** Transformation is *synthetic* *solve et coagula,* a new thing born from recombination
{% table %}
- Element
- What happens
- Process
- Image
- Reversible
---
- **Fire**
- Destroys to create
- Input consumed; new thing exists
- The Forge
- No Ore is gone, steel remains
---
- **Water**
- Dissolves to mix
- Boundaries erode; things blend
- The Solvent
- Partially Salt in Water is still salt-and-water
---
- **Spirit**
- Recombines to become
- Inputs loose identity; genuinely new things emerge
- The Alembic
- No the product is neither ingredient
{% /table %}
## The Seven Aspects
- Provide additional specificity
- Corresponding to the 7 classical metals and their planetary associations
- Categorise what exists and operates within the World
{% table %}
- Aspects
- Symbol
- Metal
- Planet
- Association
---
- *Entities*
- {% ElementSymbol
element="entities"
size="var(--typo-size-2xl)"
color="inherit" /%}
- Silver
- Moon
- Living beings; flesh, beasts, plants, spirits
---
- *Matter*
- {% ElementSymbol
element="matter"
size="var(--typo-size-2xl)"
color="inherit" /%}
- Lead
- Saturn
- Physical substances; materials, terrain, stone, foundations
---
- *Mind*
- {% ElementSymbol
element="mind"
size="var(--typo-size-2xl)"
color="inherit" /%}
- Quicksilver
- Mercury
- Thought, consciousness; intellect, emotion, skills
---
- *Society*
- {% ElementSymbol
element="society"
size="var(--typo-size-2xl)"
color="inherit" /%}
- Tin
- Jupiter
- Collective organisation; governance, law, institutions
---
- *Art*
- {% ElementSymbol element="art" size="var(--typo-size-2xl)" color="inherit" /%}
- Copper
- Venus
- Creation, expression; artistry, rituals, craft
---
- *Mysteries*
- {% ElementSymbol
element="mysteries"
size="var(--typo-size-2xl)"
color="inherit" /%}
- Gold
- Sun
- Hidden, occult; magic, destiny, secrets, *residua*
---
- *Forces*
- {% ElementSymbol
element="forces"
size="var(--typo-size-2xl)"
color="inherit" /%}
- Iron
- Mars
- Energies, natural laws; power, conflict, dynamics
{% /table %}

View File

@@ -0,0 +1,13 @@
---
title: Advanced Warhammer Quest
summary: A dungeoncrawler for the last millenium!
cover:
showInHeader: false
publishDate: 2026-02-27T14:39:00.000Z
status: published
isFeatured: false
tags: []
relatedArticles: []
seo:
noIndex: false
---

View File

@@ -0,0 +1,13 @@
---
title: Chainbreaker
summary: Last blood in a world gone mad
cover:
showInHeader: false
publishDate: 2026-02-27T14:40:00.000Z
status: published
isFeatured: false
tags: []
relatedArticles: []
seo:
noIndex: false
---

View File

@@ -0,0 +1,14 @@
---
title: Elements
summary: References for all the Elements
cover:
showInHeader: false
publishDate: 2026-02-24T09:44:00.000Z
status: published
isFeatured: false
parent: references
tags: []
relatedArticles: []
seo:
noIndex: false
---

View File

@@ -0,0 +1,23 @@
---
title: The Framework
summary: Conceptual Foundation of Crucible
cover:
showInHeader: false
publishDate: 2026-02-20T11:14:00.000Z
status: published
isFeatured: false
parent: the-crucible
tags: []
relatedArticles: []
seo:
noIndex: false
---
- Alchemical Materialism
- The Primes
- The Essences
- Affinity
- Pool
- Design Principles
- Tiers
- Domains
- Sin Engine

View File

@@ -0,0 +1,20 @@
---
title: Materia
subtitle: The Elemental Pool
summary: Foundation of the Crucible, the elemental pool
cover:
showInHeader: false
publishDate: 2026-02-25T23:19:00.000Z
status: published
isFeatured: false
parent: framework
tags: []
relatedArticles: []
seo:
noIndex: false
---
- Pool is both **identity** and **budget**
1. **Generate Materia =>** from *material conditions* (environment, subsistence, mythology, history)
1. **Read Identity =>** From a grid depending on the entity's nature (e.g. *Ethos, Theology, Polity)*
1. **Spend tokens**
- Leftover tokens are not carried over

View File

@@ -0,0 +1,17 @@
---
title: Prima Materia
subtitle: Where we read the earth and learn what it provides
summary: Where we read the earth and learn what it provides
cover:
showInHeader: true
publishDate: 2026-02-25T23:28:00.000Z
status: published
isFeatured: false
parent: the-crucible
tags:
- Crucible Stage
- Generation
relatedArticles: []
seo:
noIndex: false
---

View File

@@ -0,0 +1,14 @@
---
title: References
summary: Where we collect all the references
cover:
showInHeader: false
publishDate: 2026-02-24T09:43:00.000Z
status: published
isFeatured: false
parent: the-crucible
tags: []
relatedArticles: []
seo:
noIndex: false
---

View File

@@ -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**

View File

@@ -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 };

View File

@@ -0,0 +1,52 @@
---
title: Aether
subtitle: The Transcendent, The Ordered, The Numinous
summary: Exploring the Essence »Aether« in the Alchemical Materialism framework
cover:
showInHeader: false
publishDate: 2026-02-24T13:02:00.000Z
status: published
isFeatured: false
parent: elements
tags:
- Essences
relatedArticles: []
seo:
noIndex: false
category: essence
symbol:
discriminant: font
value:
family: Unigrim Dee
character: D
---
- **Alchemical Properties =>** the fifth element; beyond the four mundane; celestial, incorruptible
- **Primary Affinity =>** Rites
- **Associations**
- What lies beyond the material; the fifth thing
- Cosmic order → the pattern behind apparent chaos
- The numinous → the experience of something greater
- The bridge between mortal and divine; threshold substance
- Meaning itself; the answer to »why does this matter?«
- **Material Character**
- *Rites =>* Priesthoods, temples, divine mandate; fate, destiny, the inescapable
- *Statecraft =>* Divine legitimacy, sacred law, oaths before gods, hierarchy sanctified from above
- *Lore =>* Revelation, prophecy, mystical insight; knowledge from beyond; mystery traditions
- *Warfare =>* Holy war, divine champions, sacred weapons; morale & terror; the fear of the supernatural
- *Prosperity =>* Sacred crafts, ritual objects; things made for purposes beyond the materials;
- **Symbolic**
- Transcendence
- Order
- Mystery
- Meaning
- The Sacred
- What make mundane things matter
- The pattern behind the noise
- Incorruptible and therefore terrifying
- The divine gaze
- **Failures**
- Disconnection from reality
- Cosmic order that crushes mortal will
- Meaning so heavy it makes life unbearable
- Madness from contact with the transcendent
- **Key Image =>** the Stars

View File

@@ -0,0 +1,50 @@
---
title: Air
subtitle: The Invisible, The Expansive, The Permeating
summary: Exploring the Prime »Air« in the Alchemical Materialism framework
cover:
showInHeader: false
publishDate: 2026-02-24T13:01:00.000Z
status: published
isFeatured: false
parent: elements
tags:
- Essences
relatedArticles: []
seo:
noIndex: false
category: essence
symbol:
discriminant: font
value:
family: Unigrim Dee
character: H
---
- **Alchemical Properties =>** Hot & Wet; ascending, expanding, permeating
- **Primary Affinity =>** Lore
- **Associations**
- The invisible medium; what fills all space
- Breath literally, life itself; pneuma
- Movement, speed, freedom; what cannot be grasped
- The space between things; the medium of sound and speech
- Expansion without limit; the storm that scatters
- **Material Character**
- *Lore →* Speech, rhetoric, philosophy, abstract thought; pneuma as intellect; the word as power
- *Statecraft →* Communication, rumour, reputation on the wind; freedom from obligation: the ungovernable
- *Warfare →* Cavalry, archery, speed & mobility; hit-&-run; the charge; storms as divine wrath
- *Prosperity →* Windmills, sailing, ventilation of mines; the medium through which trade moves
- *Rites →* Sky-gods, storm deities, breath-of-life; divine voice; oracles; the heavens
- **Symbolic**
- Freedom
- Invisibility
- Speed
- Communication
- Expansion
- **Failures**
- Scattering
- Nothing holds
- All form dispersed
- Rootlessness
- The storm that destroys all structure
- Empty air → nothing there at all
- **Key Image =>** the Storm

View File

@@ -0,0 +1,23 @@
---
title: Art
summary: Exploring the Aspect »Art« in the Alchemical Materialism framework
cover:
showInHeader: false
publishDate: 2026-02-25T21:56:00.000Z
status: published
isFeatured: false
parent: elements
tags: []
relatedArticles: []
seo:
noIndex: false
category: aspect
symbol:
discriminant: font
value:
family: Unigrim Dee
character: F
---
- **Planet =>** Venus
- **Metal =>** Copper
- Creation, expression; artistry, rituals, craft

View File

@@ -0,0 +1,49 @@
---
title: Body
summary: Exploring the Prime »Body« in the Alchemical Materialism framework
cover:
alt: Salt »What remains«
showInHeader: false
publishDate: 2026-02-24T12:27:00.000Z
status: published
isFeatured: false
parent: elements
tags:
- Primes
relatedArticles: []
seo:
noIndex: false
category: prime
symbol:
discriminant: font
value:
family: Unigrim Dee
character: M
---
- **Alchemical principle =>** the fixed residue; what's left after burning; the stable principle
- **Core =>** Structure; form; persistence; what endures
- **Keyword =>** »It endures«
- **Principles**
- The principle of **structural persistence;** the substrate
- The institution that outlives its founders; the tradition that continues because it continues
- *Body* doesn't drive action: it's what's already there when action occurs
- Soul ignites, spirit transforms, Body is the **medium and the resistance**
- **Organisational expressions:** Institutional production: organised labor, standardised input, persistent infrastructure
- **Expressions**
- The bureaucracy processing forms centuries after anyone remembers why
- The kinship system determining marriage partners before birth
- The city walls that still stand
- The language that shapes though before you think
- The caste that tells you who you are before you are born
- The road network that dictates where trade flows
- **Failures**
- Calcification
- Rigidity
- the structure that cannot bend and therefore breaks
- the dead institution
- Tradition that crushes all life from what it holds
- **Quality**
- Fixed
- Crystalline
- Enduring
- Structural

View File

@@ -0,0 +1,50 @@
---
title: Earth
summary: Exploring the Prime »Earth« in the Alchemical Materialism framework
cover:
alt: The Material, The Heavy, The Foundation
showInHeader: false
publishDate: 2026-02-24T12:56:00.000Z
status: published
isFeatured: false
parent: elements
tags:
- Essences
relatedArticles:
- alchemical-materialism
seo:
noIndex: false
category: essence
symbol:
discriminant: font
value:
family: Unigrim Dee
character: X
---
- **Alchemical Properties =>** Cold & Dry; descending, condensing, solidifying
- **Primary affinity =>** Prosperity
- **Associations**
- Weight, density, solidity; the things that's *there*
- The ground beneath; territory, boundary, the physical
- Resistance to movement; inertia; mass
- The body itself: flesh, bone, muscle
- What can be held, measured, counted, divided
- **Material Character**
- *Prosperity →* Extraction, agriculture, physical labour; working the land; masonry, road-building
- *Warfare →* Heavy infantry, siege, fortification; holding ground; crushing weight
- *Statecraft →* Territorial administration; »this is MY land«;
- *Lore →* Practical craft, material science; knowing by doing and touching
- *Rites →* Sacred ground, burial rites, fertility rituals; the body as sacred; chthonic power
- **Symbolic**
- Rootedness
- Stubbornness
- Endurance
- Possession
- Gravity
- The foundational
- **Failures**
- Immoveable
- Crushing
- Suffocating
- The weight that buries
- **Key Image:** the Mountain

View File

@@ -0,0 +1,23 @@
---
title: Entities
summary: Exploring the Aspect »Entities« in the Alchemical Materialism framework
cover:
showInHeader: false
publishDate: 2026-02-25T21:42:00.000Z
status: published
isFeatured: false
parent: elements
tags: []
relatedArticles: []
seo:
noIndex: false
category: aspect
symbol:
discriminant: font
value:
family: Unigrim Dee
character: O
---
- **Metal =>** Silver
- **Planet =>** Moon
- Living beings; flesh, beasts, plants, spirits

View File

@@ -0,0 +1,47 @@
---
title: Fire
subtitle: The Bright, The Consuming, The Refining
summary: Exploring the Prime »Fire« in the Alchemical Materialism framework
cover:
showInHeader: false
publishDate: 2026-02-24T12:58:00.000Z
status: published
isFeatured: false
parent: elements
tags:
- Essences
relatedArticles: []
seo:
noIndex: false
category: essence
symbol:
discriminant: font
value:
family: Unigrim Dee
character: C
---
- **Alchemical Properties =>** Hot & Dry; ascending, illuminating, consuming
- **Primary affinity =>** Warfare
- **Associations**
- Light that reveals; heat that transforms
- Consumption → Fire needs fuel and destroys what it burns
- The Forge → Destruction that creates something better
- Illuminations and blindness simultaneously
- Purification through burning away impurity
- **Material Character**
- *Warfare →* scorched earth, purges, burning the heretic; destroying to cleanse; Greek Fire; purification through destruction
- *Prosperity →* the forge, the kiln; smelting, glasswork, ceramics; refining the raw into finished
- *Statecraft →* passionate bonds, burning loyalty; consuming rivalries; bonds that burn bright and burn out
- *Lore →* revelation, illumination, mastery; orthodoxy as the »one true light«
- *Rites →* sacred flames, purification rites, trial by fire; burnt offerings; the sun as god
- **Symbolic**
- Brilliance
- Hunger
- Purification
- Consumption
- the hearth that warms AND the wildfire that destroys
- **Failures**
- Consumes all fuel
- Scorches what it meant to warm
- Purification that leaves nothing alive
- **Key Image:** the Forge

View File

@@ -0,0 +1,23 @@
---
title: Forces
summary: Exploring the Aspect »Forces« in the Alchemical Materialism framework
cover:
showInHeader: false
publishDate: 2026-02-25T22:01:00.000Z
status: published
isFeatured: false
parent: elements
tags: []
relatedArticles: []
seo:
noIndex: false
category: aspect
symbol:
discriminant: font
value:
family: Unigrim Dee
character: B
---
- **Planet =>** Mars
- **Metal =>** Iron
- Energies, natural laws; power, conflict, dynamics

View File

@@ -0,0 +1,23 @@
---
title: Matter
summary: Exploring the Aspect »Matter« in the Alchemical Materialism framework
cover:
showInHeader: false
publishDate: 2026-02-25T21:49:00.000Z
status: published
isFeatured: false
parent: elements
tags: []
relatedArticles: []
seo:
noIndex: false
category: aspect
symbol:
discriminant: font
value:
family: Unigrim Dee
character: S
---
- **Metal =>** Lead
- **Planet =>** Saturn
- Physical substances; materials, terrain, stone, foundations

View File

@@ -0,0 +1,23 @@
---
title: Mind
summary: Exploring the Aspect »Mind« in the Alchemical Materialism framework
cover:
showInHeader: false
publishDate: 2026-02-25T21:51:00.000Z
status: published
isFeatured: false
parent: elements
tags: []
relatedArticles: []
seo:
noIndex: false
category: aspect
symbol:
discriminant: font
value:
family: Unigrim Dee
character: I
---
- **Metal =>** Quicksilver
- **Planet =>** Mercury
- Thought, consciousness; intellect, emotion, skills

View File

@@ -0,0 +1,23 @@
---
title: Mysteries
summary: Exploring the Aspect »Mysteries« in the Alchemical Materialism framework
cover:
showInHeader: false
publishDate: 2026-02-25T21:59:00.000Z
status: published
isFeatured: false
parent: elements
tags: []
relatedArticles: []
seo:
noIndex: false
category: aspect
symbol:
discriminant: font
value:
family: Unigrim Dee
character: R
---
- **Planet =>** Sun
- **Metal =>** Gold
- Hidden, occult; magic, destiny; secrets, realms beyond

View File

@@ -0,0 +1,23 @@
---
title: Society
summary: Exploring the Aspect »Society« in the Alchemical Materialism framework
cover:
showInHeader: false
publishDate: 2026-02-25T21:54:00.000Z
status: published
isFeatured: false
parent: elements
tags: []
relatedArticles: []
seo:
noIndex: false
category: aspect
symbol:
discriminant: font
value:
family: Unigrim Dee
character: L
---
- **Metal =>** Tin
- **Planet =>** Jupiter
- Collective organisation; governance, institutions, law

View File

@@ -0,0 +1,46 @@
---
title: Soul
summary: Exploring the Prime »Soul« in the Alchemical Materialism framework
cover:
showInHeader: false
publishDate: 2026-02-24T11:26:00.000Z
status: published
isFeatured: false
parent: elements
tags:
- Primes
relatedArticles: []
seo:
noIndex: false
category: prime
symbol:
discriminant: font
value:
family: Unigrim Dee
character: A
---
- **Alchemical Principle =>** the combustible essence; what makes fire catch; the active principle
- **Core =>** Agency; drive; the spark that initiates action
- **Keyword =>** »I burn«
- **Principles**
- The principle of **individual agency and animation**
- Greed is one expression; so is passion, obsession, curiosity, protective fury, creative drive
- What unites Soul expressions: the fire originates in *a person,* not a structure or process
- **Organisational expression:** Individual excellence; master craftsmen, personal glory, competitive innovation
- **Expressions**
- The merchant hoarding silver
- The sculptor destroying a statue because of a imperfection only they can see
- The parent killing to protect their child
- The explorer walking into the unknown
- The warrior seeking glory
- The inventor breaking every rule
- **Failures:**
- Consumes everything
- Burns out
- Burns what it touches
- Individual drive that destroys because it cannot moderate
- **Qualities:**
- Combustible
- Animating
- Individual
- Igniting

View File

@@ -0,0 +1,57 @@
---
title: Spirit
subtitle: Mercury What transforms
summary: Exploring the Prime »Spirit« in the Alchemical Materialism framework
cover:
showInHeader: false
publishDate: 2026-02-24T12:42:00.000Z
status: published
isFeatured: false
parent: elements
tags:
- Primes
relatedArticles:
- alchemical-materialism
seo:
noIndex: false
category: prime
symbol:
discriminant: font
value:
family: Unigrim Dee
character: E
---
- **Alchemical Principle =>** the volatile mediator; what escapes, dissolves, and recombines; the transformation principle
- **Core =>** Transformation, synthesis; *becoming*
- **Keyword =>** »Nothing stays, everything becomes«
- **Principles**
- The principle of **transformation as a positive force;** not mere reaction or survival
- *»Solve et coagula«* → dissolve and recombine
- Mediation, adaption, and change-as-drive are all expressions of transformation
- Mercury is the *most alchemically important* of the three → the philosopher's stone ingredient
- **Organisational Expression**
- Adaptive innovation
- Cross-pollination
- Hybrid techniques
- Syncretic methods
- **Expressions**
- Syncretic cultures absorbing and remaking what they encounter
- Trade-diaspora peoples existing between cultures; transforming both
- Revolutionary movements dissolving old structures
- Liminal societies at crossroads, borders, thresholds
- Alchemists literally
- Seasonal/cyclical peoples participating in cycles of transformation
- Mediators who create new arrangements, not just broker peace
- **Failures**
- Dissolves everything
- Nothing holds
- Identity lost
- Perpetual revolution devouring its own
- Formlessness
- Mercury slipping through every grasp
- **Quality**
- Volatile
- Liminal
- Mercurial
- Syncretic
- Transformative

View File

@@ -0,0 +1,50 @@
---
title: Water
subtitle: The Flowing, The Deep, The Dissolving
summary: Exploring the Essence »Water« in the Alchemical Materialism framework
cover:
showInHeader: false
publishDate: 2026-02-24T12:59:00.000Z
status: published
isFeatured: false
parent: elements
tags:
- Essences
relatedArticles: []
seo:
noIndex: false
category: essence
symbol:
discriminant: font
value:
family: Unigrim Dee
character: Q
---
- **Alchemical Properties =>** Cold & Wet; descending, flowing, dissolving
- **Primary Affinity =>** Statecraft
- **Associations**
- The universal solvent -> what dissolves boundaries
- Flow, connection → the medium of exchange
- Depth and concealment → What's hidden beneath the surface
- Life-giving AND drowning: rain AND Flood
- Takes the shape of its container; adapt to form
- **Material Character**
- *Statecraft →* Networks, exchange, reciprocity; debts that flow; diplomacy; status as current
- *Prosperity →* Irrigation, fishing, brewing, dyeing; the river as economic artery
- *Arms →* Naval power, coastal raiding, poison; erosion/attrition warfare
- *Lore →* Hidden knowledge, mysteries of the deep; intuition over analysis; the murky and the clear
- *Rites →* Baptism, sacred rivers, purification by washing; sea-gods; the abyss
- **Symbolic**
- Fluidity
- Depth
- Concealment
- Connection
- Erosion
- What flows between
- What lies beneath
- Patience that wears stone away
- **Failures**
- Drowning
- Erosion
- Formlessness
- **Key Image:** the River

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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',

View File

@@ -0,0 +1,66 @@
import { collection, fields } from '@keystatic/core';
import { createBaseArticleFields } from '@fields/base-article.ts';
import { createContentField } from '@fields/content.ts';
export const elements = collection({
label: 'Elements',
slugField: 'title',
path: 'src/content/crucible/elements/*/',
columns: ['category'],
format: {
contentField: 'body',
},
schema: {
...createBaseArticleFields(),
body: createContentField(),
category: fields.select({
label: 'Category',
options: [
{
label: 'Prime',
value: 'prime',
},
{
label: 'Essence',
value: 'essence',
},
{
label: 'Aspect',
value: 'aspect',
},
],
defaultValue: 'prime',
}),
symbol: fields.conditional(
fields.select({
label: 'Type',
defaultValue: 'font',
options: [
{ label: 'Font', value: 'font' },
{ label: 'SVG', value: 'svg' },
],
}),
{
svg: fields.text({
label: 'SVG Markup',
multiline: true,
}),
font: fields.object({
family: fields.select({
label: 'Font Family',
defaultValue: 'Unigrim Dee',
options: [
{ label: 'Unigrim Dee', value: 'Unigrim Dee' },
{ label: 'Unigrim Hochenheim', value: 'Unigrim Hochenheim' },
{ label: 'Unigrim Trithemius', value: 'Unigrim Trithemius' },
],
}),
character: fields.text({
label: 'Character',
}),
}),
},
),
},
});

View File

@@ -0,0 +1,7 @@
import { elements } from './elements';
const crucibleCollections = {
cr_elements: elements,
};
export default crucibleCollections;

View File

@@ -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',

View File

@@ -0,0 +1,50 @@
import { componentIcon } from '@keystar/ui/icon/icons/componentIcon';
import { inline } from '@keystatic/core/content-components';
import { fields } from '@keystatic/core';
const elementCompontent = {
ElementSymbol: inline({
label: 'Element Symbol',
icon: componentIcon,
schema: {
element: fields.relationship({
label: 'Element',
collection: 'cr_elements',
}),
size: fields.select({
label: 'Size',
defaultValue: 'var(--typo-size-md)',
options: [
{ label: '2XS', value: 'var(--typo-size-2xs)' },
{ label: 'XS', value: 'var(--typo-size-xs)' },
{ label: 'SM', value: 'var(--typo-size-sm)' },
{ label: 'MD', value: 'var(--typo-size-md)' },
{ label: 'LG', value: 'var(--typo-size-lg)' },
{ label: 'XL', value: 'var(--typo-size-xl)' },
{ label: '2XL', value: 'var(--typo-size-2xl)' },
{ label: '3XL', value: 'var(--typo-size-3xl)' },
{ label: '4XL', value: 'var(--typo-size-4xl)' },
{ label: '5XL', value: 'var(--typo-size-5xl)' },
{ label: '6XL', value: 'var(--typo-size-6xl)' },
{ label: '7XL', value: 'var(--typo-size-7xl)' },
{ label: '8XL', value: 'var(--typo-size-8xl)' },
],
}),
color: fields.select({
label: 'Color',
defaultValue: 'inherit',
options: [
{ label: 'Inherit', value: 'inherit' },
{ label: 'primary', value: 'var(--color-primary)' },
{ label: 'secondary', value: 'var(--color-secondary)' },
{ label: 'tertiary', value: 'var(--color-tertiary)' },
{ label: 'primary', value: 'var(--color-primary)' },
{ label: 'Text', value: 'var(--color-text-primary)' },
{ label: 'Text Inverse', value: 'var(--color-text-inverse)' },
],
}),
},
}),
};
export default elementCompontent;

View File

@@ -0,0 +1,5 @@
import elementComponent from './element.ts';
export const generalComponents = {
...elementComponent,
};

View File

@@ -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',

View File

@@ -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<string, ContentComponent> = {};
export const sharedComponents: Record<string, ContentComponent> =
generalComponents;
export const createContentField = (
additionalComponents?: Record<string, ContentComponent>,

View File

@@ -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;
---
<Base title={title} seo={seo}>
<main class="content">
<article>
<h1>{title}</h1>
<Content />
</article>
</main>
</Base>
<ContentLayout entry={entry} collectionName="articles" />

View File

@@ -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 || '';
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
<title>{pageTitle}</title>
{pageDescription && <meta name="description" content={pageDescription} />}
{seo?.noIndex && <meta name="robots" content="noindex" />}
</head>
<body>
<MastHead />
<slot />
</body>
</html>

View File

@@ -0,0 +1,58 @@
---
import type { CollectionEntry } from 'astro:content';
import { render } from 'astro:content';
import Base from '@layouts/Base.astro';
import Header from '@compontents/layout/PostHeader/index.astro';
import { getBreadcrumbs } from '@lib/utils/paths';
import { toMilitaryDTG } from '@lib/utils/date';
interface Props {
entry: CollectionEntry<'articles'> | CollectionEntry<'elements'>;
collectionName: string;
}
const { entry, collectionName } = Astro.props;
const { Content } = await render(entry);
const { title, summary, subtitle, updateDate, publishDate, tags, seo } =
entry.data;
const breadcrumbs = await getBreadcrumbs(
entry.data.parent ?? null,
collectionName,
);
const formattedPublishDate = toMilitaryDTG(publishDate);
const formattedUpdateDate = updateDate
? toMilitaryDTG(updateDate)
: formattedPublishDate;
const rawCover = entry.data.cover;
const headerCover =
rawCover?.src && rawCover?.showInHeader
? {
src: rawCover.src,
alt: rawCover.alt ?? '',
caption: rawCover.caption ?? '',
showInHeader: rawCover.showInHeader,
}
: undefined;
---
<Base title={title} seo={seo}>
<main>
<article>
<Header
title={title}
breadcrumbs={breadcrumbs}
publishDate={formattedPublishDate}
updateDate={formattedUpdateDate}
cover={headerCover}
tags={tags}
subtitle={subtitle}
/>
<div class="content">
<slot name="before-content" />
<Content />
<slot name="after-content" />
</div>
</article>
</main>
</Base>

Some files were not shown because too many files have changed in this diff Show More