feat(v2): add Resource Density Heatmap to Topic Map page

Render a true colour-matrix heatmap on the Topic Map page (distinct from
the size-based Label cloud on index/Tags). One tile per category, grouped
by strategic dimension, with tile warmth (.th-1..6) encoding how many
AI-curated resources it holds. Log scale since counts span ~1..160.

The existing directory grid is preserved below a new "Full Category
Directory" heading. Generator-only change; the publisher regenerates
topic-map.md. CSS adds .topic-heatmap / .topic-heatcell with light + slate
palettes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Nubenetes Bot
2026-06-20 22:20:02 +02:00
co-authored by Claude Opus 4.8
parent d19031a65f
commit 154e6940c4
2 changed files with 153 additions and 0 deletions
+98
View File
@@ -1576,6 +1576,104 @@ input[type="text"] {
.topic-map-grid { column-count: 1; }
}
/* ===================================================================
Topic Map: Resource Density Heatmap
A true colour matrix (distinct from the size-based Label cloud). Each
category is a tile whose warmth (.th-1..6) encodes resource density,
grouped by strategic dimension. Generated by v2_optimizer.py.
=================================================================== */
.topic-heatmap {
margin: 1rem 0 2rem;
padding: 1.1rem 1.2rem 1.3rem;
border: 1px solid rgba(38, 50, 56, 0.12);
border-radius: 10px;
background: rgba(38, 50, 56, 0.04);
}
[data-md-color-scheme="slate"] .topic-heatmap {
background: rgba(255, 255, 255, 0.03);
border-color: rgba(255, 255, 255, 0.1);
}
.topic-heatmap__legend {
display: flex;
align-items: center;
gap: 0.3rem;
margin-bottom: 1rem;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.03em;
color: var(--md-default-fg-color--light);
}
.topic-heatmap__legend .topic-heatcell {
width: 26px;
height: 14px;
padding: 0;
border-radius: 3px;
}
.topic-heatmap__cap { text-transform: uppercase; opacity: 0.7; }
.topic-heatmap__row {
display: grid;
grid-template-columns: 190px 1fr;
gap: 0.5rem 1rem;
align-items: start;
padding: 0.6rem 0;
border-top: 1px solid rgba(38, 50, 56, 0.08);
}
[data-md-color-scheme="slate"] .topic-heatmap__row {
border-top-color: rgba(255, 255, 255, 0.07);
}
.topic-heatmap__row:first-of-type { border-top: none; padding-top: 0; }
.topic-heatmap__dim {
font-size: 0.78rem;
font-weight: 700;
line-height: 1.25;
padding-top: 0.25rem;
color: var(--md-default-fg-color);
}
.topic-heatmap__cells {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
}
.topic-heatcell {
display: inline-flex;
align-items: center;
gap: 0.3em;
padding: 0.28rem 0.5rem;
border-radius: 5px;
font-size: 0.7rem;
font-weight: 600;
line-height: 1.2;
text-decoration: none;
color: #1a2327;
transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.topic-heatcell:hover {
transform: translateY(-2px);
box-shadow: 0 3px 9px rgba(0, 0, 0, 0.18);
}
.topic-heatcell__n {
font-size: 0.62em;
font-weight: 700;
opacity: 0.7;
}
/* Sequential warmth ramp: th-1 = sparsest, th-6 = densest. */
.th-1 { background: #e3f2fd; }
.th-2 { background: #ffe0b2; }
.th-3 { background: #ffcc80; }
.th-4 { background: #ffa726; }
.th-5 { background: #fb8c00; color: #fff; }
.th-6 { background: #e64a19; color: #fff; }
[data-md-color-scheme="slate"] .th-1 { background: #2d4456; color: #d7e3ea; }
[data-md-color-scheme="slate"] .th-2 { background: #6d4c1f; color: #f5e7d3; }
[data-md-color-scheme="slate"] .th-3 { background: #97601f; color: #fff3e0; }
[data-md-color-scheme="slate"] .th-4 { background: #c2701a; color: #fff; }
[data-md-color-scheme="slate"] .th-5 { background: #e2701a; color: #fff; }
[data-md-color-scheme="slate"] .th-6 { background: #ff5722; color: #fff; }
@media screen and (max-width: 44.9375em) {
.topic-heatmap__row { grid-template-columns: 1fr; gap: 0.35rem; }
.topic-heatmap__dim { padding-top: 0; }
}
/* Visible label for each YouTube mosaic category block (was hidden in title=) */
.mosaic-cat-label {
margin: 0 0 0.5rem;
+55
View File
@@ -1257,6 +1257,59 @@ class V2VisionEngine:
total += _count_links(v)
return total
# Resource-density heatmap: a true colour matrix (distinct from the
# size-based Label cloud on the index/Tags pages). One tile per category,
# grouped by the same strategic dimensions as the directory below, with
# tile warmth encoding how many AI-curated resources it holds. Counts span
# ~1..160, so a LOG scale is used to avoid collapsing everything into the
# lowest bucket. Six levels map onto .th-1..6.
cat_counts = {f_name: _count_links(info["content"]) for f_name, info in data.items()}
_all_counts = [c for c in cat_counts.values() if c > 0] or [1]
_hmin, _hmax = math.log(min(_all_counts)), math.log(max(_all_counts))
def _topic_heat(count):
if count <= 0:
return 1
if _hmax == _hmin:
return 3
return 1 + round((math.log(count) - _hmin) / (_hmax - _hmin) * 5) # 1..6
heatmap_md = (
"## Resource Density Heatmap\n\n"
"Each tile is a category; the warmer the colour, the more AI-curated "
"resources it holds. A fast read of where the Cloud Native ecosystem's "
"depth concentrates — click any tile to open its page.\n\n"
"<div class=\"topic-heatmap\">\n"
"<div class=\"topic-heatmap__legend\">"
"<span class=\"topic-heatmap__cap\">Fewer</span>"
"<span class=\"topic-heatcell th-1\"></span>"
"<span class=\"topic-heatcell th-2\"></span>"
"<span class=\"topic-heatcell th-3\"></span>"
"<span class=\"topic-heatcell th-4\"></span>"
"<span class=\"topic-heatcell th-5\"></span>"
"<span class=\"topic-heatcell th-6\"></span>"
"<span class=\"topic-heatmap__cap\">More</span>"
"</div>\n"
)
for dim in self.dimensions.keys():
if dim not in dim_groups:
continue
heatmap_md += "<div class=\"topic-heatmap__row\">\n"
heatmap_md += f"<div class=\"topic-heatmap__dim\">{dim}</div>\n"
heatmap_md += "<div class=\"topic-heatmap__cells\">\n"
for f in sorted(dim_groups[dim], key=lambda x: cat_counts[x], reverse=True):
count = cat_counts[f]
lvl = _topic_heat(count)
slug = f[:-3] if f.endswith(".md") else f
heatmap_md += (
f"<a class=\"topic-heatcell th-{lvl}\" href=\"./{slug}/\" "
f"title=\"{data[f]['title']}{count} resources\">"
f"<span class=\"topic-heatcell__name\">{data[f]['title']}</span>"
f"<span class=\"topic-heatcell__n\">{count}</span></a>\n"
)
heatmap_md += "</div>\n</div>\n"
heatmap_md += "</div>\n\n"
topic_md = (
"---\n"
"description: \"The complete Nubenetes V2 directory: every curated Kubernetes, "
@@ -1267,6 +1320,8 @@ class V2VisionEngine:
" Every curated category across all strategic dimensions, with the number of "
"AI-selected resources in each. Looking for the landing page? Back to the "
"[**2026 Vision**](/).\n\n"
+ heatmap_md +
"## Full Category Directory\n\n"
"<div class=\"topic-map-grid\" markdown=\"1\">\n\n"
)
for dim in self.dimensions.keys():