diff --git a/.theme/assets/styles/theme.css b/.theme/assets/styles/theme.css new file mode 100644 index 0000000..3e23ae7 --- /dev/null +++ b/.theme/assets/styles/theme.css @@ -0,0 +1,502 @@ +:root { + color-scheme: dark; + --bg: #111314; + --surface: #191c1d; + --surface-raised: #202425; + --text: #f1eee6; + --muted: #aaa99f; + --line: #34393a; + --accent: #d9a75c; + --accent-strong: #f2c57d; + --focus: #8fd3ff; + --max-width: 72rem; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-synthesis: none; +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + margin: 0; + background: + radial-gradient(circle at 10% 0%, rgb(217 167 92 / 10%), transparent 28rem), + var(--bg); + color: var(--text); + font-size: 1rem; + line-height: 1.65; +} + +a { + color: var(--accent-strong); + text-underline-offset: 0.2em; + text-decoration-thickness: 0.08em; +} + +a:hover { + color: #ffe1ad; +} + +:focus-visible { + outline: 0.2rem solid var(--focus); + outline-offset: 0.2rem; +} + +.shell { + width: min(calc(100% - 2rem), var(--max-width)); + margin-inline: auto; +} + +.skip-link { + position: fixed; + z-index: 10; + top: 0.75rem; + left: 0.75rem; + padding: 0.65rem 0.9rem; + transform: translateY(-160%); + border-radius: 0.35rem; + background: var(--text); + color: var(--bg); +} + +.skip-link:focus { + transform: translateY(0); +} + +.site-header { + border-bottom: 1px solid var(--line); + background: rgb(17 19 20 / 88%); +} + +.header-inner { + min-height: 4.75rem; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1.5rem; +} + +.brand { + display: inline-flex; + align-items: center; + gap: 0.7rem; + color: var(--text); + font-weight: 750; + letter-spacing: -0.02em; + text-decoration: none; +} + +.brand-mark { + width: 2rem; + height: 2rem; + display: grid; + place-items: center; + border: 1px solid var(--accent); + border-radius: 50%; + color: var(--accent-strong); + font-family: Georgia, "Times New Roman", serif; + font-style: italic; +} + +.site-nav { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; +} + +.site-nav a { + padding: 0.45rem 0.7rem; + border-radius: 0.35rem; + color: var(--muted); + font-size: 0.9rem; + font-weight: 650; + text-decoration: none; +} + +.site-nav a:hover, +.site-nav a[aria-current="page"] { + background: var(--surface-raised); + color: var(--text); +} + +main.shell { + min-height: 68vh; + padding-block: clamp(3.5rem, 8vw, 7rem); +} + +h1, +h2, +h3 { + margin-top: 0; + font-family: Georgia, "Times New Roman", serif; + font-weight: 500; + line-height: 1.08; + letter-spacing: -0.035em; +} + +h1 { + max-width: 13ch; + margin-bottom: 1.25rem; + font-size: clamp(3rem, 9vw, 6.5rem); +} + +h2 { + font-size: clamp(2rem, 5vw, 3.25rem); +} + +h3 { + font-size: 1.5rem; +} + +.eyebrow, +.card-kind, +.content-meta, +.footer-meta { + color: var(--accent); + font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace; + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.hero { + max-width: 62rem; +} + +.hero h1 { + max-width: 11ch; +} + +.hero-summary, +.lede { + max-width: 48rem; + color: #d2d0c8; + font-size: clamp(1.2rem, 2.5vw, 1.55rem); + line-height: 1.5; +} + +.hero-copy { + margin-top: 3rem; +} + +.section-block, +.listing { + margin-top: clamp(4rem, 9vw, 7rem); + padding-top: 2rem; + border-top: 1px solid var(--line); +} + +.section-heading { + margin-bottom: 2rem; +} + +.section-heading h2 { + margin-bottom: 0; +} + +.card-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; +} + +.card { + min-height: 18rem; + display: flex; + flex-direction: column; + padding: clamp(1.35rem, 3vw, 2rem); + border: 1px solid var(--line); + border-radius: 0.75rem; + background: linear-gradient(145deg, var(--surface-raised), var(--surface)); +} + +.card h2, +.card h3 { + margin-bottom: 0.8rem; +} + +.card h2 a, +.card h3 a, +.listing-item h2 a { + color: var(--text); + text-decoration: none; +} + +.card p:not(.card-kind) { + color: var(--muted); +} + +.card .text-link { + margin-top: auto; + padding-top: 1.5rem; +} + +.text-link { + width: fit-content; + font-weight: 750; + text-decoration: none; +} + +.page-intro, +.content-header { + margin-bottom: clamp(3rem, 8vw, 5.5rem); +} + +.page-intro h1, +.content-header h1 { + font-size: clamp(3rem, 8vw, 5.5rem); +} + +.listing { + display: grid; +} + +.listing-item { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: end; + gap: 2rem; + padding-block: 2rem; + border-bottom: 1px solid var(--line); +} + +.listing-item h2 { + margin-bottom: 0.6rem; + font-size: clamp(1.75rem, 4vw, 2.5rem); +} + +.listing-item p { + max-width: 46rem; + margin-bottom: 0; + color: var(--muted); +} + +.content-page { + max-width: 56rem; +} + +.prose { + max-width: 46rem; + color: #d5d3cb; + font-size: 1.05rem; +} + +.prose > * + * { + margin-top: 1.35em; +} + +.prose h2, +.prose h3 { + margin-top: 2em; + color: var(--text); +} + +.prose h2 { + font-size: 2rem; +} + +.prose h3 { + font-size: 1.4rem; +} + +.prose code { + padding: 0.12em 0.32em; + border: 1px solid var(--line); + border-radius: 0.25rem; + background: var(--surface); + font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace; + font-size: 0.9em; +} + +.prose pre { + overflow-x: auto; + padding: 1.25rem; + border: 1px solid var(--line); + border-radius: 0.5rem; + background: #0b0d0e; +} + +.prose pre code { + padding: 0; + border: 0; + background: none; +} + +.prose blockquote { + margin-inline: 0; + padding-left: 1.25rem; + border-left: 0.2rem solid var(--accent); + color: var(--muted); +} + +.project-facts { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-top: 2rem; +} + +.project-facts div { + min-width: 9rem; + padding: 0.8rem 1rem; + border: 1px solid var(--line); + border-radius: 0.5rem; + background: var(--surface); +} + +.project-facts dt { + color: var(--muted); + font-size: 0.75rem; + font-weight: 700; + text-transform: uppercase; +} + +.project-facts dd { + margin: 0.2rem 0 0; +} + +.tag-list { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin: 1.5rem 0 0; + padding: 0; + list-style: none; +} + +.tag-list li { + padding: 0.32rem 0.6rem; + border: 1px solid var(--line); + border-radius: 999px; + color: var(--muted); + font-size: 0.82rem; +} + +.content-tags { + margin-top: 3rem; + padding-top: 1.5rem; + border-top: 1px solid var(--line); +} + +.button-link { + display: inline-block; + margin-top: 1.5rem; + padding: 0.75rem 1rem; + border: 1px solid var(--accent); + border-radius: 0.4rem; + text-decoration: none; +} + +.error-page { + max-width: 48rem; +} + +.error-page p:not(.eyebrow) { + color: var(--muted); + font-size: 1.2rem; +} + +.empty-state { + color: var(--muted); +} + +.site-footer { + padding-block: 2.5rem; + border-top: 1px solid var(--line); + color: var(--muted); +} + +.footer-inner { + display: flex; + justify-content: space-between; + gap: 2rem; +} + +.footer-inner p { + margin: 0; +} + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +@media (max-width: 44rem) { + .header-inner, + .footer-inner { + align-items: flex-start; + flex-direction: column; + } + + .header-inner { + padding-block: 1rem; + } + + .card-grid { + grid-template-columns: 1fr; + } + + .listing-item { + grid-template-columns: 1fr; + align-items: start; + gap: 1rem; + } +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } +} + +@media (prefers-color-scheme: light) { + :root { + color-scheme: light; + --bg: #f4f0e7; + --surface: #ebe5da; + --surface-raised: #fffaf0; + --text: #202323; + --muted: #5f625f; + --line: #cbc4b7; + --accent: #87591b; + --accent-strong: #704713; + --focus: #075b9b; + } + + body { + background: + radial-gradient(circle at 10% 0%, rgb(135 89 27 / 11%), transparent 28rem), + var(--bg); + } + + a:hover { + color: #4e2d04; + } + + .site-header { + background: rgb(244 240 231 / 90%); + } + + .hero-summary, + .lede, + .prose { + color: #3d413f; + } + + .prose pre { + background: #242728; + color: #f7f3eb; + } +} diff --git a/.theme/templates/404.liquid b/.theme/templates/404.liquid new file mode 100644 index 0000000..030c63f --- /dev/null +++ b/.theme/templates/404.liquid @@ -0,0 +1,6 @@ +
+

Error 404

+

That route does not exist.

+

The page may have moved, or the address may be incomplete.

+ Return home +
diff --git a/.theme/templates/content/article.liquid b/.theme/templates/content/article.liquid new file mode 100644 index 0000000..23debe5 --- /dev/null +++ b/.theme/templates/content/article.liquid @@ -0,0 +1,25 @@ +
+
+

Article

+

{{ page.title }}

+ {% if page.summary %} +

{{ page.summary }}

+ {% endif %} + {% if page.published %} + + {% endif %} +
+
+ {{ page.renderedContent | safe_content }} +
+ {% if page.tags.size > 0 %} + + {% endif %} +
diff --git a/.theme/templates/content/page.liquid b/.theme/templates/content/page.liquid new file mode 100644 index 0000000..222750b --- /dev/null +++ b/.theme/templates/content/page.liquid @@ -0,0 +1,12 @@ +
+
+

Page

+

{{ page.title }}

+ {% if page.summary %} +

{{ page.summary }}

+ {% endif %} +
+
+ {{ page.renderedContent | safe_content }} +
+
diff --git a/.theme/templates/content/project.liquid b/.theme/templates/content/project.liquid new file mode 100644 index 0000000..815b488 --- /dev/null +++ b/.theme/templates/content/project.liquid @@ -0,0 +1,33 @@ +
+
+

Project

+

{{ page.title }}

+ {% if page.summary %} +

{{ page.summary }}

+ {% endif %} +
+ {% if page.metadata.version %} +
Version
{{ page.metadata.version }}
+ {% endif %} + {% if page.metadata.license %} +
License
{{ page.metadata.license }}
+ {% endif %} + {% if page.metadata.stack.size > 0 %} +
Stack
{{ page.metadata.stack | join: ", " }}
+ {% endif %} +
+
+
+ {{ page.renderedContent | safe_content }} +
+ {% if page.tags.size > 0 %} + + {% endif %} +
diff --git a/.theme/templates/home.liquid b/.theme/templates/home.liquid new file mode 100644 index 0000000..28b61a2 --- /dev/null +++ b/.theme/templates/home.liquid @@ -0,0 +1,29 @@ +
+

Configuration-first publishing

+

{{ page.title }}

+ {% if page.summary %} +

{{ page.summary }}

+ {% endif %} +
+ {{ page.renderedContent | safe_content }} +
+
+ +{% if collection.items.size > 0 %} +
+
+

From the repository

+

Latest work

+
+
+ {% for item in collection.items %} + + {% endfor %} +
+
+{% endif %} diff --git a/.theme/templates/layout.liquid b/.theme/templates/layout.liquid new file mode 100644 index 0000000..6907809 --- /dev/null +++ b/.theme/templates/layout.liquid @@ -0,0 +1,44 @@ + + + + + + + + {% if page.type == "home" %} + {{ site.title }} + {% else %} + {{ page.title }} ยท {{ site.title }} + {% endif %} + {% for stylesheet in theme.styles %} + + {% endfor %} + + + + + +
+ {{ page.body | safe_page_body }} +
+ + + + diff --git a/.theme/templates/sections/articles.liquid b/.theme/templates/sections/articles.liquid new file mode 100644 index 0000000..08588d8 --- /dev/null +++ b/.theme/templates/sections/articles.liquid @@ -0,0 +1,22 @@ +
+

Writing

+

{{ page.title }}

+ {% if page.summary %} +

{{ page.summary }}

+ {% endif %} +
+ +
+ {% for item in collection.items %} +
+
+

{{ item.contentModel }}

+

{{ item.title }}

+

{{ item.summary }}

+
+ Read +
+ {% else %} +

No articles have been published yet.

+ {% endfor %} +
diff --git a/.theme/templates/sections/projects.liquid b/.theme/templates/sections/projects.liquid new file mode 100644 index 0000000..6233706 --- /dev/null +++ b/.theme/templates/sections/projects.liquid @@ -0,0 +1,27 @@ +
+

Projects

+

{{ page.title }}

+ {% if page.summary %} +

{{ page.summary }}

+ {% endif %} +
+ +
+ {% for item in collection.items %} +
+

{{ item.contentModel }}

+

{{ item.title }}

+

{{ item.summary }}

+ {% if item.tags.size > 0 %} + + {% endif %} + View project +
+ {% else %} +

No projects have been published yet.

+ {% endfor %} +
diff --git a/.theme/theme.yml b/.theme/theme.yml new file mode 100644 index 0000000..cfca5b7 --- /dev/null +++ b/.theme/theme.yml @@ -0,0 +1,28 @@ +protocol: labyricorn-theme/v1 +id: labyricorn-default +version: 1.0.0 +engine: liquid + +templates: + layout: templates/layout.liquid + home: templates/home.liquid + notFound: templates/404.liquid + sections: + articles: templates/sections/articles.liquid + projects: templates/sections/projects.liquid + content: + page: templates/content/page.liquid + article: templates/content/article.liquid + project: templates/content/project.liquid + +assets: + styles: + - assets/styles/theme.css + scripts: [] + files: [] + fonts: [] + +security: + allowScripts: false + allowExternalAssets: false + allowedExternalOrigins: [] diff --git a/README.md b/README.md index 7396e80..0c29545 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,10 @@ publishing path before more content or integrations are added. ## Source of truth This repository is authoritative for the site content it contains. Labyricorn -reads `.labyricorn/site.yml`, discovers the configured content, builds the -website, and publishes the completed output to the local canonical site. +reads the repository participation policy in `.labyricorn/site.yml`, resolves +the canonical site definition in `site.yml`, validates the `.theme/` package, +discovers the configured content, builds the website, and publishes the +completed output to the local canonical site. Generated website files do not belong in this repository. @@ -16,6 +18,10 @@ Generated website files do not belong in this repository. ```text .labyricorn/site.yml Repository participation and publishing policy +site.yml Canonical site definition and theme selection +navigation.yml Public navigation and section-template selection +projects.yml Allowlisted project-page declarations +.theme/ Strict Liquid templates and declared static assets pages/ Standalone website pages articles/ Article source documents projects/ Project source documents @@ -30,6 +36,7 @@ The first deployment includes: - one homepage; - one introductory article; - one project page; +- a repository-owned, script-free Liquid theme; - local publication through Labyricorn. It does not yet include: diff --git a/articles/welcome.md b/articles/welcome.md index f1e52df..65f07ee 100644 --- a/articles/welcome.md +++ b/articles/welcome.md @@ -2,6 +2,7 @@ model: article title: Welcome to Labyricorn slug: welcome +route: /articles/welcome/ date: 2026-07-23 status: published summary: Why the first deployment is intentionally small and locally hosted. @@ -11,8 +12,6 @@ tags: - git --- -# Welcome to Labyricorn - Labyricorn is a configuration-first, Git-driven site builder. Repositories provide content and configuration; the builder turns those sources into a coherent website; the generated local site is the canonical runtime copy. diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index be8babd..3cd7805 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -17,15 +17,19 @@ Cloudflare Pages, Docker, or a public Control Plane. 2. Add that Gitea repository as the remote for this local repository. 3. Push the default branch to Gitea. 4. Connect the repository in the Labyricorn Control Plane. -5. Synchronize it and confirm `.labyricorn/site.yml` is valid. -6. Confirm that the `page`, `article`, and `project` models are installed and +5. Synchronize it and confirm `.labyricorn/site.yml` and the root `site.yml` + are valid. +6. Validate `.theme/theme.yml`, every referenced Liquid template, and the + declared stylesheet before creating a release. +7. Confirm that the `page`, `article`, and `project` models are installed and enabled in Labyricorn. -7. Confirm discovery finds the three Markdown documents in this repository. -8. Preview the generated routes and resolve any validation errors. -9. Build the site. -10. Publish the completed build to the local canonical site. -11. Verify the homepage, article, and project page from a browser. -12. Record the deployed Git revision and active local release. +8. Confirm discovery finds the three Markdown documents in this repository. +9. Preview the generated routes and resolve any validation errors. +10. Build the site. +11. Publish the completed build to the local canonical site. +12. Verify the homepage, article, project index, and project page from a + browser. +13. Record the deployed Git revision and active local release. ## Required proof @@ -33,6 +37,7 @@ The initial deployment is successful only when all of the following are true: - the repository revision shown by Labyricorn matches Gitea; - configuration validation uses the committed `site.yml`; +- theme validation uses the committed `.theme/theme.yml` and declared files; - all three source documents are discovered without filename heuristics; - generated routes come from configured model behavior; - the public pages are served from the activated local release; diff --git a/navigation.yml b/navigation.yml new file mode 100644 index 0000000..33e3122 --- /dev/null +++ b/navigation.yml @@ -0,0 +1,22 @@ +navigation: + - id: home + label: Home + route: / + iconName: Home + contentModel: page + + - id: articles + label: Articles + route: /articles/ + iconName: FileText + contentModel: article + presentation: + sectionTemplate: articles + + - id: projects + label: Projects + route: /projects/ + iconName: Boxes + contentModel: project + presentation: + sectionTemplate: projects diff --git a/pages/index.md b/pages/index.md index d9a40db..96cbdfa 100644 --- a/pages/index.md +++ b/pages/index.md @@ -7,8 +7,6 @@ status: published summary: A configuration-first, Git-driven home for projects, articles, and documentation. --- -# Build once. Understand every part. - Labyricorn brings project information, articles, and documentation together without replacing Git as the source of truth. diff --git a/projects.yml b/projects.yml new file mode 100644 index 0000000..30d5270 --- /dev/null +++ b/projects.yml @@ -0,0 +1,19 @@ +projects: + - id: labyricorn + source: site-definition + route: /projects/labyricorn/ + title: Labyricorn + summary: A configuration-first, Git-driven static-site builder and control plane. + license: Proprietary + tags: + - labyricorn + - git + - static-site + stack: + - TypeScript + - React + - Liquid + - Nginx + presentation: + detailTemplate: project + publishedFiles: [] diff --git a/projects/labyricorn.md b/projects/labyricorn.md index ff05f8b..37ecd7b 100644 --- a/projects/labyricorn.md +++ b/projects/labyricorn.md @@ -2,6 +2,7 @@ model: project title: Labyricorn slug: labyricorn +route: /projects/labyricorn/ status: published summary: A configuration-first, Git-driven site builder and its administrative control plane. repository_url: https://git.labyricorn.com @@ -11,8 +12,6 @@ tags: - static-site --- -# Labyricorn - Labyricorn builds and hosts locally generated sites from Git repositories. Content models, routes, navigation, styles, themes, and integrations derive their meaning from configuration rather than hardcoded content-type names. diff --git a/site.yml b/site.yml new file mode 100644 index 0000000..b3c49e0 --- /dev/null +++ b/site.yml @@ -0,0 +1,52 @@ +protocol: labyricorn-site/v1 + +site: + id: labyricorn + title: Labyricorn + baseUrl: https://labyricorn.com + language: en-US + +navigationFile: ./navigation.yml +projectsFile: ./projects.yml + +theme: + source: site-definition + path: .theme + +markdown: + dialect: labyricorn-commonmark-v1 + rawHtmlPolicy: disabled + rawHtmlEnabled: false + extensions: + tables: true + taskLists: true + footnotes: true + definitionLists: true + headingAnchors: true + fencedCode: true + syntaxHighlighting: true + callouts: true + youtube: false + wikipediaLinks: true + +hosting: + engine: nginx + production: + enabled: true + hostname: labyricorn.com + listen: 80 + staging: + enabled: true + hostname: preview.labyricorn.com + listen: 8080 + releases: + retainCount: 10 + +buildPolicy: + staging: + enabled: true + requireApproval: true + localActivation: + automatic: false + push: + automatic: false