From: Danilo M. Date: Tue, 21 Apr 2026 21:27:45 +0000 (+0200) Subject: docs: add tag cloud spiral layout design spec X-Git-Tag: release_22042026-1342~28 X-Git-Url: https://git.danix.xyz/?a=commitdiff_plain;h=a4edc5815170134875f19c23c028ebf88e971bba;p=danix.xyz-2.git docs: add tag cloud spiral layout design spec --- diff --git a/docs/superpowers/specs/2026-04-21-tag-cloud-spiral-design.md b/docs/superpowers/specs/2026-04-21-tag-cloud-spiral-design.md new file mode 100644 index 0000000..3d840c9 --- /dev/null +++ b/docs/superpowers/specs/2026-04-21-tag-cloud-spiral-design.md @@ -0,0 +1,79 @@ +# Tag Cloud: Archimedean Spiral Layout + +## Context + +The existing tag cloud partial uses centered flex-wrap with continuous font scaling (weight → size/opacity). Tags appear as a flat weighted list. The goal is a visually striking cloud: big tags centered, smaller tags scattered organically around the outside — deterministic per tag, not random per load. + +## Approach: Archimedean Spiral + CSS Fallback + +Two layers: +- **Hugo partial** (existing) renders tags in DOM with added `data-weight` attribute +- **Vanilla JS module** reads weights, sorts tags, places them via Archimedean spiral + +CSS fallback (no JS / narrow viewport): existing centered flex layout unchanged. + +## Components + +### 1. `tag-cloud.html` partial (modify) + +- Add `data-weight="{{ $ratio }}"` to each `.tag-cloud-link` element +- Add `data-tag-cloud` attribute to the `.tag-cloud` container div +- No other changes to markup or existing CSS classes + +### 2. `assets/js/tag-cloud-spiral.js` (new, ~60 lines) + +**Algorithm:** +1. Find all `[data-tag-cloud]` containers on page +2. For each container: collect tags, sort descending by `data-weight` +3. Per tag: derive deterministic angle seed from tag text hash +4. Place along Archimedean spiral `r = a * θ` stepping `θ` until AABB collision-free +5. Set container `position: relative`, tags `position: absolute` with computed `left/top` +6. Set container explicit height = bounding box of all placed tags + 2rem padding +7. Remove flex layout class from container after placement + +**Determinism:** simple string hash (sum of charCodes) → starting `θ` offset. Same tag text always seeds same angle. + +**Collision detection:** AABB check against array of already-placed rects. O(n²) — fine for ≤50 tags. + +**Responsive guard:** if container `offsetWidth < 400px`, skip spiral entirely, leave flex layout intact. + +### 3. Script loading + +Inline ` +``` +Or inline if small enough. Loaded only when partial is rendered. + +## A11y Compliance (WCAG 2.1 AA) + +- **DOM order** = weight order (biggest first) — logical reading sequence +- **Tab order** = DOM order — keyboard navigation follows prominence, acceptable +- **No animation** — `prefers-reduced-motion` not affected +- **Container:** `overflow: visible` to prevent clipping at zoom levels +- **Existing** `aria-label` on links preserved unchanged + +## CSS Changes + +- Container: add `overflow: visible` and `min-height` guard (set by JS, not CSS) +- No color/spacing changes — theming via CSS variables unaffected + +## Fallback Behavior + +| Condition | Behavior | +|-----------|----------| +| JS disabled | Centered flex wrap (current layout) | +| Container < 400px | Centered flex wrap (JS skips spiral) | +| JS enabled, container ≥ 400px | Archimedean spiral | + +## Verification + +1. `npm run build` — CSS compiles clean +2. `hugo serve` — tag cloud renders on homepage, sidebar, 404 +3. JS enabled wide viewport: spiral layout, big tags centered +4. JS enabled narrow viewport (< 400px): flex fallback +5. JS disabled (devtools): flex fallback +6. Keyboard Tab through tags: logical order, all focusable +7. Dark/light mode toggle: colors correct (CSS vars) +8. Screen reader (or axe DevTools): no new violations