diff --git a/README.md b/README.md index a929839..4972948 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,40 @@ prefers reduced motion. When hero styles or behavior change, increment the corresponding `style.css` or `section-heroes.js` cache-busting version in `templates/base.html`. +## Homepage tag discovery + +The homepage ends with an **Explore the Signal** topic matrix immediately after +Recent Activity and Featured Projects and before the site footer. It is a +server-rendered discovery route over the canonical child records under +`content/tags/`; it does not maintain a separate topic list. Each topic is a +normal link to its dedicated `/tags//` page and includes its current +published usage count. + +Counts use the same discovery scope as the tag directory and dedicated tag +pages: projects, project devlog entries, blog entries, and articles. The matrix +uses four restrained visual levels for counts of 0–1, 2–3, 4–7, and 8 or more. +The displayed count remains exact while the highest visual level is capped, so +a frequently used tag cannot dominate the layout. Tag records and content +changes update the matrix automatically on the next normal project-aware build. + +`assets/static/tags.js` progressively updates the Tag Summary readout when a +visitor hovers or focuses a topic. The summary is optional enhancement only: +every link remains visible, keyboard accessible, touch friendly, and usable +with JavaScript disabled. Styling lives in `assets/static/style.css`, uses the +existing Labyricorn gold treatment, wraps without horizontal scrolling, and +removes the small focus/hover lift for reduced-motion users. When these assets +change, increment their cache-busting versions in `templates/base.html`. + +After changing the homepage matrix: + +1. Run `python scripts/build_with_projects.py --output-path build` with the + Lektor runtime documented below. +2. Confirm the generated homepage topic count matches the tracked registry and + every topic links to its existing tag page with the expected usage count. +3. Check desktop and narrow layouts, keyboard focus, the summary enhancement, + normal link navigation, and the transition into the footer. +4. Run `python -m unittest discover -s tests -v` and `git diff --check`. + ## Routine content workflow Edit in a normal Git checkout, review the changes, and push them to `main`. diff --git a/assets/static/style.css b/assets/static/style.css index 1436c3e..31dda6d 100644 --- a/assets/static/style.css +++ b/assets/static/style.css @@ -780,6 +780,106 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style: .featured-projects-empty { margin: 0; padding: 24px 0; color: var(--muted); } .featured-projects-link { display: inline-block; margin-top: 22px; color: var(--teal); font: 500 10px var(--mono); letter-spacing: .12em; text-transform: uppercase; } +.homepage-tag-section { + border-top: 1px solid var(--rule); + padding: 52px 0 58px; + background: linear-gradient(180deg, rgba(201,168,76,.025), transparent 72%); +} +.homepage-tag-header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 28px 48px; + align-items: end; +} +.homepage-tag-header .eyebrow { margin-bottom: 10px; color: var(--mustard); } +.homepage-tag-header h2 { margin: 0; font-size: clamp(27px, 3vw, 36px); line-height: 1.2; } +.homepage-tag-header > div > p:last-child { max-width: 680px; margin: 12px 0 0; color: var(--muted); } +.homepage-tag-readout { + display: flex; + align-items: center; + gap: 9px; + margin: 0 0 4px; + color: var(--dim); + font: 500 9px var(--mono); + letter-spacing: .1em; + white-space: nowrap; + text-transform: uppercase; +} +.homepage-tag-readout span { + width: 6px; + height: 6px; + border: 1px solid var(--mustard); + background: rgba(201,168,76,.28); + box-shadow: 0 0 8px rgba(201,168,76,.25); +} +.tag-matrix { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 30px; } +.tag-matrix-link { + --tag-border: rgba(201,168,76,.26); + --tag-copy: #b7b3aa; + --tag-node: rgba(201,168,76,.42); + --tag-fill: rgba(201,168,76,.018); + display: inline-flex; + min-height: 42px; + align-items: center; + gap: 9px; + border: 1px solid var(--tag-border); + background: var(--tag-fill); + padding: 9px 12px; + color: var(--tag-copy); + font: 500 11px/1.2 var(--mono); + letter-spacing: .055em; + text-transform: uppercase; + transition: border-color .16s ease, background-color .16s ease, color .16s ease, transform .16s ease; +} +.tag-matrix-link::before { + content: ""; + width: 5px; + height: 5px; + flex: 0 0 auto; + border: 1px solid var(--tag-node); + background: var(--tag-node); +} +.tag-matrix-level-2 { + --tag-border: rgba(201,168,76,.38); + --tag-copy: #c6c1b5; + --tag-node: rgba(201,168,76,.58); + --tag-fill: rgba(201,168,76,.028); +} +.tag-matrix-level-3 { + --tag-border: rgba(201,168,76,.53); + --tag-copy: #d8d2c4; + --tag-node: rgba(201,168,76,.76); + --tag-fill: rgba(201,168,76,.045); +} +.tag-matrix-level-4 { + --tag-border: rgba(201,168,76,.7); + --tag-copy: var(--ink); + --tag-node: var(--mustard); + --tag-fill: rgba(201,168,76,.07); +} +.tag-matrix-link:hover, +.tag-matrix-link:focus-visible { + border-color: var(--mustard); + background: rgba(201,168,76,.1); + color: var(--ink); + transform: translateY(-1px); +} +.tag-matrix-link:focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; } +.tag-matrix-count { color: var(--mustard); font-size: 9px; letter-spacing: .02em; opacity: .78; } +.tag-matrix-summary { + display: grid; + grid-template-columns: 112px minmax(0, 1fr); + gap: 18px; + align-items: start; + min-height: 62px; + margin-top: 26px; + padding-top: 18px; + border-top: 1px solid rgba(39,48,57,.72); +} +.tag-matrix-summary-label { color: var(--mustard); font: 500 9px var(--mono); letter-spacing: .12em; text-transform: uppercase; } +.tag-matrix-summary p { display: grid; gap: 3px; margin: 0; color: var(--muted); font-size: 13px; } +.tag-matrix-summary strong { color: var(--ink); font: 500 11px var(--mono); letter-spacing: .06em; text-transform: uppercase; } + .card-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 1px; background: var(--rule); border: 1px solid var(--rule); } .entry-card { min-height: 245px; padding: 28px; background: var(--bg); } .entry-tags { display: flex; flex-wrap: wrap; gap: 7px; margin: 16px 0; } @@ -1247,6 +1347,11 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style: .circuit-replay { left: 16px; bottom: 13px; } h1 { font-size: 30px; } .homepage-discovery-grid { grid-template-columns: 1fr; gap: 48px; } + .homepage-tag-header { grid-template-columns: 1fr; gap: 16px; } + .homepage-tag-readout { white-space: normal; } + .tag-matrix { gap: 7px; margin-top: 24px; } + .tag-matrix-link { min-height: 44px; padding-inline: 11px; } + .tag-matrix-summary { grid-template-columns: 1fr; gap: 7px; } .featured-project-card { padding: 20px; } .card-grid { grid-template-columns: 1fr; } .tag-directory { grid-template-columns: 1fr; } @@ -1315,6 +1420,9 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style: } @media (prefers-reduced-motion: reduce) { + .tag-matrix-link { transition: none; } + .tag-matrix-link:hover, + .tag-matrix-link:focus-visible { transform: none; } .circuit-status span, .circuit-node-main i, .section-hero, diff --git a/assets/static/tags.js b/assets/static/tags.js index 5f04512..b2a20e2 100644 --- a/assets/static/tags.js +++ b/assets/static/tags.js @@ -54,4 +54,21 @@ const requestedTag = new URL(window.location.href).searchParams.get("tag") || ""; applyFilter(availableTags.has(requestedTag) ? requestedTag : "", false); } + + for (const matrix of document.querySelectorAll("[data-tag-matrix]")) { + const summaryTitle = matrix.querySelector("[data-tag-summary-title]"); + const summaryText = matrix.querySelector("[data-tag-summary-text]"); + + if (!summaryTitle || !summaryText) continue; + + for (const link of matrix.querySelectorAll("[data-tag-title]")) { + const showSummary = () => { + summaryTitle.textContent = link.dataset.tagTitle; + summaryText.textContent = link.dataset.tagSummary || `Open the ${link.dataset.tagTitle} archive.`; + }; + + link.addEventListener("mouseenter", showSummary); + link.addEventListener("focus", showSummary); + } + } })(); diff --git a/templates/base.html b/templates/base.html index 06d572b..6a149b8 100644 --- a/templates/base.html +++ b/templates/base.html @@ -10,8 +10,8 @@ - - + + diff --git a/templates/home.html b/templates/home.html index 0f6e95f..2cd5e77 100644 --- a/templates/home.html +++ b/templates/home.html @@ -181,3 +181,50 @@ + +{% set tracked_tags = site.get('/tags').children %} +
+
+
+
+

Topic map

+

Explore the Signal

+

A map of the topics that keep surfacing across Labyricorn. Pick a signal and follow it deeper into the site.

+
+

{{ tracked_tags.count() }} tracked topics across Labyricorn

+
+ + + +
+ Tag Summary +

Preview a topicHover or focus a topic to preview it. Select a topic to open its archive.

+
+
+
diff --git a/tests/test_home_template.py b/tests/test_home_template.py index 3bb5230..f331960 100644 --- a/tests/test_home_template.py +++ b/tests/test_home_template.py @@ -5,10 +5,14 @@ import unittest class HomeTemplateTests(unittest.TestCase): - def test_recent_activity_adds_only_latest_devlog_from_each_project(self) -> None: + def setUp(self) -> None: site_root = Path(__file__).resolve().parents[1] - template = (site_root / "templates" / "home.html").read_text(encoding="utf-8") - recent_activity = template.split( + self.template = (site_root / "templates" / "home.html").read_text( + encoding="utf-8" + ) + + def test_recent_activity_adds_only_latest_devlog_from_each_project(self) -> None: + recent_activity = self.template.split( '