# Tag Cloud Spiral Layout Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Replace the flat flex-wrap tag cloud with an Archimedean spiral layout where big tags cluster at center and smaller tags scatter outward, deterministically per tag text.

**Architecture:** Vanilla JS module reads `data-weight` attributes rendered by the Hugo partial, sorts tags by weight, places them along an outward Archimedean spiral with AABB collision detection, then sets absolute positions. Falls back to existing centered flex layout when JS is disabled or container is < 400px wide.

**Tech Stack:** Hugo (partial modification), Vanilla JS (ES5-compatible, no build step), Hugo Pipes (minify), existing CSS variables for theming.

---

## File Map

| Action | Path | Responsibility |
|--------|------|----------------|
| Modify | `themes/danix-xyz-hacker/layouts/partials/tag-cloud.html` | Add `data-weight` attr to links, `data-tag-cloud` to container |
| Modify | `themes/danix-xyz-hacker/assets/css/main.css` | Add `overflow: visible` to `.tag-cloud` |
| Create | `themes/danix-xyz-hacker/assets/js/tag-cloud-spiral.js` | Spiral placement algorithm |
| Modify | `themes/danix-xyz-hacker/layouts/_default/baseof.html` | Load spiral script via Hugo Pipes |

---

### Task 1: Add data attributes to tag-cloud partial

**Files:**
- Modify: `themes/danix-xyz-hacker/layouts/partials/tag-cloud.html:42,49,57`

- [ ] **Step 1: Add `data-tag-cloud` to both container divs**

In `tag-cloud.html`, both render paths have `<div class="tag-cloud">`. Add `data-tag-cloud` attribute to each:

```html
{{- if $wrapInWidget -}}
<div class="sidebar-widget">
  <p class="sidebar-widget-label"># {{ i18n "topTags" }}</p>
  <nav aria-label="{{ i18n "exploreTopics" }}">
    <div class="tag-cloud" data-tag-cloud>
{{- else -}}
<section aria-labelledby="tag-cloud-heading">
  <{{ $headingLevel }} id="tag-cloud-heading" class="text-lg font-semibold text-accent mb-4">
    {{ $heading }}
  </{{ $headingLevel }}>
  <nav aria-label="{{ i18n "exploreTopics" }}">
    <div class="tag-cloud" data-tag-cloud>
{{- end -}}
```

- [ ] **Step 2: Add `data-weight` to each tag link**

In the `range $orderedTags` block, add `data-weight="{{ printf "%.4f" $ratio }}"` to the `<a>` tag:

```html
        <a
          href="{{ .Page.RelPermalink }}"
          class="tag-cloud-link"
          data-weight="{{ printf "%.4f" $ratio }}"
          {{- if ge $ratio 0.5 }}
          style="font-size: {{ $size }}rem; color: var(--accent); opacity: {{ $opacity }};"
          {{- else }}
          style="font-size: {{ $size }}rem; color: var(--text-dim); opacity: {{ $opacity }};"
          {{- end }}
          aria-label="{{ .Name }}{{- if $showCount }} ({{ i18n "postCount" $count }}){{- end -}}"
        >
```

- [ ] **Step 3: Verify Hugo renders correctly**

Run: `hugo serve` and inspect homepage tag cloud in browser devtools.
Expected: each `<a>` has `data-weight="0.XXXX"`, container `<div>` has `data-tag-cloud` attribute.

- [ ] **Step 4: Commit**

```bash
git add themes/danix-xyz-hacker/layouts/partials/tag-cloud.html
git commit -m "feat: add data-weight and data-tag-cloud attributes to tag cloud partial"
```

---

### Task 2: Add `overflow: visible` to `.tag-cloud` CSS

**Files:**
- Modify: `themes/danix-xyz-hacker/assets/css/main.css:357`

- [ ] **Step 1: Update `.tag-cloud` rule**

Find `.tag-cloud` rule (around line 357) and add `overflow: visible`:

```css
.tag-cloud {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  align-items: baseline;
  overflow: visible;
}
```

- [ ] **Step 2: Rebuild CSS**

Run: `npm run build`
Expected: exits 0, `main.min.css` updated.

- [ ] **Step 3: Commit**

```bash
git add themes/danix-xyz-hacker/assets/css/main.css themes/danix-xyz-hacker/assets/css/main.min.css
git commit -m "feat: add overflow visible to tag-cloud container"
```

---

### Task 3: Create the spiral JS module

**Files:**
- Create: `themes/danix-xyz-hacker/assets/js/tag-cloud-spiral.js`

- [ ] **Step 1: Create the file with full algorithm**

```javascript
document.addEventListener('DOMContentLoaded', function () {
  var containers = document.querySelectorAll('[data-tag-cloud]');
  if (!containers.length) return;

  Array.prototype.forEach.call(containers, function (container) {
    if (container.offsetWidth < 400) return;

    var links = Array.prototype.slice.call(
      container.querySelectorAll('.tag-cloud-link')
    );
    if (!links.length) return;

    // Sort descending by weight (biggest first = placed near center)
    links.sort(function (a, b) {
      return parseFloat(b.dataset.weight) - parseFloat(a.dataset.weight);
    });

    // String hash → deterministic angle seed (0..2π)
    function hashAngle(str) {
      var h = 0;
      for (var i = 0; i < str.length; i++) {
        h = (h * 31 + str.charCodeAt(i)) & 0xffffffff;
      }
      return ((h >>> 0) / 0xffffffff) * 2 * Math.PI;
    }

    // AABB collision check
    function overlaps(a, b) {
      return !(
        a.right  < b.left  ||
        a.left   > b.right ||
        a.bottom < b.top   ||
        a.top    > b.bottom
      );
    }

    var placed = [];
    var containerWidth = container.offsetWidth;
    var cx = containerWidth / 2;

    // Measure each tag before repositioning
    var sizes = links.map(function (link) {
      var rect = link.getBoundingClientRect();
      return { w: rect.width, h: rect.height };
    });

    // Switch container to relative positioning
    container.style.position = 'relative';
    container.style.display = 'block';

    var padding = 8; // px gap between tags
    var aStep = 0.3; // radians per spiral step
    var rScale = (containerWidth * 0.018); // spiral tightness

    var minTop = 0, maxBottom = 0;

    links.forEach(function (link, i) {
      var w = sizes[i].w;
      var h = sizes[i].h;
      var seed = hashAngle(link.textContent.trim());
      var theta = seed;
      var placed_rect;

      // Step along spiral until no collision
      for (var attempt = 0; attempt < 2000; attempt++) {
        var r = rScale * theta;
        var x = cx + r * Math.cos(theta) - w / 2;
        var y = r * Math.sin(theta) - h / 2;

        var candidate = { left: x, top: y, right: x + w, bottom: y + h };
        var collision = false;

        for (var j = 0; j < placed.length; j++) {
          var p = placed[j];
          var padded = {
            left: p.left - padding,
            top: p.top - padding,
            right: p.right + padding,
            bottom: p.bottom + padding
          };
          if (overlaps(candidate, padded)) {
            collision = true;
            break;
          }
        }

        if (!collision) {
          placed_rect = candidate;
          break;
        }
        theta += aStep;
      }

      if (!placed_rect) {
        // Fallback: just append to flow if spiral exhausted
        link.style.position = 'static';
        return;
      }

      placed.push(placed_rect);

      link.style.position = 'absolute';
      link.style.left = Math.round(placed_rect.left) + 'px';
      link.style.top = Math.round(placed_rect.top) + 'px';

      if (placed_rect.top < minTop) minTop = placed_rect.top;
      if (placed_rect.bottom > maxBottom) maxBottom = placed_rect.bottom;
    });

    // Normalize: shift all tags so topmost is at y=16px
    var offset = 16 - minTop;
    links.forEach(function (link) {
      if (link.style.position === 'absolute') {
        link.style.top = (parseInt(link.style.top) + offset) + 'px';
      }
    });

    // Set container height to fit all tags + 2rem bottom padding
    container.style.height = (maxBottom - minTop + 48) + 'px';
  });
});
```

- [ ] **Step 2: Verify file saved correctly**

Run: `wc -l themes/danix-xyz-hacker/assets/js/tag-cloud-spiral.js`
Expected: ~100 lines.

- [ ] **Step 3: Commit**

```bash
git add themes/danix-xyz-hacker/assets/js/tag-cloud-spiral.js
git commit -m "feat: add Archimedean spiral layout engine for tag cloud"
```

---

### Task 4: Load the script via Hugo Pipes in baseof.html

**Files:**
- Modify: `themes/danix-xyz-hacker/layouts/_default/baseof.html`

- [ ] **Step 1: Add script block after existing JS entries**

In `baseof.html`, find the block of script tags (around line 83–120). Add after the last existing script block (before `</body>`):

```html
  <!-- Tag cloud spiral layout -->
  {{ $tagCloudScript := resources.Get "js/tag-cloud-spiral.js" | minify }}
  <script src="{{ $tagCloudScript.RelPermalink }}"></script>
```

- [ ] **Step 2: Verify Hugo builds without error**

Run: `hugo` (build, not serve)
Expected: exits 0, no errors about missing resources.

- [ ] **Step 3: Commit**

```bash
git add themes/danix-xyz-hacker/layouts/_default/baseof.html
git commit -m "feat: load tag-cloud-spiral.js via Hugo Pipes"
```

---

### Task 5: End-to-end verification

**Files:** None modified — verification only.

- [ ] **Step 1: Start dev server and open homepage**

Run: `hugo serve`
Open: `http://localhost:1313`
Expected: tag cloud at bottom of homepage shows spiral layout. Big tags near center, smaller tags scattered outward.

- [ ] **Step 2: Test narrow viewport fallback**

In browser devtools, set viewport width to 375px (iPhone SE).
Expected: tag cloud falls back to flex-wrap layout (no absolute positioning applied).

- [ ] **Step 3: Test JS-disabled fallback**

In devtools → Settings → Debugger → "Disable JavaScript". Reload.
Expected: tag cloud shows centered flex-wrap (current layout). No broken positioning.

- [ ] **Step 4: Test sidebar tag cloud**

Open any article page with sidebar.
Expected: sidebar tag cloud (≤15 tags) also renders in spiral if container ≥ 400px; flex fallback on narrow.

- [ ] **Step 5: Test 404 page**

Open: `http://localhost:1313/404.html`
Expected: tag cloud renders spiral layout.

- [ ] **Step 6: Keyboard navigation**

Tab through tags on homepage. Expected: all tags focusable in DOM order (weight descending), visible focus ring (accent outline).

- [ ] **Step 7: Dark/light mode**

Toggle theme. Expected: tag colors correct in both modes (CSS variables, not hardcoded).

- [ ] **Step 8: Determinism check**

Hard-reload page 3 times. Expected: tag positions identical each load.

- [ ] **Step 9: Final commit if any fixups made**

```bash
git add -p
git commit -m "fix: tag cloud spiral fixups from e2e verification"
```
