소스 검색

Add Developer landing page with the eleven-library catalogue

developer/index.html in the medieval-sky system: breadcrumb and Developer
nav item marked current, page hero, toolchain pillars (all Vala, proven
parts beneath, one build and one package), Spry's README stack diagram
redrawn as nested plates on the sky band with the non-web pieces set
around it, and six grouped sections (Foundations, Data, Web stack,
Security, Intelligence, Packaging) holding a project card per library,
each linking its detail page with the slug the later tasks will use.
Every summary is drawn from the project's MANIFEST.usm summary or README.

style.css gains the breadcrumb, page-hero, stack-map, firmament,
group-head and project-card components plus the nav aria-current state,
all in the existing tokens.

check.py now resolves site-root-relative links against the site root,
counts directory links as pages for the orphan check, and allowlists the
eleven not-yet-written detail pages (TODO marks them for removal as the
waves land).
saddle-ClankerSaddle 2 주 전
부모
커밋
94069a4409
3개의 변경된 파일과 714개의 추가작업 그리고 6개의 파일을 삭제
  1. 318 0
      assets/css/style.css
  2. 29 6
      check.py
  3. 367 0
      developer/index.html

+ 318 - 0
assets/css/style.css

@@ -843,6 +843,324 @@ a:hover {
   color: rgb(236 231 216 / 0.62);
 }
 
+/* ---------------------------------------------------------------------------
+   Developer pages — breadcrumb, page hero, stack map, project cards
+--------------------------------------------------------------------------- */
+
+/* Where you are: Home / Developer — established here for the detail pages too */
+
+.breadcrumb {
+  padding-block: 0.9rem 0;
+}
+
+.breadcrumb ol {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0.35rem 0.75rem;
+  padding: 0;
+  margin: 0;
+  list-style: none;
+  font-size: 0.88rem;
+}
+
+.breadcrumb li {
+  display: flex;
+  align-items: center;
+  gap: 0.75rem;
+}
+
+.breadcrumb li + li::before {
+  content: "/";
+  color: var(--gold-ink);
+}
+
+.breadcrumb a {
+  font-variant: small-caps;
+  letter-spacing: 0.05em;
+  color: var(--ink-soft);
+  text-decoration: none;
+}
+
+.breadcrumb a:hover {
+  color: var(--gold-ink);
+}
+
+.breadcrumb [aria-current] {
+  font-variant: small-caps;
+  letter-spacing: 0.05em;
+  color: var(--ink);
+}
+
+/* The masthead marks the section you stand in */
+
+.site-nav__list a[aria-current="page"] {
+  color: var(--gold-ink);
+  background-size: 100% 1px;
+}
+
+/* Page hero — the developer section's title page (no folio; the sky band below
+   carries the imagery) */
+
+.page-hero {
+  padding-block: clamp(2.5rem, 6vw, 4.5rem) clamp(2rem, 5vw, 3.25rem);
+}
+
+.page-hero .hero__copy {
+  max-width: 54rem;
+  margin-inline: auto;
+  text-align: center;
+}
+
+.page-hero .hero__actions {
+  justify-content: center;
+}
+
+.page-hero .lede {
+  margin-inline: auto;
+}
+
+/* Stack map — Spry's README diagram, redrawn as nested plates on the night sky */
+
+.stack-map {
+  max-width: 44rem;
+  margin-inline: auto;
+}
+
+.stack-layer {
+  padding: 0.85rem 1.1rem 1rem;
+  background: rgb(7 9 20 / 0.45);
+  border: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
+}
+
+.stack-layer--child {
+  margin-top: 0.75rem;
+}
+
+.stack-layer__name {
+  font-family: "Cinzel", serif;
+  font-size: 0.92rem;
+  font-weight: 600;
+  letter-spacing: 0.06em;
+  color: var(--gold-ink);
+}
+
+.stack-layer__name a {
+  color: inherit;
+  text-decoration: none;
+  border-bottom: 1px solid color-mix(in srgb, var(--gold) 55%, transparent);
+}
+
+.stack-layer__name a:hover {
+  color: var(--gold);
+  border-bottom-color: var(--gold);
+}
+
+.stack-layer__role {
+  margin-top: 0.15rem;
+  font-size: 0.92rem;
+  font-style: italic;
+  color: var(--ink-soft);
+}
+
+.stack-branches {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 0.75rem;
+  margin-top: 0.75rem;
+}
+
+.stack-map figcaption {
+  margin-top: 1.1rem;
+  font-size: 0.88rem;
+  font-style: italic;
+  text-align: center;
+  color: var(--ink-soft);
+  text-wrap: pretty;
+}
+
+/* The firmament — where the non-web pieces sit around the stack */
+
+.firmament-title {
+  margin-top: clamp(2.25rem, 6vw, 3.25rem);
+  font-size: 1.05rem;
+  letter-spacing: 0.14em;
+  text-transform: uppercase;
+  text-align: center;
+  color: var(--gold-ink);
+}
+
+.firmament {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
+  gap: 1rem 1.5rem;
+  max-width: 56rem;
+  margin: 1.4rem auto 0;
+}
+
+.firmament__item {
+  padding-left: 0.9rem;
+  border-left: 2px solid var(--gold-soft);
+}
+
+.firmament__name {
+  font-family: "Cinzel", serif;
+  font-size: 0.9rem;
+  font-weight: 600;
+  letter-spacing: 0.05em;
+}
+
+.firmament__item p + p {
+  margin-top: 0.15rem;
+  font-size: 0.92rem;
+  font-style: italic;
+  color: var(--ink-soft);
+  text-wrap: pretty;
+}
+
+/* Group heads — the catalogue's chapters */
+
+.group-head {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: baseline;
+  gap: 0.4rem 1rem;
+  margin: clamp(2.75rem, 7vw, 4rem) 0 1.4rem;
+  padding-bottom: 0.7rem;
+  border-bottom: 1px solid var(--line);
+}
+
+.section-head + .group-head {
+  margin-top: 0;
+}
+
+.group-head__num {
+  font-family: "Cinzel", serif;
+  font-weight: 700;
+  font-size: 1rem;
+  letter-spacing: 0.1em;
+  color: var(--gold-ink);
+}
+
+.group-head h3 {
+  font-size: 1.18rem;
+}
+
+.group-head__note {
+  margin-left: auto;
+  font-size: 0.92rem;
+  font-style: italic;
+  color: var(--ink-soft);
+}
+
+/* Project cards — one library per plate */
+
+.grid-projects {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
+  gap: 1.4rem;
+}
+
+.project-card {
+  display: flex;
+  flex-direction: column;
+  padding: 1.25rem 1.35rem 1.1rem;
+  background: var(--surface);
+  border: 1px solid var(--line);
+  box-shadow: inset 0 2px 0 var(--gold-soft);
+  transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
+}
+
+.project-card:hover {
+  transform: translateY(-3px);
+  border-color: var(--gold-soft);
+  box-shadow: inset 0 2px 0 var(--gold);
+}
+
+.project-card__top {
+  display: flex;
+  align-items: baseline;
+  justify-content: space-between;
+  gap: 0.75rem;
+}
+
+.project-card__top h3 {
+  font-size: 1.06rem;
+}
+
+.project-card__top a {
+  color: inherit;
+  text-decoration: none;
+}
+
+.project-card__top a:hover {
+  color: var(--gold-ink);
+  text-decoration: underline;
+  text-underline-offset: 3px;
+}
+
+.project-card__tag {
+  flex: none;
+  padding: 0.14rem 0.5rem;
+  font-family: "Cinzel", serif;
+  font-size: 0.62rem;
+  font-weight: 600;
+  letter-spacing: 0.14em;
+  text-transform: uppercase;
+  color: var(--gold-ink);
+  border: 1px solid var(--gold-soft);
+}
+
+.project-card__summary {
+  margin-top: 0.75rem;
+  flex-grow: 1;
+  font-size: 0.99rem;
+  color: var(--ink-soft);
+  text-wrap: pretty;
+}
+
+.project-card__meta {
+  display: flex;
+  align-items: baseline;
+  justify-content: space-between;
+  gap: 0.75rem;
+  margin-top: 1.15rem;
+  padding-top: 0.8rem;
+  border-top: 1px solid var(--line);
+}
+
+.project-card__meta > span {
+  font-size: 0.84rem;
+  font-style: italic;
+  color: var(--ink-soft);
+}
+
+.project-card__more {
+  font-family: "Cinzel", serif;
+  font-size: 0.72rem;
+  font-weight: 600;
+  letter-spacing: 0.12em;
+  text-transform: uppercase;
+  color: var(--gold-ink);
+  text-decoration: none;
+  white-space: nowrap;
+}
+
+.project-card__more:hover {
+  color: var(--gold-ink);
+  text-decoration: underline;
+  text-underline-offset: 3px;
+}
+
+@media (max-width: 38rem) {
+  .stack-branches {
+    grid-template-columns: 1fr;
+  }
+
+  .group-head__note {
+    margin-left: 0;
+  }
+}
+
 /* ---------------------------------------------------------------------------
    Responsive, motion, print
 --------------------------------------------------------------------------- */

+ 29 - 6
check.py

@@ -2,7 +2,8 @@
 """Static-site sanity checker for the Astrologue website.
 
 Walks every .html file under the given root, then verifies:
-  1. every src/href asset reference resolves to a real file;
+  1. every src/href asset reference resolves to a real file (site-root-
+     relative paths such as /developer/ resolve against the site root);
   2. no external URLs are referenced (the site must be self-contained);
   3. internal page anchors (#id and page#id) point at real ids;
   4. every local page is reachable by some link (orphan check);
@@ -25,7 +26,21 @@ EXCLUDE_DIRS = {"Original", "Proposed", ".git"}
 # Site-root-relative pages that are linked ahead of their existence because a
 # later task in this goal builds them. Remove entries here once the page lands
 # so the checker enforces them again.
-UPCOMING_PAGES = {"developer/"}
+# TODO(A3/A4/A5): the eleven project detail pages land in waves 3-4; strip
+# these entries as each wave merges.
+UPCOMING_PAGES = {
+    "developer/invercargill/",
+    "developer/invercargill-json/",
+    "developer/invercargill-sql/",
+    "developer/invercargill-sql-inversion/",
+    "developer/inversion/",
+    "developer/astralis/",
+    "developer/statum/",
+    "developer/spry/",
+    "developer/locksmith/",
+    "developer/vallama/",
+    "developer/usm/",
+}
 
 
 def check() -> int:
@@ -72,16 +87,24 @@ def check() -> int:
             continue
 
         target = (page.parent / unquote(parsed.path)).resolve()
+        if parsed.path.startswith("/"):
+            # site-root-relative: resolve against the site root, not /
+            target = (ROOT / unquote(parsed.path).lstrip("/")).resolve()
         if not target.exists():
-            rel = unquote(parsed.path).strip("/")
-            if parsed.path.startswith("/") and rel in {p.strip("/") for p in UPCOMING_PAGES}:
+            # allowlist upcoming pages no matter whether they are linked
+            # root-relative (/developer/spry/) or relatively (spry/)
+            try:
+                rel = target.relative_to(ROOT).as_posix().strip("/") + "/"
+            except ValueError:
+                rel = None
+            if rel is not None and rel in UPCOMING_PAGES:
                 continue
             errors.append(f"{page.name}: missing asset '{val}'")
             continue
 
-        if target.suffix == ".html" or target.name == "":
+        if target.suffix == ".html" or target.is_dir():
             # a page link; check its fragment if present
-            linked_pages.add(target)
+            linked_pages.add(target / "index.html" if target.is_dir() else target)
             if parsed.fragment:
                 tp = target if target.suffix == ".html" else target / "index.html"
                 if tp.is_file():

+ 367 - 0
developer/index.html

@@ -0,0 +1,367 @@
+<!doctype html>
+<html lang="en-nz">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Developer — Astrologue</title>
+  <meta name="description" content="Astrologue builds its own foundations in Vala. The eleven libraries that power the operating system — collections, database, web stack, cryptography, intelligence, packaging — are free software, available to every developer.">
+  <meta name="color-scheme" content="light dark">
+  <meta name="theme-color" content="#f2ebda" media="(prefers-color-scheme: light)">
+  <meta name="theme-color" content="#0a0a12" media="(prefers-color-scheme: dark)">
+  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Ccircle cx='32' cy='32' r='27' fill='none' stroke='%23a8801f' stroke-width='4'/%3E%3Cpath d='M32 8l6 18 18 6-18 6-6 18-6-18-18-6 18-6z' fill='%23a8801f'/%3E%3Ccircle cx='32' cy='32' r='4' fill='%23003078'/%3E%3C/svg%3E">
+  <meta property="og:title" content="Developer — Astrologue">
+  <meta property="og:description" content="The libraries that raise Astrologue, offered to every developer: all Vala, meson-built, USM-packaged, free software.">
+  <meta property="og:type" content="website">
+  <link rel="stylesheet" href="../assets/css/style.css">
+</head>
+<body>
+  <a class="skip-link" href="#main">Skip to content</a>
+
+  <header class="masthead">
+    <div class="container masthead__inner">
+      <a class="brand" href="../">
+        <img class="brand__logo brand__logo--light" src="../assets/img/astrologue-light.png" width="392" height="89" alt="Astrologue">
+        <img class="brand__logo brand__logo--dark" src="../assets/img/astrologue-dark.png" width="392" height="89" alt="Astrologue">
+      </a>
+      <nav class="site-nav" aria-label="Primary">
+        <ul class="site-nav__list">
+          <li><a href="../">Home</a></li>
+          <li><a href="../#news">News</a></li>
+          <li><a href="../#download">Download</a></li>
+          <li><a href="./" aria-current="page">Developer</a></li>
+          <li><a class="nav-cta" href="../#download">Get Astrologue</a></li>
+        </ul>
+      </nav>
+    </div>
+  </header>
+
+  <main id="main">
+
+    <nav class="breadcrumb" aria-label="Breadcrumb">
+      <div class="container">
+        <ol>
+          <li><a href="../">Astrologue</a></li>
+          <li aria-current="page">Developer</li>
+        </ol>
+      </div>
+    </nav>
+
+    <section class="page-hero" id="top">
+      <div class="container">
+        <div class="hero__copy">
+          <p class="kicker"><span>The developer's almanac</span></p>
+          <h1 class="hero__title">
+            Built in Vala, from the ground up.
+            <span class="hero__accent">The libraries that raise Astrologue, offered to every developer.</span>
+          </h1>
+          <p class="lede">Astrologue builds its own foundations, and it builds them in Vala. The collections and the queries, the database layer, the web stack, the cryptography, the packaging — the eleven libraries that power the operating system are the same ones you can take into your own projects: free to run, study, share and improve.</p>
+          <div class="hero__actions">
+            <a class="btn btn--primary" href="#stack">Survey the Web-Stack</a>
+            <a class="btn btn--ghost" href="#projects">Meet the eleven</a>
+          </div>
+          <p class="hero__note">Free software · LGPL-3.0-or-later for the libraries · built with meson, packaged with USM</p>
+        </div>
+      </div>
+    </section>
+
+    <section class="band band--plain" id="toolchain">
+      <div class="container">
+        <header class="section-head">
+          <p class="kicker"><span>The working method</span></p>
+          <h2>One language, one build, one package</h2>
+          <p class="section-head__intro">Every project in the catalogue keeps the same discipline, so learning one teaches you the next.</p>
+        </header>
+        <div class="grid-pillars">
+          <article class="pillar">
+            <div class="pillar__body">
+              <p class="pillar__num">I.</p>
+              <h3>All Vala</h3>
+              <p>Every library is written in Vala, from the Enumerable foundation to the HTTP server. Each compiles to C and the GObject ABI and ships its C header and Vala binding, and most add GIR introspection besides — open to other languages, not fenced in.</p>
+            </div>
+          </article>
+          <article class="pillar">
+            <div class="pillar__body">
+              <p class="pillar__num">II.</p>
+              <h3>Proven parts beneath</h3>
+              <p>Where the ground is hard, the stack stands on battle-tested C: GLib, SQLite, libsodium, GnuTLS, GPGME, libssh, llama.cpp. The Vala libraries contribute the object model and the defaults — not fresh cryptography or a new database engine.</p>
+            </div>
+          </article>
+          <article class="pillar">
+            <div class="pillar__body">
+              <p class="pillar__num">III.</p>
+              <h3>One build, one package</h3>
+              <p>Eleven projects, one shape: a <code>meson.build</code>, a <code>MANIFEST.usm</code> declaring what a project provides and needs down to individual files, and usm-scripts that build, test, install and package it the same way everywhere.</p>
+            </div>
+          </article>
+        </div>
+      </div>
+    </section>
+
+    <section class="band band--sky" id="stack">
+      <div class="container">
+        <header class="section-head">
+          <p class="kicker"><span>The Web-Stack</span></p>
+          <h2>How the pieces align</h2>
+          <p class="section-head__intro">The request path of an Astrologue web application, as drawn in Spry's own README: each layer leans on the ones beneath it.</p>
+        </header>
+
+        <figure class="stack-map">
+          <div class="stack-layer">
+            <p class="stack-layer__name"><a href="astralis/">astralis</a></p>
+            <p class="stack-layer__role">HTTP request handling</p>
+            <div class="stack-layer stack-layer--child">
+              <p class="stack-layer__name"><a href="statum/">Statum</a></p>
+              <p class="stack-layer__role">pages, slots/state, directives, client JS</p>
+              <div class="stack-layer stack-layer--child">
+                <p class="stack-layer__name"><a href="spry/">Spry</a></p>
+                <p class="stack-layer__role">users, auth slot, actions, guards</p>
+                <div class="stack-branches">
+                  <div class="stack-layer stack-layer--branch">
+                    <p class="stack-layer__name"><a href="invercargill-sql/">Invercargill-Sql</a></p>
+                    <p class="stack-layer__role">spry_users / spry_user_permissions schema</p>
+                  </div>
+                  <div class="stack-layer stack-layer--branch">
+                    <p class="stack-layer__name"><a href="inversion/">Inversion</a></p>
+                    <p class="stack-layer__role">module + service wiring</p>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+          <figcaption>A request arrives at astralis, becomes a page and a state slot under Statum, and meets users and permissions through Spry — whose schema lives in Invercargill-Sql and whose wiring is done by Inversion.</figcaption>
+        </figure>
+
+        <h3 class="firmament-title">Around the stack</h3>
+        <div class="firmament">
+          <div class="firmament__item">
+            <p class="firmament__name"><a href="invercargill/">Invercargill</a></p>
+            <p>the collections and queries underneath every layer above</p>
+          </div>
+          <div class="firmament__item">
+            <p class="firmament__name"><a href="locksmith/">Locksmith</a></p>
+            <p>objects-not-byte-arrays cryptography, wire-compatible with the stack's signed slots</p>
+          </div>
+          <div class="firmament__item">
+            <p class="firmament__name"><a href="vallama/">Vallama</a></p>
+            <p>local LLM inference for the applications you build on it</p>
+          </div>
+          <div class="firmament__item">
+            <p class="firmament__name"><a href="usm/">USM</a></p>
+            <p>the source packaging that carries the stack — and your own projects too</p>
+          </div>
+        </div>
+      </div>
+    </section>
+
+    <section class="band band--plain" id="projects">
+      <div class="container">
+        <header class="section-head">
+          <p class="kicker"><span>The catalogue</span></p>
+          <h2>Eleven libraries</h2>
+          <p class="section-head__intro">Grouped by trade. Each card opens onto its documentation: what the library does, how to build and install it, and worked examples from its own source.</p>
+        </header>
+
+        <div class="group-head">
+          <p class="group-head__num">I.</p>
+          <h3>Foundations</h3>
+          <p class="group-head__note">the ground the rest is built on</p>
+        </div>
+        <div class="grid-projects">
+          <article class="project-card">
+            <div class="project-card__top">
+              <h3><a href="invercargill/">Invercargill</a></h3>
+              <span class="project-card__tag">Foundation</span>
+            </div>
+            <p class="project-card__summary">The Enumerable library for Vala — LINQ-style querying plus the Vector, Dictionary, Series and buffer data structures the whole stack is written against.</p>
+            <p class="project-card__meta">
+              <span>LGPL-3.0-or-later</span>
+              <a class="project-card__more" href="invercargill/">Documentation&nbsp;→</a>
+            </p>
+          </article>
+          <article class="project-card">
+            <div class="project-card__top">
+              <h3><a href="invercargill-json/">Invercargill-Json</a></h3>
+              <span class="project-card__tag">Foundation</span>
+            </div>
+            <p class="project-card__summary">JSON support for Invercargill: documents become Enumerable collections that query and transform like any other sequence.</p>
+            <p class="project-card__meta">
+              <span>LGPL-3.0-or-later</span>
+              <a class="project-card__more" href="invercargill-json/">Documentation&nbsp;→</a>
+            </p>
+          </article>
+          <article class="project-card">
+            <div class="project-card__top">
+              <h3><a href="inversion/">Inversion</a></h3>
+              <span class="project-card__tag">Foundation</span>
+            </div>
+            <p class="project-card__summary">Inversion of control for the Web-Stack: a dependency-injection container with modules, scopes and three lifecycles, wired by field injection.</p>
+            <p class="project-card__meta">
+              <span>LGPL-3.0-or-later</span>
+              <a class="project-card__more" href="inversion/">Documentation&nbsp;→</a>
+            </p>
+          </article>
+        </div>
+
+        <div class="group-head">
+          <p class="group-head__num">II.</p>
+          <h3>Data</h3>
+          <p class="group-head__note">rows, schemas and migrations</p>
+        </div>
+        <div class="grid-projects">
+          <article class="project-card">
+            <div class="project-card__top">
+              <h3><a href="invercargill-sql/">Invercargill-Sql</a></h3>
+              <span class="project-card__tag">Data</span>
+            </div>
+            <p class="project-card__summary">A generic SQL abstraction built on Invercargill data structures — query results return as lazy Enumerable rows over a provider-neutral API, with SQLite beneath today.</p>
+            <p class="project-card__meta">
+              <span>LGPL-3.0-or-later</span>
+              <a class="project-card__more" href="invercargill-sql/">Documentation&nbsp;→</a>
+            </p>
+          </article>
+          <article class="project-card">
+            <div class="project-card__top">
+              <h3><a href="invercargill-sql-inversion/">Invercargill-Sql-Inversion</a></h3>
+              <span class="project-card__tag">Data</span>
+            </div>
+            <p class="project-card__summary">The Inversion integrations for Invercargill-Sql: DatabaseConfigurator, MigrationStartupService and InversionTypeProvider wire the database into the container.</p>
+            <p class="project-card__meta">
+              <span>LGPL-3.0-or-later</span>
+              <a class="project-card__more" href="invercargill-sql-inversion/">Documentation&nbsp;→</a>
+            </p>
+          </article>
+        </div>
+
+        <div class="group-head">
+          <p class="group-head__num">III.</p>
+          <h3>Web stack</h3>
+          <p class="group-head__note">from socket to signed state</p>
+        </div>
+        <div class="grid-projects">
+          <article class="project-card">
+            <div class="project-card__top">
+              <h3><a href="astralis/">Astralis</a></h3>
+              <span class="project-card__tag">Web stack</span>
+            </div>
+            <p class="project-card__summary">The library for writing HTTP backends in Vala: routing, headers, cookies, form data, JSON APIs, server-sent events, and layered web-config.json settings.</p>
+            <p class="project-card__meta">
+              <span>LGPL-3.0-or-later</span>
+              <a class="project-card__more" href="astralis/">Documentation&nbsp;→</a>
+            </p>
+          </article>
+          <article class="project-card">
+            <div class="project-card__top">
+              <h3><a href="statum/">Statum</a></h3>
+              <span class="project-card__tag">Web stack</span>
+            </div>
+            <p class="project-card__summary">Page rendering and state management for web applications: server-rendered pages, signed and encrypted state slots, directives and actions, bound by a small client script.</p>
+            <p class="project-card__meta">
+              <span>LGPL-3.0-or-later</span>
+              <a class="project-card__more" href="statum/">Documentation&nbsp;→</a>
+            </p>
+          </article>
+          <article class="project-card">
+            <div class="project-card__top">
+              <h3><a href="spry/">Spry</a></h3>
+              <span class="project-card__tag">Web stack</span>
+            </div>
+            <p class="project-card__summary">Reusable authentication and user-management services for Statum — the signed auth slot, guards and subclassable actions — plus the spry CLI that scaffolds, keys and deploys an application.</p>
+            <p class="project-card__meta">
+              <span>LGPL-3.0-or-later</span>
+              <a class="project-card__more" href="spry/">Documentation&nbsp;→</a>
+            </p>
+          </article>
+        </div>
+
+        <div class="group-head">
+          <p class="group-head__num">IV.</p>
+          <h3>Security</h3>
+          <p class="group-head__note">keys, seals and signatures</p>
+        </div>
+        <div class="grid-projects">
+          <article class="project-card">
+            <div class="project-card__top">
+              <h3><a href="locksmith/">Locksmith</a></h3>
+              <span class="project-card__tag">Security</span>
+            </div>
+            <p class="project-card__summary">Developer-friendly cryptography in objects, not byte arrays: sodium boxes, X.509, SSH, OpenPGP and WebPush over libsodium, GnuTLS, GPGME and libssh. It implements no cryptography itself.</p>
+            <p class="project-card__meta">
+              <span>LGPL-3.0-or-later</span>
+              <a class="project-card__more" href="locksmith/">Documentation&nbsp;→</a>
+            </p>
+          </article>
+        </div>
+
+        <div class="group-head">
+          <p class="group-head__num">V.</p>
+          <h3>Intelligence</h3>
+          <p class="group-head__note">language models, locally</p>
+        </div>
+        <div class="grid-projects">
+          <article class="project-card">
+            <div class="project-card__top">
+              <h3><a href="vallama/">Vallama</a></h3>
+              <span class="project-card__tag">Intelligence</span>
+            </div>
+            <p class="project-card__summary">High-level Vala bindings for llama.cpp inference: chat sessions, grammar-constrained tool calling, embeddings and tokenizers — running on your own hardware.</p>
+            <p class="project-card__meta">
+              <span>LGPL-3.0-or-later</span>
+              <a class="project-card__more" href="vallama/">Documentation&nbsp;→</a>
+            </p>
+          </article>
+        </div>
+
+        <div class="group-head">
+          <p class="group-head__num">VI.</p>
+          <h3>Packaging</h3>
+          <p class="group-head__note">how it all ships</p>
+        </div>
+        <div class="grid-projects">
+          <article class="project-card">
+            <div class="project-card__top">
+              <h3><a href="usm/">USM</a></h3>
+              <span class="project-card__tag">Packaging</span>
+            </div>
+            <p class="project-card__summary">The Universal Source Manifest: a project declares what it provides and needs in a MANIFEST.usm, and usm acquires, builds, tests, packages and deploys it.</p>
+            <p class="project-card__meta">
+              <span>GPL-3.0-or-later</span>
+              <a class="project-card__more" href="usm/">Documentation&nbsp;→</a>
+            </p>
+          </article>
+        </div>
+      </div>
+    </section>
+
+  </main>
+
+  <footer class="site-footer">
+    <div class="container">
+      <div class="footer-grid">
+        <div class="foot-brand">
+          <img class="brand__logo brand__logo--dark" src="../assets/img/astrologue-dark.png" width="392" height="89" alt="Astrologue">
+          <p>An operating system for change — kept for self sufficiency, resilience, and software freedom, so regular people can hold the power of general purpose computers in their own hands.</p>
+        </div>
+        <nav aria-label="The four freedoms">
+          <p class="foot-title">The Four Freedoms</p>
+          <ol class="freedoms">
+            <li><strong>Run</strong> the software as you wish, for any purpose.</li>
+            <li><strong>Study</strong> the source and change it to do your computing as you wish.</li>
+            <li><strong>Share</strong> copies so you can help others.</li>
+            <li><strong>Modify</strong> and publish your versions so everyone benefits.</li>
+          </ol>
+        </nav>
+        <nav aria-label="Site">
+          <p class="foot-title">Site</p>
+          <ul class="foot-list">
+            <li><a href="../">Home</a></li>
+            <li><a href="../#news">News</a></li>
+            <li><a href="../#download">Download</a></li>
+            <li><a href="./">Developer</a></li>
+          </ul>
+        </nav>
+      </div>
+      <p class="colophon">Set in Cinzel and EB Garamond (SIL Open Font License). Astrologue is free software, licensed GPL-3.0-or-later: take it, tend it, pass it on. The libraries are LGPL-3.0-or-later, so they remain free to link.</p>
+    </div>
+  </footer>
+</body>
+</html>