commit 04faf86f1bebc32dd31c809ee298aec6f617d76f Author: Labyricorn Deployment Date: Tue Aug 11 19:36:53 2026 -0700 Initial Lektor site diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a874ba9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/.lektor/ +/.cache/ +/build/ +/dist/ +__pycache__/ +*.py[cod] +.DS_Store + +/*.tar.gz diff --git a/Labyricorn.lektorproject b/Labyricorn.lektorproject new file mode 100644 index 0000000..42ac525 --- /dev/null +++ b/Labyricorn.lektorproject @@ -0,0 +1,6 @@ +[project] +name = Labyricorn +url = https://www.labyricorn.com + +[packages] + diff --git a/README.md b/README.md new file mode 100644 index 0000000..1670641 --- /dev/null +++ b/README.md @@ -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. + diff --git a/assets/static/favicon.svg b/assets/static/favicon.svg new file mode 100644 index 0000000..99469b4 --- /dev/null +++ b/assets/static/favicon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/static/style.css b/assets/static/style.css new file mode 100644 index 0000000..a5c7f6d --- /dev/null +++ b/assets/static/style.css @@ -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; } +} + diff --git a/content/404/contents.lr b/content/404/contents.lr new file mode 100644 index 0000000..d6fb842 --- /dev/null +++ b/content/404/contents.lr @@ -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](/). + diff --git a/content/articles/boring-deployments/contents.lr b/content/articles/boring-deployments/contents.lr new file mode 100644 index 0000000..c93cb54 --- /dev/null +++ b/content/articles/boring-deployments/contents.lr @@ -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. + diff --git a/content/articles/contents.lr b/content/articles/contents.lr new file mode 100644 index 0000000..3cf7dc8 --- /dev/null +++ b/content/articles/contents.lr @@ -0,0 +1,8 @@ +_model: section +--- +title: Articles +--- +eyebrow: Field Notes +--- +summary: Longer-form technical notes, decisions, and lessons worth keeping. + diff --git a/content/blog/contents.lr b/content/blog/contents.lr new file mode 100644 index 0000000..aa2fe5e --- /dev/null +++ b/content/blog/contents.lr @@ -0,0 +1,8 @@ +_model: section +--- +title: Blog +--- +eyebrow: Dispatches +--- +summary: Short updates from the Labyricorn workshop. + diff --git a/content/blog/hello-world/contents.lr b/content/blog/hello-world/contents.lr new file mode 100644 index 0000000..c459b6a --- /dev/null +++ b/content/blog/hello-world/contents.lr @@ -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. + diff --git a/content/contents.lr b/content/contents.lr new file mode 100644 index 0000000..9df2315 --- /dev/null +++ b/content/contents.lr @@ -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. + diff --git a/content/projects/contents.lr b/content/projects/contents.lr new file mode 100644 index 0000000..89595a5 --- /dev/null +++ b/content/projects/contents.lr @@ -0,0 +1,8 @@ +_model: section +--- +title: Projects +--- +eyebrow: Workbench +--- +summary: Experiments, production systems, and useful things built in public. + diff --git a/content/projects/labyricorn-site/contents.lr b/content/projects/labyricorn-site/contents.lr new file mode 100644 index 0000000..3c13c31 --- /dev/null +++ b/content/projects/labyricorn-site/contents.lr @@ -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. + diff --git a/models/entry.ini b/models/entry.ini new file mode 100644 index 0000000..9b6e55e --- /dev/null +++ b/models/entry.ini @@ -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 + diff --git a/models/page.ini b/models/page.ini new file mode 100644 index 0000000..4013c52 --- /dev/null +++ b/models/page.ini @@ -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 + diff --git a/models/section.ini b/models/section.ini new file mode 100644 index 0000000..19026fb --- /dev/null +++ b/models/section.ini @@ -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 + diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..d6145bf --- /dev/null +++ b/templates/base.html @@ -0,0 +1,53 @@ + + + + + + + {% if this._path != '/' %}{{ this.title }} — {% endif %}Labyricorn + + + + + + + + + +
{% block body %}{% endblock %}
+ + + + + diff --git a/templates/entry.html b/templates/entry.html new file mode 100644 index 0000000..d68cd2a --- /dev/null +++ b/templates/entry.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{% block body %} +
+
+
+

{{ this.kicker }} — {{ this.date|dateformat('YYYY.MM.dd') }}

+

{{ this.title }}

+

{{ this.summary }}

+
+
+
+
+ {{ this.body }} + {% if this.external_url %}

Visit project →

{% endif %} +
+
+
+{% endblock %} + diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..0b45768 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,36 @@ +
+
+

{{ this.eyebrow }}

+

{{ this.summary }}

+
{{ this.body }}
+
+
+ +
+
+
+ Recent Activity + Sorted by date +
+
+ {% 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) %} + + {% endfor %} +
+
+
+ diff --git a/templates/page.html b/templates/page.html new file mode 100644 index 0000000..5794349 --- /dev/null +++ b/templates/page.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} +{% block body %} + {% if this._path == '/' %} + {% include "home.html" %} + {% else %} +
+
+

{{ this.eyebrow }}

+

{{ this.summary }}

+
+
+
+
{{ this.body }}
+
+ {% endif %} +{% endblock %} + diff --git a/templates/section.html b/templates/section.html new file mode 100644 index 0000000..4050023 --- /dev/null +++ b/templates/section.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% block body %} +
+
+

{{ this.eyebrow }}

+

{{ this.summary }}

+
+
+
+
+
{{ this.title }}{{ this.children.count() }} entries
+
+ {% for item in this.children %} + + {% endfor %} +
+
+
+{% endblock %} +