1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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 `<script>` at bottom of `tag-cloud.html` partial using Hugo Pipes:
```
{{ $js := resources.Get "js/tag-cloud-spiral.js" | minify }}
<script src="{{ $js.RelPermalink }}"></script>
```
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
|