diff options
| author | Danilo M. <danix@danix.xyz> | 2026-04-21 10:00:30 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-04-21 10:00:30 +0200 |
| commit | 5b1e212af64a1a7acef75ef24760b91eda481f43 (patch) | |
| tree | ced5e17a1a655dea4310626a52272470f7c665f7 /docs/superpowers/specs/2026-04-20-back-to-top-button-design.md | |
| parent | c3a92cd2b9824a56a4047f493fd18de1806f2271 (diff) | |
| download | danixxyz-5b1e212af64a1a7acef75ef24760b91eda481f43.tar.gz danixxyz-5b1e212af64a1a7acef75ef24760b91eda481f43.zip | |
chore: add back-to-top button planning and design docs, update local settings
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Diffstat (limited to 'docs/superpowers/specs/2026-04-20-back-to-top-button-design.md')
| -rw-r--r-- | docs/superpowers/specs/2026-04-20-back-to-top-button-design.md | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-04-20-back-to-top-button-design.md b/docs/superpowers/specs/2026-04-20-back-to-top-button-design.md new file mode 100644 index 0000000..bc5959f --- /dev/null +++ b/docs/superpowers/specs/2026-04-20-back-to-top-button-design.md @@ -0,0 +1,133 @@ +# Back-to-Top Button — Design Spec + +**Date:** 2026-04-20 +**Status:** Approved + +--- + +## Context + +The TODO list includes "add back-to-top button". Article pages can be long and users currently have no quick way to return to the top. The button should integrate cleanly with the existing hacker-aesthetic theme, follow Alpine.js patterns already in use, and meet WCAG 2.1 AA accessibility standards. + +--- + +## Design Decisions + +| Decision | Choice | Rationale | +|---|---|---| +| Position | Fixed bottom-right | Conventional, expected UX | +| Shape | Circle solid | Consistent with theme energy; always-visible purple matches accent | +| Trigger | 33% scroll depth | Balanced — not too early, not too late | +| Animation | Slide up + fade (300ms) | Reuses existing `slideUp` keyframe | +| Pages | `.Kind "page"` only | Articles and singles — same condition as reading progress bar | +| Implementation | Inline Alpine.js in a partial | Consistent with `social-share.html`, `header.html` patterns | + +--- + +## Behaviour + +- **Appears** when `window.scrollY / (document.documentElement.scrollHeight - window.innerHeight) >= 0.33` +- **Disappears** when scroll drops back below 33% +- **On click**: smooth-scrolls to top (`window.scrollTo({ top: 0, behavior: 'smooth' })`) +- **Reduced motion**: CSS global `prefers-reduced-motion` rule already suppresses all animations; the button still appears/disappears (via Alpine `x-show`) but without the slide animation. The `smooth` scroll behavior should also be skipped — handled in the click handler by checking `window.matchMedia('(prefers-reduced-motion: reduce)').matches`. +- **z-index**: `z-40` — sits below toasts (`z-50`), modals (`z-50`), and reading progress bar (`z-9999`). Toasts stack above it naturally. + +--- + +## Visual Spec + +``` +Position: fixed bottom-6 right-6 (24px from edges) +Size: 44×44px circle +Default: bg: var(--accent) #a855f7, glow: box-shadow 0 0 12px rgba(168,85,247,0.4) +Hover: bg: #9333ea (one shade darker), glow: box-shadow 0 0 20px var(--accent) +Icon: Feather Icons `chevron-up` (already loaded globally via CDN) +Entrance: slideUp keyframe, 300ms ease-out +Exit: opacity fade (Alpine x-show transition), 200ms +``` + +--- + +## Accessibility + +- `aria-label="Back to top"` on the button element +- `role="button"` implicit (native `<button>`) +- Keyboard accessible: Tab-focusable, Enter/Space triggers scroll +- Focus ring: `focus-visible` outline using `var(--accent)` (already defined globally in CSS) +- Button is removed from tab order when hidden via Alpine `x-show` (Alpine removes from DOM flow when not visible) +- WCAG 2.1 AA contrast: white `↑` icon on `#a855f7` purple background — passes AA for UI components + +--- + +## Implementation + +### New files +- `themes/danix-xyz-hacker/layouts/partials/back-to-top.html` — the button partial + +### Modified files +- `themes/danix-xyz-hacker/layouts/_default/baseof.html` — include partial + JS, conditionally on `.Kind "page"` +- `themes/danix-xyz-hacker/assets/css/main.css` — add `.back-to-top` component class + +### No new JS file needed +Logic is simple enough for inline Alpine `x-data` — consistent with `social-share.html`. No separate `.js` asset required. + +--- + +## Component Structure + +**`back-to-top.html` partial:** +```html +<div + x-data="{ visible: false }" + @scroll.window="visible = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) >= 0.33" +> + <button + x-show="visible" + x-transition:enter="..." + x-transition:enter-start="..." + x-transition:enter-end="..." + x-transition:leave="..." + x-transition:leave-start="..." + x-transition:leave-end="..." + class="back-to-top" + aria-label="Back to top" + @click="window.scrollTo({ top: 0, behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth' })" + > + <!-- feather chevron-up icon --> + </button> +</div> +``` + +**`.back-to-top` CSS class** (added to `main.css` `@layer components`): +```css +.back-to-top { + @apply fixed bottom-6 right-6 z-40 w-11 h-11 rounded-full flex items-center justify-center; + background: var(--accent); + box-shadow: 0 0 12px rgba(168, 85, 247, 0.4); + transition: background 200ms ease, box-shadow 200ms ease; + color: #fff; +} +.back-to-top:hover { + background: #9333ea; + box-shadow: 0 0 20px var(--accent); +} +.back-to-top:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} +``` + +--- + +## Verification + +1. Start Hugo dev server (`hugo server`) +2. Open any article page +3. Scroll past 33% — button slides up from bottom-right +4. Scroll back up — button disappears +5. Click button — smooth scroll to top +6. Tab to button — focus ring visible +7. Press Enter while focused — smooth scroll to top +8. Test with `prefers-reduced-motion: reduce` in browser devtools — button appears without animation, click scrolls instantly +9. Confirm toasts (if triggered) render above the button +10. Check on mobile (narrow viewport) — button sits at bottom-right without overlapping content |
