Files
blu-lux/templates.lux
Brandon Lucas 0b0cf2f8a2 feat: add blu-site static site generator and fix language issues
Build a complete static site generator in Lux that faithfully clones
blu.cx (elmstatic). Generates 14 post pages, section indexes, tag pages,
and a home page with snippets grid from markdown content.

ISSUES.md documents 15 Lux language limitations found during the project.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 07:57:45 -05:00

111 lines
7.0 KiB
Plaintext

// HTML templates for the static site generator
// All templates generate HTML via string concatenation
pub fn htmlHead(title: String, description: String): String =
"<!doctype html><html lang=\"en\"><head>" +
"<title>" + title + "</title>" +
"<meta charset=\"utf-8\">" +
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" +
"<meta name=\"description\" content=\"" + description + "\">" +
"<meta property=\"og:title\" content=\"" + title + "\">" +
"<meta property=\"og:description\" content=\"" + description + "\">" +
"<meta property=\"og:type\" content=\"website\">" +
"<meta property=\"og:url\" content=\"https://blu.cx\">" +
"<meta property=\"og:image\" content=\"https://blu.cx/images/social-card.png\">" +
"<meta property=\"og:site_name\" content=\"Brandon Lucas\">" +
"<meta name=\"twitter:card\" content=\"summary_large_image\">" +
"<meta name=\"twitter:title\" content=\"" + title + "\">" +
"<meta name=\"twitter:description\" content=\"" + description + "\">" +
"<link rel=\"canonical\" href=\"https://blu.cx\">" +
"<link rel=\"preload\" href=\"/fonts/EBGaramond-Regular.woff2\" as=\"font\" type=\"font/woff2\" crossorigin=\"\">" +
"<link rel=\"preload\" href=\"/fonts/UnifrakturMaguntia-Regular.woff2\" as=\"font\" type=\"font/woff2\" crossorigin=\"\">" +
"<link href=\"/styles.css\" rel=\"stylesheet\" type=\"text/css\">" +
"<link href=\"/highlight/tokyo-night-dark.min.css\" rel=\"stylesheet\" type=\"text/css\">" +
"<script src=\"/highlight/highlight.min.js\" defer=\"\"></script>" +
"<script>document.addEventListener('DOMContentLoaded', function() \{ hljs.highlightAll(); \});</script>" +
"</head>"
pub fn htmlNav(): String =
"<a href=\"/\"><img src=\"/images/favicon.webp\" alt=\"Narsil Logo\" width=\"59\" height=\"80\"></a>"
pub fn htmlFooter(): String =
"<div class=\"footer flex flex-col gap-2 items-center\">" +
"<div><a href=\"/\"><img alt=\"Narsil Favicon\" src=\"/images/favicon.webp\" width=\"59\" height=\"80\"></a></div>" +
"<div class=\"flex gap-2\">" +
"<a href=\"https://github.com/thebrandonlucas\"><svg width=\"16\" height=\"16\" viewbox=\"0 0 16 16\"><path fill=\"#fff\" d=\"M7.999,0.431c-4.285,0-7.76,3.474-7.76,7.761 c0,3.428,2.223,6.337,5.307,7.363c0.388,0.071,0.53-0.168,0.53-0.374c0-0.184-0.007-0.672-0.01-1.32 c-2.159,0.469-2.614-1.04-2.614-1.04c-0.353-0.896-0.862-1.135-0.862-1.135c-0.705-0.481,0.053-0.472,0.053-0.472 c0.779,0.055,1.189,0.8,1.189,0.8c0.692,1.186,1.816,0.843,2.258,0.645c0.071-0.502,0.271-0.843,0.493-1.037 C4.86,11.425,3.049,10.76,3.049,7.786c0-0.847,0.302-1.54,0.799-2.082C3.768,5.507,3.501,4.718,3.924,3.65 c0,0,0.652-0.209,2.134,0.796C6.677,4.273,7.34,4.187,8,4.184c0.659,0.003,1.323,0.089,1.943,0.261 c1.482-1.004,2.132-0.796,2.132-0.796c0.423,1.068,0.157,1.857,0.077,2.054c0.497,0.542,0.798,1.235,0.798,2.082 c0,2.981-1.814,3.637-3.543,3.829c0.279,0.24,0.527,0.713,0.527,1.437c0,1.037-0.01,1.874-0.01,2.129 c0,0.208,0.14,0.449,0.534,0.373c3.081-1.028,5.302-3.935,5.302-7.362C15.76,3.906,12.285,0.431,7.999,0.431z\"></path></svg></a>" +
"<a href=\"https://x.com/brandonstlucas\"><svg width=\"16\" height=\"16\" viewbox=\"0 0 16 16\"><path fill=\"#fff\" d=\"M15.969,3.058c-0.586,0.26-1.217,0.436-1.878,0.515c0.675-0.405,1.194-1.045,1.438-1.809 c-0.632,0.375-1.332,0.647-2.076,0.793c-0.596-0.636-1.446-1.033-2.387-1.033c-1.806,0-3.27,1.464-3.27,3.27 c0,0.256,0.029,0.506,0.085,0.745C5.163,5.404,2.753,4.102,1.14,2.124C0.859,2.607,0.698,3.168,0.698,3.767 c0,1.134,0.577,2.135,1.455,2.722C1.616,6.472,1.112,6.325,0.671,6.08c0,0.014,0,0.027,0,0.041c0,1.584,1.127,2.906,2.623,3.206 C3.02,9.402,2.731,9.442,2.433,9.442c-0.211,0-0.416-0.021-0.615-0.059c0.416,1.299,1.624,2.245,3.055,2.271 c-1.119,0.877-2.529,1.4-4.061,1.4c-0.264,0-0.524-0.015-0.78-0.046c1.447,0.928,3.166,1.469,5.013,1.469 c6.015,0,9.304-4.983,9.304-9.304c0-0.142-0.003-0.283-0.009-0.423C14.976,4.29,15.531,3.714,15.969,3.058z\"></path></svg></a>" +
"</div>" +
"<div>Copyright (c) 2025 Brandon Lucas. All Rights Reserved.</div>" +
"</div>"
// Full HTML document wrapper
pub fn htmlDocument(title: String, description: String, body: String): String =
htmlHead(title, description) +
"<body><main class=\"flex flex-col gap-8 w-[80%] items-center mx-auto my-20\">" +
htmlNav() +
body +
htmlFooter() +
"</main></body></html>"
// Individual post/article page
pub fn htmlPostPage(title: String, date: String, tagsHtml: String, content: String): String =
"<div class=\"page w-[80%] flex flex-col gap-8\">" +
"<h1 class=\"text-4xl text-center font-bold w-full\">" + title + "</h1>" +
"<div class=\"post-content flex flex-col gap-4 pb-12 border-b\">" +
"<div class=\"post-metadata border-b mb-8 pb-8 items-center flex flex-col\">" +
"<span>" + date + "</span>" +
"<span>-</span>" +
"<div>" + tagsHtml + "</div>" +
"</div>" +
"<div class=\"markdown\">" + content + "</div>" +
"</div></div>"
// Render tag links for a post
pub fn renderTagLinks(tags: List<String>): String =
String.join(List.map(tags, fn(tag: String): String =>
"<a href=\"/tags/" + tag + "\">" + tag + "</a>"
), ", ")
// Section index page (list of posts)
pub fn htmlPostList(sectionTitle: String, postsHtml: String): String =
"<div class=\"page w-[80%] flex flex-col gap-8\">" +
"<h1 class=\"text-4xl text-center font-bold w-full\">" + sectionTitle + "</h1>" +
"<div class=\"flex flex-col gap-4\">" +
postsHtml +
"</div></div>"
// A single post entry in a list
pub fn htmlPostEntry(title: String, date: String, url: String): String =
"<div class=\"flex flex-col gap-1 border-b pb-4\">" +
"<a href=\"" + url + "\" class=\"text-xl\">" + title + "</a>" +
"<span class=\"text-gray-400\">" + date + "</span>" +
"</div>"
// Home page with snippets grid
pub fn htmlHomePage(siteTitle: String, snippetsHtml: String): String =
"<h1 class=\"unifrakturmaguntia-regular text-6xl text-center w-full\">" + siteTitle + "</h1>" +
"<div class=\"font-bold text-4xl italic text-center w-full\">" + "Βράνδων Λουκᾶς" + "</div>" +
"<h2 class=\"text-xl text-center flex flex-col\">" +
"<span>Bitcoin Lightning Payments @ voltage.cloud</span>" +
"<span>Bitcoin Privacy &amp; Scalability @ payjoin.org.</span>" +
"<span>Love sovereign software &amp; history.</span>" +
"<span>Learning Nix, Elm, Rust, Ancient Greek and Latin.</span>" +
"</h2>" +
"<div class=\"flex flex-col gap-4 w-full\">" +
snippetsHtml +
"</div>"
// Snippet card (used on home page)
pub fn htmlSnippetCard(content: String): String =
"<div class=\"flex flex-col gap-4 border border-gray-500 p-8 rounded-sm max-h-150 overflow-y-auto text-wrap break-words\">" +
"<div><div class=\"markdown\">" + content + "</div></div>" +
"</div>"
// Tag index page
pub fn htmlTagPage(tagName: String, postsHtml: String): String =
"<div class=\"page w-[80%] flex flex-col gap-8\">" +
"<h1 class=\"text-4xl text-center font-bold w-full\">Tag: " + tagName + "</h1>" +
"<div class=\"flex flex-col gap-4\">" +
postsHtml +
"</div></div>"