--- /dev/null
+# 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