This commit is contained in:
42
.gitea/workflows/deploy.yml
Normal file
42
.gitea/workflows/deploy.yml
Normal 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/
|
||||
@@ -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}]`],
|
||||
),
|
||||
]);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
103
src/components/content/Callout.astro
Normal file
103
src/components/content/Callout.astro
Normal 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>
|
||||
63
src/components/content/Figure.astro
Normal file
63
src/components/content/Figure.astro
Normal 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>
|
||||
114
src/components/content/Sidenote.astro
Normal file
114
src/components/content/Sidenote.astro
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
321
src/content/articles/domain/index.mdoc
Normal file
321
src/content/articles/domain/index.mdoc
Normal 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; 500–900mm/year; dry summers
|
||||
- Csa, Csb
|
||||
- Hot dry summers; wet winters
|
||||
---
|
||||
- *Monsoon*
|
||||
- Warm year-round (>20°C); mild seasonal variation
|
||||
- Very high seasonal; 1500–3000+mm/year in wet seasonal
|
||||
- Am
|
||||
- Extreme wet/dry cycle; flooding defines calendar
|
||||
---
|
||||
- *Oceanic*
|
||||
- Mild winters (0–10°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; 300–600mm/year
|
||||
- Dfc, Dfd, Dwc, Dwd
|
||||
- Brutal winters; brief growing season
|
||||
---
|
||||
- *Subtropical*
|
||||
- Mild winters (5–10°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*
|
||||
- 50–300 m
|
||||
- Open steppe, prairie, farmland; wide horizons; wind-exposed; good for agriculture and cavalry
|
||||
---
|
||||
- *Hills*
|
||||
- 300–800 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 1000–5000
|
||||
---
|
||||
- *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 %}
|
||||
19
src/content/articles/kindred/index.mdoc
Normal file
19
src/content/articles/kindred/index.mdoc
Normal 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
|
||||
---
|
||||
13
src/content/articles/meta/index.mdoc
Normal file
13
src/content/articles/meta/index.mdoc
Normal 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
|
||||
---
|
||||
@@ -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
|
||||
|
||||
BIN
src/fonts/JuliaMono/JuliaMono-Black.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-Black.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-BlackItalic.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-BlackItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-Bold.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-Bold.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-BoldItalic.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-BoldLatin.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-BoldLatin.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-ExtraBold.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-ExtraBold.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-ExtraBoldItalic.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-ExtraBoldItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-Light.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-Light.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-LightItalic.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-LightItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-Medium.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-Medium.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-MediumItalic.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-MediumItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-Regular.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-Regular.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-RegularItalic.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-RegularItalic.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-RegularLatin.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-RegularLatin.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-SemiBold.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-SemiBold.woff2
Normal file
Binary file not shown.
BIN
src/fonts/JuliaMono/JuliaMono-SemiBoldItalic.woff2
Normal file
BIN
src/fonts/JuliaMono/JuliaMono-SemiBoldItalic.woff2
Normal file
Binary file not shown.
39
src/keystatic/components/callout.ts
Normal file
39
src/keystatic/components/callout.ts
Normal 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;
|
||||
35
src/keystatic/components/figure.ts
Normal file
35
src/keystatic/components/figure.ts
Normal 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;
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
50
src/keystatic/components/sidenote.ts
Normal file
50
src/keystatic/components/sidenote.ts
Normal 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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
21
src/lib/utils/extractors.ts
Normal file
21
src/lib/utils/extractors.ts
Normal 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;
|
||||
}
|
||||
@@ -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 == */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
src: url('/src/fonts/UnigrimDee-Regular.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@font-face {
|
||||
font-family: 'Iosevka Slab';
|
||||
font-weight: 300;
|
||||
@@ -147,4 +149,125 @@
|
||||
font-display: swap;
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
1
src/styles/mixins/patterns.css
Normal file
1
src/styles/mixins/patterns.css
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user