diff --git a/.github/workflows/deploy-quartz.yml b/.github/workflows/deploy-quartz.yml new file mode 100644 index 0000000..3595c22 --- /dev/null +++ b/.github/workflows/deploy-quartz.yml @@ -0,0 +1,24 @@ +name: Deploy Quartz + +on: + push: + branches: + - master + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + deploy: + uses: GameCult/GameCult-Quartz/.github/workflows/quartz-pages.yml@main + with: + content-dir: Eusocial Interbeing + overlay-dir: site + output-dir: quartz-site/public diff --git a/.gitignore b/.gitignore index 4225627..36a2103 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ state/scratch-compaction-*.md /.rag/ /.tools/ /.npm-cache/ +/.quartz-build/ +/quartz-site/ diff --git a/Eusocial Interbeing/index.md b/Eusocial Interbeing/index.md index 5e027d0..2ed77d3 100644 --- a/Eusocial Interbeing/index.md +++ b/Eusocial Interbeing/index.md @@ -1,24 +1,33 @@ -# Eusocial Interbeing +# Zyphos -This setting centers on eusocial inter-species dynamics: civilizations that do not merely live in ecosystems, but negotiate with reproductive sites, memory-bearing forests, immune networks, migratory herds, pollinating castes, fungal archives, and engineered idea-organisms. +*A close-binary living world for the Aquarium demo, with names waiting for Weksa to earn them.* + +Zyphos is one member of a tightly bound terrestrial binary. Its slightly smaller twin, [[World/Zyphos Umbros Binary System|Umbros]], hangs in the sky as a fixed planetary presence. The primary star is dim, the habitable zone is close, and the biosphere has learned to treat light as a scarce civic resource rather than wallpaper. + +This site collects the setting work behind the Zyphos demo: the physics-constrained world model, the mutable-memory biosphere, the civilizations shaped by ecological contracts, the Aquarium rendering surface, and the future Weksa language work that will name the world from inside its inhabitants' ontologies. ## Core Premise -On one continent, the [[Airawa]] once lived in tight ecological reciprocity with [[Mother Trees]]. Their societies stored communal memory in tree networks that demanded nutrients, ritual labor, and political deference in return. One Airawa tribe created the [[Biological Memetic Engine]] to break that dependency. The tool worked too well. It became the foundation of the [[Airawa Empire]], a eugenics-obsessed biopunk state whose rulers can impose doctrine through the continent's connected living networks. +Zyphos life is founded on [[Ecology/Mutable Memory Endosymbiosis|mutable memory endosymbiosis]]. Every cell carries a memory-bearing symbiont. Sentience is not reserved for human-shaped minds; it gradients through cells, tissues, organisms, colonies, routes, forests, reefs, and landscapes. -On another continent, where Airawa never evolved or migrated, the [[Sa'auei'a]] developed a highly advanced nomadic civilization. They organize in squad-sized family units, keep [[Breeding Grounds]] sacred and communal, and bargain with ecologies that remember generosity. +On one continent, the [[Species/Airawa|Airawa]] once lived in tight ecological reciprocity with [[Species/Mother Trees|Mother Trees]]. Their societies stored communal memory in tree networks that demanded nutrients, ritual labor, and political deference in return. One Airawa tribe created the [[Technologies/Biological Memetic Engine|Biological Memetic Engine]] to break that dependency. The tool worked too well. It became the foundation of the [[Civilizations/Airawa Empire|Airawa Empire]], a eugenics-obsessed biopunk state whose rulers can impose doctrine through the continent's connected living networks. -## Main Paths +On another continent, where Airawa never evolved or migrated, the [[Species/Sa'auei'a|Sa'auei'a]] developed a highly advanced nomadic civilization. They organize in squad-sized family units, keep [[Ecology/Breeding Grounds|Breeding Grounds]] sacred and communal, and bargain with ecologies that remember generosity. +## Project Surfaces + +- [[World/Zyphos Simulation Brief]] +- [[World/Zyphos Umbros Binary System]] +- [[Ecology/Mutable Memory Endosymbiosis]] +- [[Ecology/Zyphos Biosphere Examples]] - [[World/index]] +- [[Ecology/index]] - [[Species/index]] - [[Civilizations/index]] -- [[Ecology/index]] - [[Technologies/index]] -- [[Conflicts/index]] -- [[Themes/index]] -- [[Inspirations/index]] ## Current Design Rule Every major institution should be legible as an ecological deal, a reproductive pressure, a memory technology, or a path-dependent consequence. If it is just a normal state wearing wet architecture, cut it until the wound has a purpose. + +Physics is design authority. If the orbital math or energy budget tightens the image, the world bends. The bruise is usually where the better idea lives. diff --git a/Eusocial Interbeing/static/icon.png b/Eusocial Interbeing/static/icon.png new file mode 100644 index 0000000..fcf45ec Binary files /dev/null and b/Eusocial Interbeing/static/icon.png differ diff --git a/README.md b/README.md index 9e07db8..19dfb7f 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,10 @@ An Obsidian vault for an original biopolitical ecology setting: two continents, The project is loosely inspired by Avatar's underused ecological network premise, Sue Burke's plant-negotiation fiction, and Harry Turtledove's path-dependent technology arguments. It is not an Avatar fan setting. The useful corpse has been composted. We move on. Start at [[Eusocial Interbeing/index]]. + +## Site + +The Zyphos public site is a GitHub Pages Quartz site using the same deployment +shape as Aetheria. Vault content lives under `Eusocial Interbeing/`; the Quartz +overlay lives under `site/`; the workflow publishes to GitHub Pages for +`zyphos.gamecult.org`. diff --git a/site/README.md b/site/README.md new file mode 100644 index 0000000..8a38de7 --- /dev/null +++ b/site/README.md @@ -0,0 +1,15 @@ +# Zyphos Quartz Site + +Quartz overlay for the future `https://zyphos.gamecult.org` GitHub Pages site. +The content source is the vault directory `Eusocial Interbeing/`; this `site/` +directory only owns presentation, navigation, and static site assets. + +The site follows the same deployment pattern as `AetheriaLore`: + +- content directory: `Eusocial Interbeing` +- overlay directory: `site` +- output directory: `quartz-site/public` +- GitHub Pages host: `gamecult.github.io` +- custom domain: `zyphos.gamecult.org` + +DNS should point `zyphos.gamecult.org` at GitHub Pages, not Yggdrasil. diff --git a/site/quartz.config.ts b/site/quartz.config.ts new file mode 100644 index 0000000..2437ac4 --- /dev/null +++ b/site/quartz.config.ts @@ -0,0 +1,141 @@ +import { QuartzConfig } from "./quartz/cfg" +import * as Plugin from "./quartz/plugins" +import { QuartzEmitterPlugin } from "./quartz/plugins/types" +import { Fragment, h } from "preact" +import { zyphosHeroScript } from "./quartz/components/ZyphosHeroScript" + +const ZyphosFavicons: QuartzEmitterPlugin = () => ({ + name: "ZyphosFavicons", + async *emit() {}, + async *partialEmit() {}, + externalResources: () => ({ + additionalHead: [ + () => + h(Fragment, {}, [ + h("meta", { property: "twitter:domain", content: "zyphos.gamecult.org" }), + h("meta", { property: "og:url", content: "https://zyphos.gamecult.org/" }), + h("meta", { property: "twitter:url", content: "https://zyphos.gamecult.org/" }), + ]), + ], + }), +}) + +const ZyphosHeroResources: QuartzEmitterPlugin = () => ({ + name: "ZyphosHeroResources", + async *emit() {}, + async *partialEmit() {}, + externalResources: () => ({ + js: [ + { + script: zyphosHeroScript, + loadTime: "afterDOMReady", + contentType: "inline", + spaPreserve: true, + }, + ], + }), +}) + +const config: QuartzConfig = { + configuration: { + pageTitle: "Zyphos", + pageTitleSuffix: "", + enableSPA: true, + enablePopovers: true, + analytics: null, + locale: "en-GB", + baseUrl: "zyphos.gamecult.org", + ignorePatterns: [ + "private", + "templates", + ".obsidian", + "Inspirations", + "Inspirations/**", + ], + defaultDateType: "modified", + theme: { + fontOrigin: "googleFonts", + cdnCaching: true, + typography: { + header: { + name: "Montserrat", + weights: [100, 200, 300, 400, 600], + }, + title: { + name: "Montserrat", + weights: [100, 200, 300], + }, + body: { + name: "Ubuntu", + weights: [300, 400, 500, 700], + includeItalic: true, + }, + code: "IBM Plex Mono", + }, + colors: { + lightMode: { + light: "#07110f", + lightgray: "#16251f", + gray: "#668077", + darkgray: "#bdd4ca", + dark: "#effcf8", + secondary: "#ffae58", + tertiary: "#8edfb0", + highlight: "rgba(142, 223, 176, 0.14)", + textHighlight: "#ffae5855", + }, + darkMode: { + light: "#07110f", + lightgray: "#16251f", + gray: "#668077", + darkgray: "#bdd4ca", + dark: "#effcf8", + secondary: "#ffae58", + tertiary: "#8edfb0", + highlight: "rgba(142, 223, 176, 0.14)", + textHighlight: "#ffae5855", + }, + }, + }, + }, + plugins: { + transformers: [ + Plugin.FrontMatter(), + Plugin.CreatedModifiedDate({ + priority: ["frontmatter", "git", "filesystem"], + }), + Plugin.SyntaxHighlighting({ + theme: { + light: "github-dark", + dark: "github-dark", + }, + keepBackground: false, + }), + Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }), + Plugin.GitHubFlavoredMarkdown(), + Plugin.TableOfContents(), + Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }), + Plugin.Description(), + Plugin.Latex({ renderEngine: "katex" }), + ], + filters: [Plugin.RemoveDrafts()], + emitters: [ + Plugin.AliasRedirects(), + Plugin.ComponentResources(), + Plugin.ContentPage(), + Plugin.FolderPage(), + Plugin.ContentIndex({ + enableSiteMap: false, + enableRSS: false, + }), + Plugin.Assets(), + Plugin.Static(), + ZyphosFavicons(), + ZyphosHeroResources(), + Plugin.NotFoundPage(), + Plugin.CustomOgImages(), + ], + }, +} + +export default config diff --git a/site/quartz.layout.ts b/site/quartz.layout.ts new file mode 100644 index 0000000..d78d433 --- /dev/null +++ b/site/quartz.layout.ts @@ -0,0 +1,53 @@ +import { PageLayout, SharedLayout } from "./quartz/cfg" +import * as Component from "./quartz/components" +import ZyphosMasthead from "./quartz/components/ZyphosMasthead" +import ZyphosOverviewSidebar from "./quartz/components/ZyphosOverviewSidebar" +import ZyphosThemeLock from "./quartz/components/ZyphosThemeLock" + +export const sharedPageComponents: SharedLayout = { + head: Component.Head(), + header: [ZyphosThemeLock(), ZyphosMasthead(), Component.Search()], + afterBody: [], + footer: Component.Footer({ + links: {}, + }), +} + +export const defaultContentPageLayout: PageLayout = { + beforeBody: [ + Component.ConditionalRender({ + component: Component.Breadcrumbs({ + rootName: "Zyphos", + showCurrentPage: false, + showRoot: false, + }), + condition: (page) => page.fileData.slug !== "index", + }), + Component.ConditionalRender({ + component: Component.ArticleTitle(), + condition: (page) => !page.fileData.slug?.endsWith("/index") && page.fileData.slug !== "index", + }), + Component.ConditionalRender({ + component: Component.ContentMeta(), + condition: (page) => !page.fileData.slug?.endsWith("/index") && page.fileData.slug !== "index", + }), + ], + afterBody: [], + left: [ZyphosOverviewSidebar()], + right: [ + Component.DesktopOnly(Component.TableOfContents()), + Component.Backlinks(), + ], +} + +export const defaultListPageLayout: PageLayout = { + beforeBody: [ + Component.Breadcrumbs({ + rootName: "Zyphos", + showCurrentPage: false, + showRoot: false, + }), + ], + left: [], + right: [], +} diff --git a/site/quartz/components/ZyphosHeroScript.tsx b/site/quartz/components/ZyphosHeroScript.tsx new file mode 100644 index 0000000..d2feb5e --- /dev/null +++ b/site/quartz/components/ZyphosHeroScript.tsx @@ -0,0 +1,118 @@ +import { QuartzComponent, QuartzComponentConstructor } from "./types" + +export const zyphosHeroScript = String.raw` +function bootZyphosHero() { + const home = document.querySelector("body[data-slug='index'] article") + if (!home || home.querySelector(".zyphos-hero-canvas")) return + + const canvas = document.createElement("canvas") + canvas.className = "zyphos-hero-canvas" + canvas.setAttribute("aria-hidden", "true") + home.prepend(canvas) + + const context = canvas.getContext("2d") + + function resize() { + const rect = home.getBoundingClientRect() + const ratio = Math.min(window.devicePixelRatio || 1, 2) + canvas.width = Math.max(1, Math.floor(rect.width * ratio)) + canvas.height = Math.floor(Math.min(window.innerHeight * 0.72, 620) * ratio) + canvas.style.height = Math.floor(canvas.height / ratio) + "px" + context.setTransform(ratio, 0, 0, ratio, 0, 0) + } + + function drawPlanet(x, y, radius, colors, time, shadow) { + const gradient = context.createRadialGradient(x - radius * 0.32, y - radius * 0.35, 0, x, y, radius) + gradient.addColorStop(0, colors[0]) + gradient.addColorStop(0.5, colors[1]) + gradient.addColorStop(1, colors[2]) + context.fillStyle = gradient + context.beginPath() + context.arc(x, y, radius, 0, Math.PI * 2) + context.fill() + + const eclipse = context.createRadialGradient(x + Math.cos(time) * radius * 0.3, y, 0, x, y, radius * 1.2) + eclipse.addColorStop(0, shadow) + eclipse.addColorStop(1, "rgba(0, 0, 0, 0)") + context.fillStyle = eclipse + context.beginPath() + context.arc(x, y, radius, 0, Math.PI * 2) + context.fill() + + context.strokeStyle = "rgba(142, 223, 176, 0.18)" + for (let i = 0; i < 5; i += 1) { + context.beginPath() + context.ellipse(x, y + Math.sin(i * 1.6) * radius * 0.42, radius * (0.44 + i * 0.05), radius * 0.08, i * 0.28, 0, Math.PI * 2) + context.stroke() + } + } + + function frame(now) { + const width = canvas.clientWidth + const height = canvas.clientHeight + const t = now * 0.00025 + context.clearRect(0, 0, width, height) + + const star = context.createRadialGradient(width * 0.78, height * 0.18, 0, width * 0.78, height * 0.18, width * 0.55) + star.addColorStop(0, "rgba(255, 174, 88, 0.22)") + star.addColorStop(1, "rgba(255, 174, 88, 0)") + context.fillStyle = star + context.fillRect(0, 0, width, height) + + context.strokeStyle = "rgba(115, 198, 227, 0.11)" + for (let i = 0; i < 7; i += 1) { + const y = height * (0.2 + i * 0.09) + Math.sin(t * 2 + i) * 6 + context.beginPath() + context.moveTo(0, y) + context.bezierCurveTo(width * 0.35, y - 30, width * 0.62, y + 36, width, y - 12) + context.stroke() + } + + const zr = Math.min(width, height) * 0.21 + const zx = width * 0.68 + const zy = height * 0.62 + const ur = zr * 0.62 + const ux = zx - zr * 1.85 + const uy = zy - zr * 0.7 + + context.strokeStyle = "rgba(239, 252, 248, 0.15)" + context.beginPath() + context.moveTo(ux, uy) + context.lineTo(zx, zy) + context.stroke() + + drawPlanet(ux, uy, ur, ["#7d8899", "#2c3a4a", "#070b11"], t + 1.2, "rgba(0, 0, 0, 0.6)") + drawPlanet(zx, zy, zr, ["#c7f7d0", "#28726c", "#06131b"], t, "rgba(0, 0, 0, 0.42)") + + context.fillStyle = "rgba(142, 223, 176, 0.62)" + for (let i = 0; i < 56; i += 1) { + const angle = i * 2.399 + t * 0.35 + const r = zr * (0.18 + ((i * 37) % 100) / 140) + const x = zx + Math.cos(angle) * r * 0.82 + const y = zy + Math.sin(angle) * r * 0.38 + const pulse = 1 + Math.sin(now * 0.002 + i) * 0.8 + context.beginPath() + context.arc(x, y, Math.max(0.7, pulse), 0, Math.PI * 2) + context.fill() + } + + requestAnimationFrame(frame) + } + + resize() + window.addEventListener("resize", resize) + requestAnimationFrame(frame) +} + +document.addEventListener("nav", bootZyphosHero) +document.addEventListener("DOMContentLoaded", bootZyphosHero) +bootZyphosHero() +` + +export default (() => { + const ZyphosHeroScript: QuartzComponent = () => ( +