Try deploy
Some checks failed
Build and Deploy DAVE | DMGs Site / deploy (push) Failing after 3m22s

This commit is contained in:
2026-03-05 11:39:28 +01:00
parent ea0f471fd3
commit 3dec403492
40 changed files with 1093 additions and 117 deletions

View File

@@ -0,0 +1,42 @@
name: Build and Deploy DAVE | DMGs Site
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 8
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: Build
run: pnpm run build
- name: Deploy
run: |
rm -rf /var/www/dave-dmg/blog/*
cp -r dist/* /var/www/dave-dmg/blog/

View File

@@ -1,4 +1,9 @@
import { defineMarkdocConfig, component, nodes } from '@astrojs/markdoc/config';
import {
defineMarkdocConfig,
component,
nodes,
Markdoc,
} from '@astrojs/markdoc/config';
export default defineMarkdocConfig({
nodes: {
@@ -16,5 +21,70 @@ export default defineMarkdocConfig({
color: { type: String },
},
},
Callout: {
render: component('./src/components/content/Callout.astro'),
attributes: {
type: {
type: String,
default: 'default',
},
title: { type: String },
},
},
Figure: {
render: component('./src/components/content/Figure.astro'),
selfClosing: true,
attributes: {
src: {
type: String,
required: true,
},
alt: {
type: String,
required: true,
},
caption: { type: String },
credit: { type: String },
},
},
Sidenote: {
selfClosing: true,
attributes: {
id: {
type: String,
required: true,
},
title: {
type: String,
},
marker: {
type: String,
default: '⋄',
},
content: {
type: String,
required: true,
},
type: {
type: String,
default: 'default',
},
},
transform(node, config) {
const attrs = node.transformAttributes(config);
return new Markdoc.Tag('sup', {}, [
new Markdoc.Tag(
'a',
{
href: `#${attrs.id}`,
id: `ref-${attrs.id}`,
class: 'sidenote-ref',
style: `anchor-name: --note-${attrs.id}`,
},
[`[${attrs.marker}]`],
),
]);
},
},
},
});

View File

@@ -0,0 +1,103 @@
---
interface Props {
type: | "default" | "optional" | "example" | "spoiler";
title?: string;
}
const { type, title} = Astro.props;
const isSpoiler = type === 'spoiler'
---
{isSpoiler ? (
<details class={`callout spoiler`}>
<summary>
<span class="title">{title || 'Show spoiler'}</span>
<span class="badge">{type.toUpperCase()}</span>
</summary>
<div class="body">
<slot />
</div>
</details>
) : (
<div class={`callout ${type}`}>
{title && <header class="title">{title}</header>}
<span class="badge">{type.toUpperCase()}</span>
<div class="body">
<slot />
</div>
</div>
)}
<style>
.callout {
--callout-symbol: "";
@mixin my var(--space-8);
position: relative;
padding: var(--space-8) var(--space-8) var(--space-8) var(--space-14);
border: var(--space-1) solid var(--color-surface-inverse);
box-shadow: var(--color-shadow);
&::before {
content: var(--callout-symbol);
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: flex-start;
justify-content: center;
width: var(--space-10);
height: 100%;
font-family: var(--font-mono);
font-size: var(--type-4);
line-height: 1.875;
color: var(--color-primary);
background: var(--color-surface-inverse);
}
& .title {
@mixin typo_body-alt;
@mixin my var(--size-0);
font-family: var(--font-header);
font-weight: 900;
text-transform: uppercase;
}
& .badge {
@mixin px var(--size-4);
@mixin py var(--size-2);
@mixin typo_code;
position: absolute;
top: 0;
right: 0;
color: var(--color-text-inverse);
background: var(--color-surface-inverse);
}
&.example {
--callout-symbol: '◆';
}
&.default {
--callout-symbol: '‽';
}
&.optional {
--callout-symbol: '★'
}
&.spoiler {
--callout-symbol: "⌧"
}
}
</style>

View File

@@ -0,0 +1,63 @@
---
import { Image } from 'astro:assets';
interface Props {
src: string;
alt: string;
caption?: string;
credit?: string;
}
const { src, alt, caption, credit } = Astro.props;
const images = import.meta.glob<{ default: ImageMetadata }>(
'/src/content/**/*.{png,jpg,jpeg,webp,avif}',
{ eager: true },
);
const imagePath = `/src${src}`;
console.log(imagePath);
const image = images[imagePath]?.default;
---
<figure class="wrapper">
{image ? (
<Image src={image} alt={alt} loading="lazy" />
) : (
<img src={src} alt={alt} loading="lazy" />
)}
{(caption || credit) && (
<figcaption class="container">
{caption && <span class="caption">{caption}</span>}
{credit && <cite class="credit">{credit}</cite>}
</figcaption>
)}
</figure>
<style>
.wrapper {
@mixin border-x var(--size-6), solid, var(--color-surface-inverse);
}
.container {
padding: var(--space-4);
background-color: var(--color-surface-inverse);
& .caption {
@mixin typo_code;
display: block;
color: var(--color-text-inverse);
text-align: center;
}
& .credit {
@mixin typo_fine;
display: block;
color: var(--color-palette-stone);
text-align: right;
}
}
</style>

View File

@@ -0,0 +1,114 @@
---
interface Props {
id: string;
marker: string;
type: string;
content: string;
title: string;
}
const {id, marker, type, content, title} = Astro.props
---
<div class="note" id={id} style={`position-anchor: --note-${id}; --marker: "${marker}"`}>
<div class="container content">
{title && <header class="title">{title}</header>}
<span class="badge">{type.toUpperCase()}</span>
<p class="body">{content}</p>
<footer class="backref">
<a href={`#ref-${id}`}>↩</a>
</footer>
</div>
</div>
<style>
.note {
scroll-margin-top: calc(var(--layout-header-height) + var(--size-8));
font-size: clamp(1rem, 2.5vw, 1.25rem);
@supports (anchor-name: --test) {
@media (--bp-desktop) {
position: absolute;
top: calc(anchor(top) - var(--size-10));
}
}
}
.container {
position: relative;
padding: var(--space-10) var(--space-8) var(--space-6) var(--space-14);
border: var(--space-1) solid var(--color-surface-inverse);
box-shadow: var(--color-shadow);
&::before {
content: var(--marker);
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: flex-start;
justify-content: center;
width: var(--space-10);
height: 100%;
font-family: var(--font-mono);
font-size: var(--type-4);
line-height: 1.875;
color: var(--color-primary);
background: var(--color-surface-inverse);
}
& .title {
@mixin typo_body-alt;
@mixin my var(--size-0);
font-family: var(--font-header);
font-weight: 900;
text-transform: uppercase;
}
& .badge {
@mixin px var(--size-4);
@mixin py var(--size-2);
@mixin typo_code;
position: absolute;
top: 0;
right: 0;
color: var(--color-text-inverse);
background: var(--color-surface-inverse);
}
& .body {
@mixin typo_body-small;
}
& .backref {
@mixin mt var(--size-4);
text-align: right;
& a {
@mixin pa var(--size-2);
font-weight: 700;
color: var(--text-color-tertiary);
transition:
color var(--motion-normal) var(--ease-out),
background var(--motion-normal) var(--ease-out);
&:hover {
color: var(--color-text-inverse);
text-decoration: none;
background: var(--color-primary);
}
}
}
}
</style>

View File

@@ -57,8 +57,7 @@ const image = images[imagePath]?.default;
}
.caption {
padding: var(--spacing-snug);
font-family: var(--font-mono);
padding: var(--space-4);
color: var(--color-text-inverse);
background-color: var(--color-surface-inverse);
}
@@ -70,6 +69,6 @@ const image = images[imagePath]?.default;
.caption-text,
.caption-meta {
font-size: var(--typo-size-sm);
@mixin typo_code;
}
</style>

View File

@@ -21,7 +21,7 @@ seo:
- **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
- **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
@@ -210,8 +210,6 @@ seo:
- No the product is neither ingredient
{% /table %}
---
## The Seven Aspects
- Provide additional specificity
@@ -243,6 +241,12 @@ seo:
- Mercury
- Thought, consciousness; intellect, emotion, skills
---
- *Mysteries*
- {% ElementSymbol element="mysteries" size="var(--type-3)" color="inherit" /%}
- Gold
- Sun
- Hidden, occult; magic, destiny, secrets, *residua*{% Sidenote #residua title="What are Residua?" marker="⋄" content="Residua are our fancy name for otherworldly realms the energies that were left over when the world was created. Think the underworld, inferno, or the elemental planes." type="default" /%}
---
- *Society*
- {% ElementSymbol element="society" size="var(--type-3)" color="inherit" /%}
- Tin
@@ -255,12 +259,6 @@ seo:
- Venus
- Creation, expression; artistry, rituals, craft
---
- *Mysteries*
- {% ElementSymbol element="mysteries" size="var(--type-3)" color="inherit" /%}
- Gold
- Sun
- Hidden, occult; magic, destiny, secrets, *residua*
---
- *Forces*
- {% ElementSymbol element="forces" size="var(--type-3)" color="inherit" /%}
- Iron

View File

@@ -0,0 +1,321 @@
---
title: Domain
subtitle: What the earth provides
summary: >-
Climate, topography, biome, features, resources. The ground beneath; what
grows, what weathers, what the soil yields. The material conditions that force
civilizations into being.
cover:
showInHeader: false
publishDate: 2026-03-05T08:47:00.000Z
status: published
isFeatured: false
parent: prima-materia
tags:
- Prima Materia
- Generation
- Domain
relatedArticles: []
seo:
noIndex: false
---
## Overview
- **Map is the Starting Point;** napkin sketch or Azgaar export
- Domain = Climate + Topography + Biome + Features + Resources
- **Scale**
- Locality → Territory → Province → Region → Area → Landmass
- *Features* and *resources* are **locality-level** a river is *here*, iron is in *this* valley
- *Resources* = **abundance,** not exclusive access; most common material is available to anyone; the list marks what a locality has in surplus
- **Climate =>** 10 types; simplified Köppen-Geiger; temperature, precipitation, characteristics
- **Topography =>** 6 types; elevation and terrain character
- **Biome =>** 10 types; dominant vegetation;
- **Features**{% Sidenote #features-resources title="Relevance of features & resources" marker="⋄" content="Features and resources are only relevant on the locality level" type="optional" /%} **=>** 10 types; rivers, coasts, volcanoes, passes, settlements; affect availability tables downstream
- **Resources =>** 75 entries; what the land yields in abundance
- Tables follow below; elemental association appear later when they become mechanically relevant
## Procedure
1. **Pick your area or region** the broad stroke; a continent's western coast, a mountain range, a river basin
1. **Generate localities within it** each locality gets its own Climate, Topography, Biome
- For those wanting a more random approach, the following [table](https://docs.google.com/spreadsheets/d/16d9MUMFMqbUXkL6bOHN3sbFSvQiR0K3yta--RiW4Ldw/edit?usp=sharing) can be used to generate biomes based on Climate, Topography, and Features
1. **Assign features** rivers, coasts, passes, settlements
1. **Roll for resources** each locality has a **1-in-10 chance** to generate a resource
1. **Read the dominant pattern** the Climate, Topography, and Biome that appear most across localities become the domain signature for the culture living there
{% Callout type="example" title="Gwayth Teyd" %}
Looking at the map for my »Chainbreaker« setting, I decide to start with the area on the western coast of Khimbria my northern continent which is dominated by temperate rainforest. Knowing that I want to the dominant culture to be inspired by the britannic tribes of the antiquity, I decide to call it: the Gwayth Ted.
{% /Callout %}
## Climate
- Borrowed and simplified from Köppen-Geiger climate classification
- Ten types covering the range of conditions your world might produce
{% table %}
- Climate
- Temp.
- Precipiation
- K-G
- Characteristics
---
- *Artic*
- Extremely cold winters (< -30°C); cool summers (<10°C)
- Very low; < 300 mm/year
- ET, EF
- Cold year-round; mostly snow; permafrost
---
- *Arid*
- Hot summers (>30°C) or cold winters (<0°C)
- Very low; < 250mm/year
- BWh, BWk
- True desert; hot (BWh) or cold (BWk)
---
- *Continental*
- Cold winters (<0°C); hot summers (>25°C)
- Moderate; 500 1000 mm/year
- Dfa, Dfb, Dwa, Dwb
- Sharp seasonal swing; summer rainfall
---
- *Mediterranean*
- Mild winters (0 10°C); hot summers (>25°C)
- Moderate; 500900mm/year; dry summers
- Csa, Csb
- Hot dry summers; wet winters
---
- *Monsoon*
- Warm year-round (>20°C); mild seasonal variation
- Very high seasonal; 15003000+mm/year in wet seasonal
- Am
- Extreme wet/dry cycle; flooding defines calendar
---
- *Oceanic*
- Mild winters (010°C); cool summers (<25°C)
- High; > 800mm/year
- Cfb, Cfc
- Mild year-round; contant rainfall; grey skies
---
- *Semiarid*
- Cold winters (often <0°C); hot summers (>25°C)
- Low to moderate; 250mm/year
- BSh, BSk
- Steppe; drier than grassland, wetter than desert
---
- *Subarctic*
- Very cold winters (< -20°C); short summers (<15°C)
- Low to moderate; 300600mm/year
- Dfc, Dfd, Dwc, Dwd
- Brutal winters; brief growing season
---
- *Subtropical*
- Mild winters (510°C); hot summers (>22°C)
- Moderate to high; 600-1200mm/year
- Cfa, Cwa
- Humid heat; mild winters; even rainfall
---
- *Tropical*
- Warm year-round; avg > 18°C
- High; > 1500mm/year
- Af, Aw/As
- Constant warmth; heavy rain; Aw/As have dry season
{% /table %}
## Topography
- Lay of the land
- Elevation shapes what grows, who lives here, and how hard it is to move
{% table %}
- Topography
- Elevation
- Characteristics
---
- *Lowlands*
- Below sea level to 50 m
- Deltas, polders, fens, tidal flats; often waterlogged or reclaimed; fertile but floodprone
---
- *Plains*
- 50300 m
- Open steppe, prairie, farmland; wide horizons; wind-exposed; good for agriculture and cavalry
---
- *Hills*
- 300800 m
- Rolling terrain; mixed land use; defensible ridgelines; broken sightlines
---
- *Highlands*
- 800 1500 m (flat)
- Elevated plateaus; wind-scoured; isolated; natural fortress country
---
- *Mountains*
- 1500+ m
- Peaks, passes, and valleys; vertical climate zones; barriers to movement; mineral wealth
---
- *Badlands*
- Variable
- Eroded, broken terrain; gullies, mesas, exposed rock; poor soil; difficult to settle, easy to hide in
{% /table %}
## Biome
- Dominant Vegetation
- What grows tells you what lives
{% table %}
- Biome
- Description
---
- *Barren*
- Salt flats, volacanic waste, eroded badlands; no significant vegetation; exposed rock and mineral deposits
---
- *Tundra*
- Permafrost beneath low scrub, mosses, lichens; treeless; wind-exposed; brief growing season
---
- *Shrublands*
- Dry-adapted woody scrub; charparral, maquis, fynbos; fire-prone, wind-scoured, quick to regenerate
---
- *Grasslands*
- Open steppe, prairie, pampas; deep fertile soil beneath tall grasses; few trees, wide horizons
---
- *Savanna*
- Tropical or subtropical grassland with scattered trees; distinct wet and dry seasons; periodic burns
---
- *Wetlands*
- Bogs, marshes, fens, swamps; waterlogged ground; dense reed bets, peat deposists; high biodiversity
---
- *Woods*
- Open canopy with clearings; light penetrates to ground level; mixed undergrowth; edge habitat
---
- *Forest*
- Dense temperate woodland; heavy canopy; deep leaf litter, large hardoods; oak, beech, maple
---
- *Rainforest*
- Multi-layered tropical canopy; constant moisture; maximum biodiversity; epiphytes, vines, dense undergrowth
---
- *Taiga*
- Boreal connifer forest; spruce, pine, larch over muskeg and frozen ground;
{% /table %}
## Features
- Geographical features at the locality level
{% table %}
- Feature
- Description
---
- *River*
- Flowing freshwater; trade artery, irrigation source, natural boundary; floods deposit fertile silt
---
- *Coast*
- Land meets sea; harbors, tidal flats, salt marshes; access to maritime trade and naval power
---
- *Lake*
- Enclosed body of water; fishery, freshwater reserve, natural landmark; still water hides depth
---
- *Floodplain*
- Low ground beside rivers; seasonally inundated; rich alluvial soik; high yield, high risk
---
- *Oasis*
- Isolated water source in arid terrain; date palms, wells, caravan stops; life surrounded by nothing
---
- *Volcano*
- Active or dormant; volcanic soil is fertile, eruptions devastating; obsidian, sulfur, hot spring nearby
---
- *Island*
- Land surrounded by water; isolation breeds distinctiveness; defensible, resource-limited, reade-dependent
---
- *Mountain Pass*
- Navigable route through high terrain; chokepoint for trade and invasion; whoever holds it controls movement
---
- *Town*
- Permanent settlement; market, craft production, local administration; polulation 10005000
---
- *City*
- Major settlement; walls, institutions, dense population, surplus extraction, specialization, political gravity; population 5000+
{% /table %}
## Resources
- What the land provides
- Each locality has a **1-in-10 chance** to generate a resource
- Mark **abundance** a defining surplus worth trading or fighting over
- Resources are used in the *Conjuction* state
- Use the following [table](https://docs.google.com/spreadsheets/d/1bUUr5F93SrQ4fZm7ONf2YTudJ-1lnbyKiFHbtLgMgrY/edit?usp=sharing) to roll for the resource of a locality
| Resources | Description |
|--------------------|-------------------------------------------------------------------------------------------------------|
| Stone | Limestone, granite, sandstone, slate; ubiquitous building and toolmaking material |
| Base Metals | Copper, tin, lead, zinc; smelted from ore into everyday tools, vessels, fittings |
| Vegetables | Root crops, legumes, leafy greens; garden staples grown in moist soil |
| Fruits | Orchard and vine crops; apples, figs, pomegranates, pears; wind-pollinated, sun-ripened |
| Staple Grains | Wheat, barley, millet, sorghum; the caloric foundation of settled civilization |
| Clay | Shaped wet, hardened by kiln; pottery, brick, tile, ceramic |
| Alchemical Earths | Calcium oxide, potash, natron, vitriol; reactive mineral substances used in processing and craft |
| Fish | River and coastal catch; preserved by salt, smoke, or drying |
| Hunting Companions | Hawks, falcons, hounds; trained animals used in pursuit and killing of game |
| Resins | Hardened tree sap; pine tar, frankincense, myrrh; sealant, adhesive, incense |
| Salt | Mineral preservative and seasoning; harvested from sea, spring, or mine |
| Oilseeds | Sesame, flax, rapeseed; pressed and heated into oil for lamps, cooking, and craft |
| Furs | Beaver, rabbit, fox pelts; insulation, clothing, and high-volume trade commodity |
| Livestock | Cattle, sheep, goats, pigs; kept for meat, milk, wool, leather, traction |
| Reeds | Marsh grasses; used for weaving, thatching, basket-making, matting |
| Hemp | Coarse fiber plant; rope, sailcloth, sacking, rough textile |
| Softwoods | Pine, fir, spruce; fast-growing construction timber and primary fuel wood |
| Dye Plants | Indigo, madder, weld; plants processed into colorfast textile dyes |
| Flax | Fine fiber plant; retted and spun into linen; light, breathable fabric and sailcloth |
| Aromatic Herbs | Lavender, rosemary, sage, thyme; culinary and ritual aromatics |
| Woad | Leaf-derived blue pigment; body paint, textile dye; labor-intensive extraction |
| Psychoactives | Mushrooms, ergot, datura, cannabis; mind-altering substances used in ritual and medicine |
| Honey | Bee product; sweetener, preservative, fermented into mead; harvested from wild or kept hives |
| Wild Game | Deer, boar, hare, game birds; hunted in open country and woodland |
| Earth Pigments | Ochre, umber, sienna; mineral pigments ground for paint, body decoration, and burial rites |
| Seals | Marine mammals hunted for blubber, oil, meat, and heavy pelts; cold-water resource |
| Iron | Smelted from bog or mountain ore; tools, weapons, structural hardware; the essential metal |
| Olives | Long-lived orchard tree; fruit pressed into oil for cooking, lighting, preservation, and anointing |
| Hardwoods | Oak, ash, elm, walnut; dense timber for shipbuilding, furniture, tools, construction |
| Jade | Nephrite or jadeite; dense, hard stone worked into blades, ornaments, and ritual objects |
| Dates | Desert palm fruit; high-calorie staple of arid regions; dried for storage and transport |
| Artisan Stone | Alabaster, marble, pumice, soapstone; decorative and sculptural stone shaped by cutting and polishing |
| Dyes | Processed colorants; cochineal, kermes, woad extract; requiring boiling, mordanting, vat fermentation |
| Bamboo | Fast-growing giant grass; hollow, light, strong; construction, scaffolding, weapons, tools |
| Obsidian | Volcanic glass; fractures to razor edges; blades, arrowheads, mirrors |
| Rice | Grain cultivated in flooded paddies; requires irrigation infrastructure and water management |
| Citrus | Lemons, oranges, limes; juicy, acidic fruit; prevents scurvy, flavors food, preserves |
| Papyrus | Aquatic reed processed into writing material; stripped, layered, pressed, dried |
| Grapes | Vine fruit; eaten fresh, dried as raisins, crushed and fermented into wine and vinegar |
| Medicinal Herbs | Healing plants; prepared as poultice, tincture, tea, or salve; specialized knowledge required |
| Special Livestock | Camels, llamas, yaks, reindeer; animals adapted to extreme terrain; transport, milk, fiber, meat |
| Cotton | Tropical fiber plant; ginned, carded, spun into soft, dyeable textile |
| Pelts | Luxury furs; ermine, sable, marten; high-value status goods moving through long-distance trade |
| Horses | Riding, draft, and pack animal; cavalry, courier, transport; requires pasture and fodder |
| Silk | Caterpillar cocoon fiber; reeled, twisted, woven into extremely fine, strong fabric |
| Maize | Corn; high-yield grain requiring processing (nixtamalization); flour, porridge, flatbread |
| Yew | Slow-growing toxic hardwood; exceptional bow-stave timber; durable, flexible, poisonous |
| Tea | Dried leaf steeped in hot water; stimulant beverage; requires specific highland growing conditions |
| Ivory (marine) | Walrus and narwhal tusk; carved into fine objects, jewelry, inlay; rare and highly portable |
| Amber | Fossilized tree resin; translucent, sometimes containing preserved insects; ornamental and trade good |
| Porphyry | Dense purple-red igneous stone; extremely hard to quarry and work; imperial building material |
| Bitumen | Natural tar and petroleum seeps; waterproofing, adhesive, fuel, caulking |
| Murex | Sea snail harvested for purple dye; thousands of shells yield grams of pigment |
| Saffron | Crocus flower stigma; harvested by hand, three threads per flower; dye, spice, medicine |
| Kaolin | Crocus flower stigma; harvested by hand, three threads per flower; dye, spice, medicine |
| War Beasts | Elephants, rhinoceri, or similar; large animals trained for combat; living siege weapons |
| Precious Stones | Diamonds, rubies, sapphires, emeralds; formed under extreme pressure and heat; cut and polished |
| Arsenic | Realgar, orpiment; toxic mineral compounds; pigment, poison, glass-making additive |
| Sulfur | Yellow mineral from volcanic deposits; combustible; used in fumigation, medicine, fire-weapons |
| Cinnabar | Mercury sulfide ore; bright vermillion pigment; toxic; source of liquid mercury |
| Sugar | Cane or beet juice boiled and crystallized; requires heavy irrigation and intensive labor |
| Spices | Pepper, cinnamon, clove, nutmeg; dried seeds, bark, and buds; flavor, preservation, medicine |
| Pearls | Formed inside oysters and mussels; harvested by diving; gems requiring no cutting or polishing |
| Precious Metals | Gold and silver; panned, mined, smelted; currency, jewelry, gilding, ritual objects |
| Silphium | Extinct or near-extinct plant; medicinal, contraceptive, culinary; once worth its weight in coin |
| Steppe Horses | Superior war and riding breeds from harsh grassland; exceptional endurance and hardiness |
| Coffee | Roasted bean brewed into stimulant drink; requires specific tropical highland growing conditions |
| Whales | Hunted for oil, bone, baleen, ambergris; deep-ocean pursuit requiring specialized vessels |
| Rare Woods | Ebony, lignum vitae, purpleheart, sandalwood; dense exotic timber from distant sources |
| Cedar | Aromatic rot-resistant softwood; temple and ship timber; naturally insect-repellent |
| Cocoa | Tree pod containing bitter seeds; roasted and ground into paste or drink; requires tropical lowlands |
| Incense | Frankincense, myrrh, and similar gum resins; burned to produce aromatic smoke |
| Lapis Lazuli | Deep blue metamorphic stone; ground into ultramarine pigment; jewelry, inlay, decoration |
| Coca | Shrub leaf chewed or brewed as stimulant; suppresses hunger and altitude sickness; highland crop |
| Poppy | Flower producing opium latex; powerful painkiller and sedative; highly addictive |
{% Callout type="example" title="Gwayth Teyd Part II" %}
After having generated the localities of the Gwayth Teyd, I discover that most of its localities have a *Oceanic climate* while the terrain is mostly *Lowlands* with a *Forest* vegetation. The Gwayth Teyd also features a surpsingly high number of *Silk* and *Dye* resources.
{% /Callout %}

View File

@@ -0,0 +1,19 @@
---
title: Kindred
subtitle: What the Body is
summary: >-
Species-level biology. The body you're born in, not the culture you're born
into. Humans are the baseline; everything else carries elemental weight.
cover:
showInHeader: false
publishDate: 2026-03-05T08:51:00.000Z
status: draft
isFeatured: false
parent: prima-materia
tags:
- Prima Materia
- Generator
relatedArticles: []
seo:
noIndex: false
---

View File

@@ -0,0 +1,13 @@
---
title: The Metatron
summary: Where meta stuff is presented
cover:
showInHeader: false
publishDate: 2026-03-04T23:45:00.000Z
status: published
isFeatured: false
tags: []
relatedArticles: []
seo:
noIndex: false
---

View File

@@ -11,7 +11,22 @@ parent: the-crucible
tags:
- Crucible Stage
- Generation
- Material Conditions
- Domain
- Kindred
relatedArticles: []
seo:
noIndex: false
---
## Overview
- **Stage 1** of the Crucible; tangible foundation
- Everything built later kin, faith, magic, states rests on what exists here
- The physical world before culture touches it
- Two halves:
- [**Domain:**](/the-crucible/prima-materia/domain) the land; climate, topography, biome, features, resources
- [**Kindred:**](/the-crucible/prima-materia/kindred) the biology; species-level bodies, physical traits, innate abilities
- Neither half generates *Culture* that's **Calcination**
- *Prima Materia* generates **Conditions;** the material constraints culture must respond to
- Different land, different problems; different problems, different solutions; different solutions, different civilisations
- The land does not care what you believe; the body does not care what you build

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

@@ -0,0 +1,39 @@
import { wrapper } from '@keystatic/core/content-components';
import { fields } from '@keystatic/core';
import { megaphoneIcon } from '@keystar/ui/icon/icons/megaphoneIcon';
const calloutComponent = {
Callout: wrapper({
label: 'Callout',
icon: megaphoneIcon,
schema: {
type: fields.select({
label: 'Type',
defaultValue: 'default',
options: [
{
label: 'Default',
value: 'default',
},
{
label: 'Optional',
value: 'optional',
},
{
label: 'Example',
value: 'example',
},
{
label: 'Spoiler',
value: 'spoiler',
},
],
}),
title: fields.text({
label: 'Title',
}),
},
}),
};
export default calloutComponent;

View File

@@ -0,0 +1,35 @@
import { imageIcon } from '@keystar/ui/icon/icons/imageIcon';
import { block } from '@keystatic/core/content-components';
import { fields } from '@keystatic/core';
const figureComponent = {
Figure: block({
label: 'Figure',
icon: imageIcon,
schema: {
src: fields.image({
label: 'Image',
directory: 'src/content/articles',
publicPath: '/content/articles',
}),
alt: fields.text({
label: 'Alt Text',
validation: {
length: {
min: 1,
},
},
}),
caption: fields.text({
label: 'Caption',
multiline: true,
}),
credit: fields.text({
label: 'Credit/Attribution',
description: 'Photographer, artist, or source',
}),
},
}),
};
export default figureComponent;

View File

@@ -1,5 +1,11 @@
import elementComponent from './element.ts';
import calloutComponent from './callout.ts';
import sidenoteComponent from './sidenote.ts';
import figureComponent from './figure.ts';
export const generalComponents = {
...elementComponent,
...calloutComponent,
...sidenoteComponent,
...figureComponent,
};

View File

@@ -0,0 +1,50 @@
import { inline } from '@keystatic/core/content-components';
import { fields } from '@keystatic/core';
import { panelRightDashedIcon } from '@keystar/ui/icon/icons/panelRightDashedIcon';
const sidenoteComponents = {
Sidenote: inline({
label: 'Sidenote',
icon: panelRightDashedIcon,
schema: {
id: fields.text({
label: 'ID',
validation: {
isRequired: true,
},
}),
title: fields.text({
label: 'Title',
}),
marker: fields.text({
label: 'Marker',
defaultValue: '⋄',
}),
content: fields.text({
label: 'Body',
multiline: true,
validation: { isRequired: true },
}),
type: fields.select({
label: 'Type',
options: [
{
value: 'default',
label: 'Default',
},
{
value: 'example',
label: 'Example',
},
{
value: 'optional',
label: 'Optional',
},
],
defaultValue: 'default',
}),
},
}),
};
export default sidenoteComponents;

View File

@@ -5,6 +5,8 @@ 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';
import { extractSidenotes } from '@lib/utils/extractors';
import Sidenote from '@compontents/content/Sidenote.astro';
interface Props {
entry: CollectionEntry<'articles'> | CollectionEntry<'elements'>;
@@ -15,7 +17,8 @@ const { entry, collectionName } = Astro.props;
const { Content } = await render(entry);
const { title, summary, subtitle, updateDate, publishDate, tags, seo } =
entry.data;
const hasMargin = Astro.slots.has('margin')
const sidenotes = entry.body ? extractSidenotes(entry.body) : [];
const hasMargin = Astro.slots.has('margin') || !!sidenotes;
const breadcrumbs = await getBreadcrumbs(
entry.data.parent ?? null,
@@ -57,6 +60,7 @@ const headerCover =
</div>
{hasMargin &&(
<aside class="margin">
{sidenotes.map(note => <Sidenote {...note} />)}
<slot name="margin" />
</aside>
)}
@@ -69,8 +73,9 @@ const headerCover =
.frame {
@mixin layout-wrapper;
@media (--bp-desktop) {
position: relative;
@media (--bp-desktop) {
display: grid;
grid-template-columns: var(--layout-content-width) var(--layout-margin-width);
gap: var(--layout-gutter);
@@ -82,7 +87,6 @@ const headerCover =
}
.margin {
position: relative;
min-width: 0;
}
</style>

View File

@@ -1,87 +0,0 @@
import { getCollection, type CollectionEntry } from 'astro:content';
export type ContentType = 'article' | 'page' | 'element';
export interface ResolvedEntry {
type: ContentType;
path: string;
entry:
| CollectionEntry<'pages'>
| CollectionEntry<'articles'>
| CollectionEntry<'elements'>;
}
function buildPath(
slug: string,
parentSlug: string | null,
articles: CollectionEntry<'articles'>[],
visited: Set<string> = new Set(),
): string {
if (!parentSlug) return slug;
if (visited.has(parentSlug)) {
console.warn(`Circular reference detected at ${parentSlug}`);
return slug;
}
visited.add(parentSlug);
const parent = articles.find((a) => a.slug === parentSlug);
if (!parent) return slug;
const parentPath = buildPath(
parent.slug,
parent.data.parent ?? null,
articles,
visited,
);
return `${parentPath}/${slug}`;
}
function resolveArticles(
articles: CollectionEntry<'articles'>[],
): ResolvedEntry[] {
return articles
.filter((entry) => entry.data.status === 'published')
.map((entry) => ({
type: 'article' as const,
path: buildPath(entry.slug, entry.data.parent ?? null, articles),
entry,
}));
}
interface GlobEntry {
id: string;
data: { status: string; parent?: string | null };
}
function resolveGlobCollection(
collection: GlobEntry[],
type: ContentType,
articles: CollectionEntry<'articles'>[],
): { type: ContentType; path: string; entry: GlobEntry }[] {
return collection
.filter((entry) => entry.data.status === 'published')
.map((entry) => ({
type,
path: buildPath(entry.id, entry.data.parent ?? null, articles),
entry,
}));
}
export async function resolveAllPaths(): Promise<ResolvedEntry[]> {
const articles = await getCollection('articles');
const elements = await getCollection('elements');
const pages = await getCollection('pages');
const resolvedPages: ResolvedEntry[] = pages.map((page) => ({
type: 'page' as const,
path: `static/${page.slug}`,
entry: page,
}));
return [
...resolveArticles(articles),
...resolveGlobCollection(elements, 'element', articles),
...resolvedPages,
] as ResolvedEntry[];
}

View File

@@ -41,3 +41,13 @@ export interface PaletteEntry {
parent: string | null;
depth: number;
}
/** Sidenote */
export interface Sidenote {
id: string;
title: string;
marker: string;
type: string;
content: string;
}

View File

@@ -0,0 +1,21 @@
import Markdoc from '@markdoc/markdoc';
import type { Sidenote } from '@lib/types/content';
export function extractSidenotes(raw: string): Sidenote[] {
const ast = Markdoc.parse(raw);
const sidenotes: Sidenote[] = [];
for (const node of ast.walk()) {
if (node.type === 'tag' && node.tag === 'Sidenote') {
const attrs = node.attributes;
sidenotes.push({
id: attrs.id,
marker: attrs.marker ?? '⋄',
title: attrs.title ?? '',
type: attrs.type ?? 'default',
content: attrs.content,
});
}
}
return sidenotes;
}

View File

@@ -83,7 +83,8 @@
--color-overlay-heavy: oklch(0% 0 0deg / 60%);
/* == Shadows == */
--color-shadow: oklch(0% 0 0deg / 10%);
--color-shadow: var(--space-2) var(--space-2) 0 oklch(from var(--color-surface-inverse) calc(l - 0.075) c h), var(--space-4) var(--space-4) 0 oklch(from var(--color-surface-inverse) calc(l - 0.2) c h);
;
--color-shadow-strong: oklch(0% 0 0deg / 25%);
/* == Utility == */

View File

@@ -233,7 +233,7 @@
contain: style;
list-style: none;
& li {
& > li {
@mixin ml var(--space-6);
@mixin pl var(--space-4);
@@ -302,7 +302,7 @@
border: var(--size-1) solid var(--color-border-strong);
color: var(--color-text-inverse);
text-align: left;
text-align: center;
background: var(--color-surface-inverse);
}
@@ -311,6 +311,14 @@
@mixin pa var(--space-4);
border: var(--size-1) solid var(--color-border-subtle);
text-align: center;
}
& th,
& td {
&:first-child {
text-align: left;
}
}
}
@@ -336,7 +344,8 @@
}
/* === DETAILS / SUMMARY === */
& details {
/* & details {
@mixin mr var(--space-8);
@mixin border-l var(--size-4), solod, var(--color-border-normal);
@@ -377,7 +386,7 @@
& details > *:not(summary) {
@mixin px var(--space-6);
}
} */
& hr {
@mixin my var(--space-10);
@@ -411,7 +420,8 @@
}
/* === FIGURES === */
& figure {
/* & figure {
@mixin my var(--space-10);
& img {
@@ -426,7 +436,7 @@
color: var(--color-text-tertiary);
}
}
} */
/* === INLINE ELEMENTS === */
@@ -459,6 +469,11 @@
text-decoration: none;
background: var(--color-primary);
}
&.sidenote-ref {
scroll-margin-top: calc(var(--layout-header-height) + var(--size-8));
text-decoration: none;
}
}
&:visited {
@@ -517,11 +532,11 @@
font-style: normal;
}
& cite {
/* & cite {
font-weight: 600;
font-style: normal;
color: var(--color-text-secondary);
}
} */
& q {
font-style: normal;

View File

@@ -11,6 +11,8 @@
src: url('/src/fonts/UnigrimDee-Regular.woff2') format('woff2');
}
/**
@font-face {
font-family: 'Iosevka Slab';
font-weight: 300;
@@ -148,3 +150,124 @@
src: url('/src/fonts/IosevkaSansMono/IosevkaSansMono-ExtraBoldItalic.woff2')
format('woff2');
}
**/
@font-face {
font-family: 'Julia Mono';
font-weight: 300;
font-style: normal;
font-display: swap;
src: url('/src/fonts/JuliaMono/JuliaMono-Light.woff2')
format('woff2');
}
@font-face {
font-family: 'Julia Mono';
font-weight: 400;
font-style: normal;
font-display: swap;
src: url('/src/fonts/JuliaMono/JuliaMono-Regular.woff2')
format('woff2');
}
@font-face {
font-family: 'Julia Mono';
font-weight: 500;
font-style: normal;
font-display: swap;
src: url('/src/fonts/JuliaMono/JuliaMono-Medium.woff2')
format('woff2');
}
@font-face {
font-family: 'Julia Mono';
font-weight: 600;
font-style: normal;
font-display: swap;
src: url('/src/fonts/JuliaMono/JuliaMono-SemiBold.woff2')
format('woff2');
}
@font-face {
font-family: 'Julia Mono';
font-weight: 700;
font-style: normal;
font-display: swap;
src: url('/src/fonts/JuliaMono/JuliaMono-Bold.woff2')
format('woff2');
}
@font-face {
font-family: 'Julia Mono';
font-weight: 900;
font-style: normal;
font-display: swap;
src: url('/src/fonts/JuliaMono/JuliaMono-Black.woff2')
format('woff2');
}
@font-face {
font-family: 'Julia Mono';
font-weight: 300;
font-style: italic;
font-display: swap;
src: url('/src/fonts/JuliaMono/JuliaMono-LightItalic.woff2')
format('woff2');
}
@font-face {
font-family: 'Julia Mono';
font-weight: 400;
font-style: italic;
font-display: swap;
src: url('/src/fonts/JuliaMono/JuliaMono-RegularItalic.woff2')
format('woff2');
}
@font-face {
font-family: 'Julia Mono';
font-weight: 500;
font-style: italic;
font-display: swap;
src: url('/src/fonts/JuliaMono/JuliaMono-MediumItalic.woff2')
format('woff2');
}
@font-face {
font-family: 'Julia Mono';
font-weight: 600;
font-style: italic;
font-display: swap;
src: url('/src/fonts/JuliaMono/JuliaMono-SemiBoldItalic.woff2')
format('woff2');
}
@font-face {
font-family: 'Julia Mono';
font-weight: 700;
font-style: italic;
font-display: swap;
src: url('/src/fonts/JuliaMono/JuliaMono-BoldItalic.woff2')
format('woff2');
}
@font-face {
font-family: 'Julia Mono';
font-weight: 900;
font-style: italic;
font-display: swap;
src: url('/src/fonts/JuliaMono/JuliaMono-BlackItalic.woff2')
format('woff2');
}

View File

@@ -146,8 +146,8 @@
/* === FONT FAMILIES === */
--font-header: 'Geist Variable', sans;
--font-display: 'Blaka',serif;
--font-body: 'Iosevka Slab', sans;
--font-mono: 'Iosevka Mono', monospace;
--font-body: 'Julia Mono', sans;
--font-mono: 'Julia Mono', monospace;
--font-symbols: 'Unigrim Dee', fantasy;
/* === SIZE (REM-based) === */
@@ -205,9 +205,10 @@
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
/* === LAYOUT === */
--layout-content-width: minmax(0, 72ch);
--layout-margin-width: minmax(18ch, 1fr);
--layout-gutter: var(--space-6);
--layout-content-width: minmax(0, 80ch);
--layout-margin-width: minmax(10ch, 1fr);
--layout-gutter: var(--space-12);
--layout-page-margin: var(--space-8);
--layout-max-width: 90rem;
--layout-header-height: 3.125rem;
}

View File

@@ -0,0 +1 @@