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:
@@ -17,6 +17,7 @@ templ Layout(v LayoutView) {
|
||||
<title>{ v.Title }</title>
|
||||
<meta name="description" content={ v.Description }/>
|
||||
<meta name="color-scheme" content="dark"/>
|
||||
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml"/>
|
||||
<link rel="stylesheet" href="/static/site.css"/>
|
||||
<link rel="stylesheet" href="/static/chroma.css"/>
|
||||
<script src="/static/htmx.min.js" defer></script>
|
||||
@@ -68,7 +69,8 @@ templ Nav(items []NavItem, current string) {
|
||||
templ navLinks(items []NavItem, current string) {
|
||||
for _, it := range items {
|
||||
<a href={ templ.SafeURL(it.Href) } class={ navClass(it) } aria-current={ currentAttr(it) }>
|
||||
<span class="nav-gutter" aria-hidden="true">{ it.Gutter }</span>
|
||||
<span class="nav-mark">@AreaIcon(it.Area)
|
||||
</span>
|
||||
<span class="nav-title">{ it.Title }</span>
|
||||
</a>
|
||||
}
|
||||
@@ -76,7 +78,8 @@ templ navLinks(items []NavItem, current string) {
|
||||
|
||||
templ evidenceLink(current string) {
|
||||
<a href="/evidence/" class={ navClassFor(current == "evidence") }>
|
||||
<span class="nav-gutter" aria-hidden="true">EV</span>
|
||||
<span class="nav-mark">@AreaIcon("evidence")
|
||||
</span>
|
||||
<span class="nav-title">Evidence index</span>
|
||||
</a>
|
||||
}
|
||||
@@ -178,7 +181,8 @@ templ FragmentView(f stage.Fragment) {
|
||||
</header>
|
||||
switch f.Kind {
|
||||
case stage.KindText:
|
||||
<div class="frag-text">@f.Body</div>
|
||||
<div class="frag-text">@f.Body
|
||||
</div>
|
||||
case stage.KindCode:
|
||||
if f.Code != nil {
|
||||
<figure class="code">
|
||||
@@ -263,7 +267,8 @@ templ EvidenceTable(rows []EvidenceRow) {
|
||||
templ PillarCard(c PillarCardView) {
|
||||
<article class="pillar">
|
||||
<p class="pillar-kicker">
|
||||
<span class="pillar-gutter" aria-hidden="true">{ c.Area.Gutter }</span>
|
||||
<span class="pillar-mark">@AreaIcon(c.Area.Key)
|
||||
</span>
|
||||
if c.Area.Role != "" {
|
||||
<span>{ c.Area.Role }</span>
|
||||
}
|
||||
@@ -283,11 +288,7 @@ templ PillarCard(c PillarCardView) {
|
||||
</ul>
|
||||
}
|
||||
if len(c.Tech) > 0 {
|
||||
<ul class="tech" aria-label="Technology">
|
||||
for _, t := range c.Tech {
|
||||
<li>{ t }</li>
|
||||
}
|
||||
</ul>
|
||||
@TechChips(c.Tech)
|
||||
}
|
||||
<p class="pillar-foot">
|
||||
<span class="pillar-count">{ strconv.Itoa(c.Pages) } pages</span>
|
||||
|
||||
67
internal/web/templates/icons.templ
Normal file
67
internal/web/templates/icons.templ
Normal file
@@ -0,0 +1,67 @@
|
||||
package templates
|
||||
|
||||
import "gitea.lab.audasmedia.com.au/sam/ai-resume/internal/brand"
|
||||
|
||||
// AreaIcon is the mark for one area of the site. The six marks are drawn here,
|
||||
// geometrically, in one visual language: 24x24, 1.6 px stroke, round caps, no
|
||||
// fill, colour inherited from the text. They are decoration only — the label
|
||||
// beside them carries the meaning — so every one is aria-hidden.
|
||||
//
|
||||
// "evidence" is the seventh mark: the index, not an area.
|
||||
templ AreaIcon(area string) {
|
||||
<svg class="area-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||
switch area {
|
||||
case "ai":
|
||||
<circle cx="5.5" cy="6.5" r="2.5"/>
|
||||
<circle cx="18.5" cy="6.5" r="2.5"/>
|
||||
<circle cx="12" cy="17.5" r="2.5"/>
|
||||
<path d="M8 6.5h8"/>
|
||||
<path d="M6.7 8.8 10.8 15.6"/>
|
||||
<path d="M17.3 8.8 13.2 15.6"/>
|
||||
case "platform":
|
||||
<rect x="3.5" y="3.5" width="17" height="4.6" rx="1.5"/>
|
||||
<rect x="3.5" y="9.7" width="17" height="4.6" rx="1.5"/>
|
||||
<rect x="3.5" y="15.9" width="17" height="4.6" rx="1.5"/>
|
||||
<path d="M7 5.8h2M7 12h2M7 18.2h2"/>
|
||||
case "edge":
|
||||
<rect x="2.5" y="13" width="9" height="8" rx="1.5"/>
|
||||
<circle cx="7" cy="17" r="1.6"/>
|
||||
<path d="M15 11.5a3.5 3.5 0 0 1-3.5 3.5"/>
|
||||
<path d="M18 11.5a6.5 6.5 0 0 1-6.5 6.5"/>
|
||||
case "products":
|
||||
<path d="M12 3.2 19 7.3v9.4L12 20.8 5 16.7V7.3z"/>
|
||||
<path d="M5 7.3l7 4.1 7-4.1"/>
|
||||
<path d="M12 11.4v9.4"/>
|
||||
case "history":
|
||||
<circle cx="12" cy="12" r="8.5"/>
|
||||
<path d="M12 7v5.2l3.4 2.1"/>
|
||||
case "about":
|
||||
<circle cx="12" cy="8.4" r="3.6"/>
|
||||
<path d="M5.4 20a6.6 6.6 0 0 1 13.2 0"/>
|
||||
case "evidence":
|
||||
<path d="M6.8 3.4h7.2l4 4v13.2H6.8z"/>
|
||||
<path d="M14 3.4v4h4"/>
|
||||
<path d="M9.6 14.2l2 2 3.6-4.1"/>
|
||||
}
|
||||
</svg>
|
||||
}
|
||||
|
||||
// TechChips is the technology list on a page. A chip carries a published mark
|
||||
// when one exists and plain text when it does not, so a missing icon reads as a
|
||||
// design decision rather than a broken image.
|
||||
templ TechChips(items []string) {
|
||||
<ul class="tech" aria-label="Technology">
|
||||
for _, t := range items {
|
||||
<li class="tech-chip">
|
||||
if paths, ok := brand.Mark(t); ok {
|
||||
<svg class="tech-mark" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false">
|
||||
for _, d := range paths {
|
||||
<path d={ d } fill="currentColor"/>
|
||||
}
|
||||
</svg>
|
||||
}
|
||||
<span>{ t }</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
type NavItem struct {
|
||||
Title string
|
||||
Href string
|
||||
Gutter string
|
||||
Area string
|
||||
Current bool
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user