Initial Lektor site

This commit is contained in:
Labyricorn Deployment
2026-08-11 19:46:05 -07:00
commit 04faf86f1b
21 changed files with 444 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
/.lektor/
/.cache/
/build/
/dist/
__pycache__/
*.py[cod]
.DS_Store
/*.tar.gz
+6
View File
@@ -0,0 +1,6 @@
[project]
name = Labyricorn
url = https://www.labyricorn.com
[packages]
+21
View File
@@ -0,0 +1,21 @@
# Labyricorn
Source for the Labyricorn static website, built with Lektor.
## Local development
Install Lektor in an isolated environment, then run:
```bash
lektor server
```
For a production-style build:
```bash
lektor build --output-path build
```
Generated output is intentionally not committed. Production deployment builds
an immutable release and atomically updates the nginx document-root symlink.
+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#0e1116"/>
<path d="M17 14v36h30v-8H27V14z" fill="#edeae2"/>
<path d="M26 40h21" stroke="#51b6a5" stroke-width="4"/>
</svg>

After

Width:  |  Height:  |  Size: 233 B

+85
View File
@@ -0,0 +1,85 @@
:root {
--bg: #0e1116;
--panel: #11161c;
--ink: #edeae2;
--muted: #aaa79f;
--dim: #666b70;
--rule: #273039;
--teal: #51b6a5;
--magenta: #d26186;
--violet: #8c78d8;
--mono: "IBM Plex Mono", ui-monospace, monospace;
--serif: "Playfair Display", Georgia, serif;
--sans: Inter, system-ui, sans-serif;
}
* { box-sizing: border-box; }
html { color-scheme: dark; }
body { margin: 0; background: var(--bg); color: var(--ink); font: 16px/1.6 var(--sans); }
a { color: inherit; text-decoration: none; }
a:hover, a:focus-visible { color: var(--teal); }
.shell { width: min(100% - 40px, 1100px); margin-inline: auto; }
.narrow { max-width: 720px; margin-left: calc((100% - min(100% - 40px, 1100px)) / 2); }
.site-header { border-bottom: 1px solid var(--rule); }
.header-main { min-height: 105px; display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 18px; }
.brand { font: 700 34px/1 var(--serif); letter-spacing: -.02em; }
.tagline, .header-meta, .primary-nav, .site-footer, .section-heading, .entry-meta, .read-more, .eyebrow { font-family: var(--mono); text-transform: uppercase; letter-spacing: .12em; }
.tagline { color: var(--teal); font-size: 11px; }
.primary-nav { display: flex; gap: 30px; font-size: 12px; }
.header-meta { display: flex; justify-content: space-between; padding: 0 0 22px; color: var(--dim); font-size: 10px; }
.socials { display: flex; flex-wrap: wrap; gap: 20px; color: var(--muted); text-transform: none; letter-spacing: .04em; }
.hero { border-bottom: 1px solid var(--rule); padding: 60px 0 55px; }
.hero.compact { padding-block: 58px; }
.eyebrow { margin: 0 0 18px; color: var(--magenta); font-size: 11px; }
.eyebrow::before { content: "— "; }
h1, h2 { font-family: var(--serif); }
h1 { max-width: 650px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style: italic; line-height: 1.25; }
.entry-hero h1 { font-style: normal; }
.dek { max-width: 650px; margin-top: 18px; color: var(--muted); }
.dek p { margin: 0; }
.activity-section { min-height: 370px; padding: 48px 0 72px; }
.section-heading { display: flex; justify-content: space-between; padding-bottom: 22px; border-bottom: 1px solid var(--rule); color: var(--muted); font-size: 10px; }
.timeline { position: relative; margin: 16px 0 0 7px; padding-left: 30px; border-left: 1px solid var(--rule); }
.timeline-entry { position: relative; padding: 0 0 30px; }
.timeline-entry + .timeline-entry { padding-top: 26px; border-top: 1px solid rgba(39,48,57,.65); }
.timeline-entry::before { content: ""; position: absolute; left: -36px; top: 10px; width: 9px; height: 9px; border: 1px solid var(--teal); border-radius: 50%; background: var(--bg); }
.timeline-entry + .timeline-entry::before { top: 36px; }
.accent-blog::before { border-color: var(--violet); }
.accent-article::before { border-color: var(--magenta); }
.entry-meta { display: flex; align-items: center; gap: 16px; color: var(--muted); font-size: 10px; }
.label { border: 1px solid currentColor; padding: 2px 8px; color: var(--teal); }
.accent-blog .label { color: var(--violet); }
.accent-article .label { color: var(--magenta); }
.timeline-entry h2, .entry-card h2 { margin: 10px 0 5px; font-size: 21px; line-height: 1.3; }
.timeline-entry p, .entry-card p { max-width: 680px; margin: 0 0 10px; color: var(--muted); }
.read-more { color: var(--magenta); font-size: 10px; }
.card-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 1px; background: var(--rule); border: 1px solid var(--rule); }
.entry-card { min-height: 245px; padding: 28px; background: var(--bg); }
.content-section { padding: 55px 0 80px; }
.prose { max-width: 720px; }
.prose p { color: #c8c5bc; }
.prose a { color: var(--teal); text-decoration: underline; text-underline-offset: 3px; }
.button-link { display: inline-block; margin-top: 20px; font: 500 11px var(--mono); text-transform: uppercase; letter-spacing: .1em; }
.site-footer { border-top: 1px solid var(--rule); color: var(--muted); font-size: 9px; }
.footer-inner { min-height: 105px; display: flex; justify-content: space-between; align-items: center; gap: 24px; }
.site-footer nav { display: flex; gap: 22px; }
@media (max-width: 720px) {
.shell { width: min(100% - 28px, 1100px); }
.narrow { margin-inline: auto; }
.header-main { grid-template-columns: 1fr; gap: 9px; padding-block: 24px; }
.brand { font-size: 30px; }
.primary-nav { margin-top: 14px; justify-content: space-between; gap: 14px; }
.header-meta { display: none; }
.hero, .hero.compact { padding-block: 44px; }
h1 { font-size: 30px; }
.card-grid { grid-template-columns: 1fr; }
.footer-inner { align-items: flex-start; flex-direction: column; padding-block: 28px; }
.site-footer nav { flex-wrap: wrap; }
}
+11
View File
@@ -0,0 +1,11 @@
_model: page
---
title: Not Found
---
eyebrow: Error 404
---
summary: This page is missing from the record.
---
body:
[Return to the chronicle](/).
@@ -0,0 +1,18 @@
_model: entry
---
title: Boring Deployments Are a Feature
---
date: 2026-08-10
---
kicker: Article
---
summary: Why a small, transparent deployment chain is often the most resilient one.
---
body:
A static site does not need a sprawling production stack. A canonical Git
repository, an isolated build tool, immutable release directories, an atomic
symlink, and nginx are enough to make deployment understandable and reliable.
The important property is not novelty. It is that a failed build cannot replace
the working site.
+8
View File
@@ -0,0 +1,8 @@
_model: section
---
title: Articles
---
eyebrow: Field Notes
---
summary: Longer-form technical notes, decisions, and lessons worth keeping.
+8
View File
@@ -0,0 +1,8 @@
_model: section
---
title: Blog
---
eyebrow: Dispatches
---
summary: Short updates from the Labyricorn workshop.
+16
View File
@@ -0,0 +1,16 @@
_model: entry
---
title: Hello from Labyricorn
---
date: 2026-08-11
---
kicker: Blog
---
summary: The first entry from the new Git-backed Lektor site.
---
body:
The new Labyricorn site starts here: an intentionally small publishing system
with content that is easy to edit, review, build, deploy, and roll back.
More project notes, articles, and experiments will follow.
+12
View File
@@ -0,0 +1,12 @@
_model: page
---
title: Home
---
eyebrow: The Chronicle
---
summary: A living record of what's shipping, what's written, and what's quietly under construction.
---
body:
Every entry is maintained in Git and published through a repeatable static build.
Commit it, deploy it, and it appears.
+8
View File
@@ -0,0 +1,8 @@
_model: section
---
title: Projects
---
eyebrow: Workbench
---
summary: Experiments, production systems, and useful things built in public.
@@ -0,0 +1,18 @@
_model: entry
---
title: Labyricorn Website
---
date: 2026-08-11
---
kicker: Project
---
summary: A Git-backed Lektor website built into immutable releases and served by nginx.
---
body:
This website is the first production project in the new Labyricorn publishing
system. Source lives in Git; Lektor renders static files; nginx serves only the
active release.
The deployment process validates every build before atomically switching the
public document root, keeping the previous release available for rollback.
+30
View File
@@ -0,0 +1,30 @@
[model]
name = Entry
label = {{ this.title }}
[fields.title]
label = Title
type = string
[fields.date]
label = Date
type = date
[fields.kicker]
label = Type
type = select
choices = Blog, Article, Project
choice_labels = Blog, Article, Project
[fields.summary]
label = Summary
type = text
[fields.body]
label = Body
type = markdown
[fields.external_url]
label = External URL
type = url
+20
View File
@@ -0,0 +1,20 @@
[model]
name = Page
label = {{ this.title }}
[fields.title]
label = Title
type = string
[fields.eyebrow]
label = Eyebrow
type = string
[fields.summary]
label = Summary
type = text
[fields.body]
label = Body
type = markdown
+19
View File
@@ -0,0 +1,19 @@
[model]
name = Section
label = {{ this.title }}
[children]
order_by = -date
[fields.title]
label = Title
type = string
[fields.eyebrow]
label = Eyebrow
type = string
[fields.summary]
label = Summary
type = text
+53
View File
@@ -0,0 +1,53 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="{{ this.summary|default('Labyricorn developer devlog and project hub')|striptags }}">
<title>{% if this._path != '/' %}{{ this.title }} — {% endif %}Labyricorn</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Inter:wght@400;500&family=Playfair+Display:ital,wght@0,600;0,700;1,500;1,600&display=swap" rel="stylesheet">
<link rel="icon" href="{{ '/static/favicon.svg'|url }}" type="image/svg+xml">
<link rel="stylesheet" href="{{ '/static/style.css'|url }}">
</head>
<body>
<header class="site-header">
<div class="shell header-main">
<a class="brand" href="{{ '/'|url }}">Labyricorn</a>
<span class="tagline">// developer devlog &amp; project hub</span>
<nav class="primary-nav" aria-label="Primary navigation">
<a href="{{ '/projects'|url }}">Projects</a>
<a href="{{ '/articles'|url }}">Articles</a>
<a href="{{ '/blog'|url }}">Blog</a>
</nav>
</div>
<div class="shell header-meta">
<span>REG. NO. 004 — EST. LOCAL</span>
<div class="socials">
<a href="https://github.com/" rel="me">GitHub</a>
<a href="https://git.labyricorn.com/">Gitea</a>
<a href="https://linkedin.com/">LinkedIn</a>
<a href="https://twitch.tv/">Twitch</a>
<a href="https://youtube.com/">YouTube</a>
<a href="https://ko-fi.com/">Ko-fi</a>
</div>
</div>
</header>
<main>{% block body %}{% endblock %}</main>
<footer class="site-footer">
<div class="shell footer-inner">
<span>© 2026 Labyricorn — GENERATED FROM GIT, NEVER HAND-EDITED</span>
<nav aria-label="Footer navigation">
<a href="mailto:[email protected]">Contact</a>
<a href="https://github.com/">GitHub</a>
<a href="https://git.labyricorn.com/">Gitea</a>
<a href="https://ko-fi.com/">Ko-fi</a>
</nav>
</div>
</footer>
</body>
</html>
+19
View File
@@ -0,0 +1,19 @@
{% extends "base.html" %}
{% block body %}
<article>
<header class="hero compact entry-hero">
<div class="shell narrow">
<p class="eyebrow">{{ this.kicker }} — {{ this.date|dateformat('YYYY.MM.dd') }}</p>
<h1>{{ this.title }}</h1>
<p class="dek">{{ this.summary }}</p>
</div>
</header>
<section class="content-section">
<div class="shell prose">
{{ this.body }}
{% if this.external_url %}<p><a class="button-link" href="{{ this.external_url }}">Visit project →</a></p>{% endif %}
</div>
</section>
</article>
{% endblock %}
+36
View File
@@ -0,0 +1,36 @@
<section class="hero">
<div class="shell narrow">
<p class="eyebrow">{{ this.eyebrow }}</p>
<h1>{{ this.summary }}</h1>
<div class="dek">{{ this.body }}</div>
</div>
</section>
<section class="activity-section">
<div class="shell">
<div class="section-heading">
<span>Recent Activity</span>
<span>Sorted by date</span>
</div>
<div class="timeline">
{% set entries = [] %}
{% for section_id in ['projects', 'articles', 'blog'] %}
{% for item in site.get('/' ~ section_id).children %}
{% if entries.append(item) %}{% endif %}
{% endfor %}
{% endfor %}
{% for item in entries|sort(attribute='date', reverse=true) %}
<article class="timeline-entry accent-{{ item.kicker|lower }}">
<div class="entry-meta">
<span class="label">{{ item.kicker }}</span>
<time datetime="{{ item.date }}">{{ item.date|dateformat('YYYY.MM.dd') }}</time>
</div>
<h2><a href="{{ item|url }}">{{ item.title }}</a></h2>
<p>{{ item.summary }}</p>
<a class="read-more" href="{{ item|url }}">Read entry →</a>
</article>
{% endfor %}
</div>
</div>
</section>
+17
View File
@@ -0,0 +1,17 @@
{% extends "base.html" %}
{% block body %}
{% if this._path == '/' %}
{% include "home.html" %}
{% else %}
<section class="hero compact">
<div class="shell narrow">
<p class="eyebrow">{{ this.eyebrow }}</p>
<h1>{{ this.summary }}</h1>
</div>
</section>
<section class="content-section">
<div class="shell prose">{{ this.body }}</div>
</section>
{% endif %}
{% endblock %}
+25
View File
@@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block body %}
<section class="hero compact">
<div class="shell narrow">
<p class="eyebrow">{{ this.eyebrow }}</p>
<h1>{{ this.summary }}</h1>
</div>
</section>
<section class="activity-section">
<div class="shell">
<div class="section-heading"><span>{{ this.title }}</span><span>{{ this.children.count() }} entries</span></div>
<div class="card-grid">
{% for item in this.children %}
<article class="entry-card accent-{{ item.kicker|lower }}">
<div class="entry-meta"><span class="label">{{ item.kicker }}</span><time>{{ item.date|dateformat('YYYY.MM.dd') }}</time></div>
<h2><a href="{{ item|url }}">{{ item.title }}</a></h2>
<p>{{ item.summary }}</p>
<a class="read-more" href="{{ item|url }}">Read entry →</a>
</article>
{% endfor %}
</div>
</div>
</section>
{% endblock %}