visuals: technology marks, area icons, client wall, two diagrams

Phase 7 (AUD-149), cheapest first.

- internal/brand: vendored Simple Icons path data (CC0) for 50 marks, looked up
  by the technology label the content already uses. Inline SVG, 16 px,
  currentColor, no request and no font. tools/gen_brand_icons.sh regenerates it.
- Six hand-drawn area icons replace the two-letter badges in the nav and on the
  landing cards; the evidence index gets a seventh.
- TechChips renders a mark where one exists and plain text where one does not,
  so a missing icon reads as a decision rather than a broken image.
- Client wall: 45 names as one typeface, one size, one grid of equal tiles, and
  the reason for no logo collage written on the page. No mark invented.
- Two diagrams, authored in-repo: homelab topology (platform-network.md) and
  the RAG pipeline (ai-rag-corpus.md), each referenced from the page it explains.
- Content images now carry intrinsic width/height, loading="lazy" and
  decoding="async" at render time; a site test fails the build if an image
  ships without them.
- Topbar is two rows at desktop: the area names plus marks no longer overlap the
  search form (Lighthouse target-size was failing at 1440 px).
- Favicon, so the browser stops requesting a missing /favicon.ico.

Satisfies: logos on every area page, area icons in nav and cards, two diagrams
referenced from their pages, images sized and lazy, Lighthouse >= 95 held.

(cherry picked from commit a473e4738a3751418c2097df2bcf0568fb6d0208)
This commit is contained in:
2026-10-28 15:50:08 +11:00
parent 59580b6e45
commit a76882de67
18 changed files with 914 additions and 51 deletions

View File

@@ -17,7 +17,6 @@ templ HomePage(v HomeView) {
</p>
@SearchForm("", "hero-search")
</section>
<section class="pillars" aria-label="Six areas">
for _, c := range v.Cards {
@PillarCard(c)
@@ -46,14 +45,9 @@ templ DocPage(v DocView) {
}
</div>
if len(v.Tech) > 0 {
<ul class="tech" aria-label="Technology">
for _, t := range v.Tech {
<li>{ t }</li>
}
</ul>
@TechChips(v.Tech)
}
</header>
<div class="prose">
@v.Body
</div>
@@ -97,7 +91,6 @@ templ DocPage(v DocView) {
</ul>
</section>
}
<footer class="doc-foot">
<p>Last verified { v.Doc.LastVerified } · <code>content/{ v.Doc.Path }</code></p>
</footer>
@@ -207,15 +200,21 @@ templ StreamPage(v StreamView) {
<p class="doc-claim">This page renders the same layout and the same panel as every other page. Its panel is bound to an event stream instead of pre-rendered files, and no layout code changed.</p>
</header>
<div class="prose">
<p>The presentation panel is a content sink. In Mode A it takes pre-rendered fragments over
<p>
The presentation panel is a content sink. In Mode A it takes pre-rendered fragments over
HTMX: you click a row in an evidence table and one file is swapped in. On this page the same
panel is bound to <code>/api/stage/stream</code> and receives { strconv.Itoa(v.Frames) } fragments
over server-sent events, one after another.</p>
<p>Fragments are pushed, not requested. Nothing about the panel — its markup, its fragment
over server-sent events, one after another.
</p>
<p>
Fragments are pushed, not requested. Nothing about the panel — its markup, its fragment
view, its empty state — knows the difference. That is the point: the Phase 5 console
re-points this panel at retrieval output and the layout does not change.</p>
<p class="section-note">This demonstration streams the artefacts already built for the site, with
no model in the loop. It exists to prove the transport, not to pretend an AI is answering.</p>
re-points this panel at retrieval output and the layout does not change.
</p>
<p class="section-note">
This demonstration streams the artefacts already built for the site, with
no model in the loop. It exists to prove the transport, not to pretend an AI is answering.
</p>
</div>
if len(v.Fragment) > 0 {
<section class="doc-sub">