Initial Lektor site
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
/.lektor/
|
||||
/.cache/
|
||||
/build/
|
||||
/dist/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.DS_Store
|
||||
|
||||
/*.tar.gz
|
||||
@@ -0,0 +1,6 @@
|
||||
[project]
|
||||
name = Labyricorn
|
||||
url = https://www.labyricorn.com
|
||||
|
||||
[packages]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 |
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
_model: section
|
||||
---
|
||||
title: Articles
|
||||
---
|
||||
eyebrow: Field Notes
|
||||
---
|
||||
summary: Longer-form technical notes, decisions, and lessons worth keeping.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
_model: section
|
||||
---
|
||||
title: Blog
|
||||
---
|
||||
eyebrow: Dispatches
|
||||
---
|
||||
summary: Short updates from the Labyricorn workshop.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 & 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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user