feat(11.1-02): add the documentation theme, chroma highlighting and docs:serve

- WinterCMS-style shell: header with search and theme toggle, grouped
  sidebar, on-page TOC, pager, page actions, callouts, heading
  permalinks, footer and a 404 page
- fenced code highlighted at build time by chroma/v2 into tok-* classes,
  with a copy button; no inline script, style or handler
- vendored DM Sans/DM Mono fonts and Lucide icons with their licences
- client-side search over search-index.json built with textContent only
- summer docs:serve builds into a temp dir, serves on loopback by default,
  returns 404.html with status 404 and rebuilds on change
This commit is contained in:
Jakub Zych
2026-09-30 21:57:55 +02:00
parent d89e18bc8e
commit dd11bdb0c7
39 changed files with 3114 additions and 145 deletions

View File

@@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
{{template "head" .}}
</head>
<body class="docs docs-404">
{{template "shell-start" .}}
<main id="content" tabindex="-1">
<div class="page">
<article class="page-body">
<h1>{{.Title}}</h1>
<p class="lead">{{.Description}}</p>
<div class="prose">
<p>Use the sidebar or search to find what you need, or go back to the <a href="{{.HomeURL}}">documentation home</a>.</p>
</div>
</article>
</div>
{{template "footer" .}}
</main>
{{template "shell-end" .}}
</body>
</html>

View File

@@ -0,0 +1,6 @@
{{define "footer" -}}
<footer class="site-footer">
<span>SummerCMS documentation</span>
<span class="footer-links"><a href="{{.LLMS}}">llms.txt</a><a href="{{.LLMSFull}}">llms-full.txt</a></span>
</footer>
{{- end}}

View File

@@ -0,0 +1,9 @@
{{define "header" -}}
<header class="site-header">
<button class="menu-button icon-button" type="button" aria-label="Open navigation" aria-expanded="false" aria-controls="sidebar">{{template "icon-menu"}}{{template "icon-x"}}</button>
<a class="wordmark" href="{{.HomeURL}}" aria-label="SummerCMS documentation home"><span class="wordmark-sun">{{template "icon-sun"}}</span><span class="wordmark-text">Summer<span class="wordmark-cms">CMS</span></span></a>
<div class="header-spacer"></div>
<button class="search-trigger" type="button" aria-label="Search docs" aria-haspopup="dialog" aria-controls="search">{{template "icon-search"}}<span class="search-trigger-label">Search docs</span><kbd>Ctrl K</kbd></button>
<button class="theme-toggle" type="button" data-theme="system" aria-label="Color theme: System. Switch to Light." title="Color theme: System. Switch to Light.">{{template "icon-monitor"}}{{template "icon-sun"}}{{template "icon-moon"}}</button>
</header>
{{- end}}

View File

@@ -0,0 +1,16 @@
{{/* Lucide 1.17.0 icons (ISC, see assets/LICENSE-lucide.txt), path data copied from @lucide/vue. */}}
{{define "icon-sun"}}<svg class="icon icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>{{end}}
{{define "icon-moon"}}<svg class="icon icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/></svg>{{end}}
{{define "icon-monitor"}}<svg class="icon icon-monitor" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/></svg>{{end}}
{{define "icon-search"}}<svg class="icon icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/></svg>{{end}}
{{define "icon-menu"}}<svg class="icon icon-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 5h16"/><path d="M4 12h16"/><path d="M4 19h16"/></svg>{{end}}
{{define "icon-x"}}<svg class="icon icon-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>{{end}}
{{define "icon-chevron-left"}}<svg class="icon icon-chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg>{{end}}
{{define "icon-chevron-right"}}<svg class="icon icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg>{{end}}
{{define "icon-pencil"}}<svg class="icon icon-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/></svg>{{end}}
{{define "icon-file-text"}}<svg class="icon icon-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>{{end}}
{{define "icon-copy"}}<svg class="icon icon-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>{{end}}
{{define "icon-check"}}<svg class="icon icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>{{end}}
{{define "icon-info"}}<svg class="icon icon-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>{{end}}
{{define "icon-lightbulb"}}<svg class="icon icon-lightbulb" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/></svg>{{end}}
{{define "icon-triangle-alert"}}<svg class="icon icon-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>{{end}}

View File

@@ -1,32 +1,62 @@
{{define "head" -}}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<title>{{.DocTitle}}</title>
<meta name="description" content="{{.Description}}">
<meta property="og:title" content="{{.Title}}">
<meta property="og:description" content="{{.Description}}">
{{- if .MarkdownURL}}
<link rel="alternate" type="text/markdown" href="{{.MarkdownURL}}">
{{- end}}
<script src="{{.Assets}}/theme-init.js"></script>
<link rel="stylesheet" href="{{.Assets}}/site.css">
<script src="{{.Assets}}/site.js" defer></script>
<script src="{{.Assets}}/search.js" defer></script>
{{- end -}}
{{define "shell-start" -}}
<a class="skip-link" href="#content">Skip to content</a>
{{template "header" .}}
<div class="layout">
{{template "sidebar" .}}
<div class="scrim" hidden></div>
{{- end -}}
{{define "shell-end" -}}
</div>
{{template "search" .}}
<div class="sr-only" id="live-region" aria-live="polite"></div>
{{- end -}}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{.DocTitle}}</title>
<meta name="description" content="{{.Description}}">
<link rel="stylesheet" href="{{.CSS}}">
{{template "head" .}}
</head>
<body class="docs">
<div class="layout">
<nav class="sidebar" aria-label="Documentation">
<a class="sidebar-home" href="{{.HomeURL}}">SummerCMS</a>
{{- range .Nav}}
<div class="sidebar-section">
<p class="sidebar-title">{{.Title}}</p>
<ul>
{{- range .Items}}
<li><a href="{{.URL}}"{{if .Current}} aria-current="page"{{end}}>{{.Title}}</a></li>
{{template "shell-start" .}}
<main id="content" tabindex="-1">
<div class="page{{if .TOC}} has-toc{{end}}">
<article class="page-body">
{{- if .Eyebrow}}
<p class="eyebrow">{{.Eyebrow}}</p>
{{- end}}
</ul>
</div>
{{- end}}
</nav>
<main id="content">
<h1>{{.Title}}</h1>
<p class="lead">{{.Description}}</p>
{{.Content}}
</main>
<div class="page-actions">
{{- if .EditURL}}
<a href="{{.EditURL}}">{{template "icon-pencil"}}<span>Edit this page</span></a>
{{- end}}
<a href="{{.MarkdownURL}}">{{template "icon-file-text"}}<span>View as Markdown</span></a>
</div>
{{template "toc-inline" .}}
<div class="prose">
{{.Content}}
</div>
{{template "pager" .}}
</article>
{{template "toc" .}}
</div>
{{template "footer" .}}
</main>
{{template "shell-end" .}}
</body>
</html>

View File

@@ -0,0 +1,14 @@
{{define "pager" -}}
{{- if or .Prev .Next}}
<nav class="pager" aria-label="Previous and next page">
{{- if .Prev}}
<a class="pager-card pager-prev" href="{{.Prev.URL}}"><span class="pager-label">{{template "icon-chevron-left"}}Previous</span><span class="pager-title">{{.Prev.Title}}</span>{{if .Prev.Section}}<span class="pager-section">{{.Prev.Section}}</span>{{end}}</a>
{{- else}}
<span class="pager-empty"></span>
{{- end}}
{{- if .Next}}
<a class="pager-card pager-next" href="{{.Next.URL}}"><span class="pager-label">Next{{template "icon-chevron-right"}}</span><span class="pager-title">{{.Next.Title}}</span>{{if .Next.Section}}<span class="pager-section">{{.Next.Section}}</span>{{end}}</a>
{{- end}}
</nav>
{{- end}}
{{- end}}

View File

@@ -0,0 +1,8 @@
{{define "search" -}}
<dialog class="search" id="search">
<div class="search-field">{{template "icon-search"}}<input class="search-input" id="search-input" type="search" placeholder="Search the docs" aria-label="Search the docs" autocomplete="off" spellcheck="false" role="combobox" aria-expanded="false" aria-controls="search-results" aria-autocomplete="list" aria-activedescendant=""></div>
<div class="search-status" id="search-status"></div>
<ul id="search-results" class="search-results" role="listbox" aria-label="Search results" data-index="{{.SearchIndex}}"></ul>
<div class="sr-only" id="search-live" aria-live="polite"></div>
</dialog>
{{- end}}

View File

@@ -0,0 +1,16 @@
{{define "sidebar" -}}
<div class="sidebar-panel" id="sidebar">
<nav class="sidebar" aria-label="Documentation">
{{- range .Nav}}
<div class="sidebar-section{{if .API}} sidebar-api{{end}}">
<p class="sidebar-title">{{.Title}}</p>
<ul>
{{- range .Items}}
<li><a href="{{.URL}}"{{if .Current}} aria-current="page"{{end}}>{{.Title}}</a></li>
{{- end}}
</ul>
</div>
{{- end}}
</nav>
</div>
{{- end}}

View File

@@ -0,0 +1,24 @@
{{define "toc" -}}
{{- if .TOC}}
<aside class="toc" aria-label="On this page">
<p class="toc-title">On this page</p>
<ul>
{{- range .TOC}}
<li class="toc-h{{.Level}}"><a href="#{{.ID}}">{{.Text}}</a></li>
{{- end}}
</ul>
</aside>
{{- end}}
{{- end}}
{{define "toc-inline" -}}
{{- if .TOC}}
<details class="toc-inline">
<summary>On this page</summary>
<ul>
{{- range .TOC}}
<li class="toc-h{{.Level}}"><a href="#{{.ID}}">{{.Text}}</a></li>
{{- end}}
</ul>
</details>
{{- end}}
{{- end}}