diff options
| -rw-r--r-- | data/quotes.yaml | 27 | ||||
| -rw-r--r-- | docs/superpowers/specs/2026-04-20-footer-redesign-design.md | 159 | ||||
| -rw-r--r-- | i18n/en.yaml | 2 | ||||
| -rw-r--r-- | i18n/it.yaml | 2 | ||||
| -rw-r--r-- | themes/danix-xyz-hacker/assets/css/main.css | 15 | ||||
| -rw-r--r-- | themes/danix-xyz-hacker/assets/css/main.min.css | 81 | ||||
| -rw-r--r-- | themes/danix-xyz-hacker/assets/js/fortune.js | 9 | ||||
| -rw-r--r-- | themes/danix-xyz-hacker/layouts/partials/footer.html | 87 |
8 files changed, 360 insertions, 22 deletions
diff --git a/data/quotes.yaml b/data/quotes.yaml new file mode 100644 index 0000000..c6bebf9 --- /dev/null +++ b/data/quotes.yaml @@ -0,0 +1,27 @@ +quotes: + - text: "The quieter you become, the more you can hear." + author: "Ram Dass" + - text: "In theory, theory and practice are the same. In practice, they are not." + author: "Albert Einstein" + - text: "The best time to plant a tree was 20 years ago. The second best time is now." + author: "Chinese Proverb" + - text: "Debugging is like being the detective in a crime drama." + author: "Filipe Fortes" + - text: "Good design is invisible." + author: "Paul Rand" + - text: "The only way to do great work is to love what you do." + author: "Steve Jobs" + - text: "Simplicity is the ultimate sophistication." + author: "Leonardo da Vinci" + - text: "Security is not a feature, it's a prerequisite." + author: "Bruce Schneier" + - text: "Privacy is not something that I'm merely entitled to, it's an absolute prerequisite." + author: "Marlon Brando" + - text: "The web as I envisaged it, we have not seen it yet." + author: "Tim Berners-Lee" + - text: "First, solve the problem. Then, write the code." + author: "John Johnson" + - text: "Code is read much more often than it is written." + author: "Guido van Rossum" + - text: "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." + author: "Martin Fowler" diff --git a/docs/superpowers/specs/2026-04-20-footer-redesign-design.md b/docs/superpowers/specs/2026-04-20-footer-redesign-design.md new file mode 100644 index 0000000..e917b51 --- /dev/null +++ b/docs/superpowers/specs/2026-04-20-footer-redesign-design.md @@ -0,0 +1,159 @@ +# Footer Redesign — Design Spec +**Date:** 2026-04-20 +**Status:** Approved +**Branch:** week-7-footer (to be created) + +--- + +## Context + +The current footer is a placeholder: 3-column grid with site title, quick links, and email — no personality, no hacker identity. This redesign replaces it with a footer that reflects the site owner's professional identity, tech stack, and philosophy, while adding a "Fortune Cookie" feature that displays a random quote on every page load. + +--- + +## Layout + +3 equal columns, full-width copyright bar underneath. Stacks to 1 column on mobile. No column section headers. + +``` +┌─────────────────────────────────────────────────┐ +│ $ fortune │ role: … │ built with │ +│ danix │ cert: … │ [Hugo][Tailwind] │ +│ │ os: … │ [Alpine][HTML5] │ +│ "quote…" │ focus: … │ [CSS3][JS] │ +│ — Author │ │ │ +│ │ │ features │ +│ │ │ [WCAG 2.1 AA] │ +│ │ │ [Open Source] │ +│ │ │ [Privacy Friendly]│ +│ │ │ [Claude Code] │ +├─────────────────────────────────────────────────┤ +│ Made with ❤️ lack of 😴 lots of ☕ by danix │ +│ © 2026 Danilo M. · All rights reserved │ +└─────────────────────────────────────────────────┘ +``` + +Mobile: all columns stack vertically, copyright bar unchanged. + +--- + +## Column 1 — Fortune Cookie + +No section header. + +**Subline:** `$ fortune danix` (dimmed, monospace — looks like a shell command) +**Quote text:** italic, `--text` color, `font-mono` +**Attribution:** `— Author Name`, `--text-dim` color + +**Data source:** `data/quotes.yaml` +**Format:** +```yaml +quotes: + - text: "The quieter you become, the more you can hear." + author: "Ram Dass" + - text: "In theory, theory and practice are the same. In practice, they are not." + author: "Albert Einstein" +``` + +**Rendering:** Hugo renders all quotes into a JSON array in the template. A small vanilla JS snippet (no Alpine needed) picks one at random on each page load and injects it into the DOM. The `<blockquote>` element is pre-rendered in Hugo with the first quote as a no-JS fallback. + +**A11y:** `<blockquote>` with `<cite>` for attribution. `aria-live="polite"` on the quote container so screen readers announce the randomly selected quote after JS fires. + +--- + +## Column 2 — About (Terminal Readout) + +No section header. + +**Style:** key-value table, monospace font + +| Key | Value | Color | +|-----|-------|-------| +| `role:` | Cybersecurity Specialist | `--text` | +| `cert:` | eJPT | `--accent2` (green — highlights credential) | +| `os:` | Slackware (2005–present) | `--text`, year in `--text-dim` | +| `focus:` | open-source · privacy | `--text` | + +Keys use `--text-dim`. Layout: `<dl>` with `<dt>`/`<dd>` pairs for semantic correctness and screen reader support. + +--- + +## Column 3 — Stack & Feature Badges + +No section header. Two sub-groups with small dimmed labels above each group. + +**built with** (purple badges — `--accent`): +Hugo · Tailwind CSS · Alpine.js · HTML5 · CSS3 · JavaScript + +**features** (green badges — `--accent2`): +WCAG 2.1 AA · Open Source · Privacy Friendly · Claude Code + +**Badge style:** new `.badge-footer-accent` and `.badge-footer-accent2` CSS variants — same shape as existing `.badge` component but using `--accent` / `--accent2` colors. `aria-hidden="true"` on purely decorative badges; meaningful ones (WCAG 2.1 AA) get descriptive text. + +--- + +## Copyright Bar + +Full-width, centered, below a `border-t` separator. + +**Line 1:** `Made with ❤️ lack of 😴 lots of ☕ by danix` +- `danix` links to the language-appropriate About page: + - English: `/is/` + - Italian: `/it/is/` + - Resolved dynamically via Hugo: `{{ .Site.LanguagePrefix }}/is/` + - Styled in `--accent`, hover to `--accent2` +- Emoji are wrapped in `<span aria-hidden="true">` with adjacent screen-reader text via `<span class="sr-only">` + +**Line 2:** `© 2026 Danilo M. · All rights reserved` +- Year rendered with Hugo's `{{ now.Year }}` +- Uses existing `{{ i18n "allRightsReserved" }}` key + +--- + +## Theming Standard Compliance + +- All colors via CSS custom properties: `var(--accent)`, `var(--accent2)`, `var(--text)`, `var(--text-dim)` +- No hard-coded hex values in template or new CSS +- Fonts: `font-mono` (JetBrains Mono) for all terminal-style text +- No section headers — cleaner, less noise +- Frosted-bar styling (`frosted-bar border-t`) inherited from existing footer wrapper — no change needed +- Mobile-first: `grid-cols-1` base, `md:grid-cols-3` at 768px+ +- Spacing: gap-8 between columns (2rem), consistent with existing grid patterns + +--- + +## Accessibility Requirements + +- **Semantic HTML:** `<footer>`, `<blockquote>`, `<cite>`, `<dl>`/`<dt>`/`<dd>` +- **Screen readers:** `aria-live="polite"` on fortune quote container; `aria-hidden="true"` on decorative emoji with `sr-only` text equivalents +- **Color contrast:** All text on `--bg2` background must meet ≥4.5:1 (verified: `--text` 12.3:1, `--text-dim` 5.2:1, `--accent` 5.1:1, `--accent2` 7.2:1) +- **Keyboard:** Only interactive element is the `danix` About link — standard focus ring applies +- **Motion:** No animations — no `prefers-reduced-motion` concern +- **Touch targets:** The `danix` link in copyright bar must be ≥44×44px tap target (padding applied) + +--- + +## Files to Create / Modify + +| File | Action | +|------|--------| +| `themes/danix-xyz-hacker/layouts/partials/footer.html` | Replace entirely | +| `themes/danix-xyz-hacker/assets/css/main.css` | Add `.badge-footer-accent`, `.badge-footer-accent2` component classes | +| `data/quotes.yaml` | Create — initial quote set (10–15 quotes) | +| `themes/danix-xyz-hacker/assets/js/fortune.js` | Create — tiny vanilla JS random quote picker | +| `i18n/en.yaml` | Add `footer_built_with`, `footer_features` label keys | +| `i18n/it.yaml` | Add same keys in Italian | + +--- + +## Verification + +1. Run `hugo server` — footer renders on all page types (home, single, list) +2. Reload page 5+ times — quote changes each reload +3. Disable JS — first quote from YAML shows as static fallback +4. Toggle dark/light theme — all colors switch correctly via CSS vars +5. Resize to 375px — columns stack to single column, no overflow +6. Tab through footer — only the `danix` About link is focusable, focus ring visible +7. Check `danix` link: English site → `/is/`, Italian site → `/it/is/` +8. Run screen reader — quote announced after page load, emoji skipped, `dl` keys/values read correctly +9. Run `npm run build` — CSS compiles without errors diff --git a/i18n/en.yaml b/i18n/en.yaml index 5c35568..874c0e6 100644 --- a/i18n/en.yaml +++ b/i18n/en.yaml @@ -14,6 +14,8 @@ email: "Email" contact: "Contact" links: "Links" allRightsReserved: "All rights reserved." +footer_built_with: "built with" +footer_features: "features" # Articles readMore: "Read more" diff --git a/i18n/it.yaml b/i18n/it.yaml index 4d228be..8c3eba1 100644 --- a/i18n/it.yaml +++ b/i18n/it.yaml @@ -14,6 +14,8 @@ email: "Email" contact: "Contatti" links: "Link" allRightsReserved: "Tutti i diritti riservati." +footer_built_with: "costruito con" +footer_features: "caratteristiche" # Articles readMore: "Continua a leggere" diff --git a/themes/danix-xyz-hacker/assets/css/main.css b/themes/danix-xyz-hacker/assets/css/main.css index 298c38b..163505f 100644 --- a/themes/danix-xyz-hacker/assets/css/main.css +++ b/themes/danix-xyz-hacker/assets/css/main.css @@ -596,6 +596,21 @@ html.theme-light picture img[src="/images/default_thumbnail_dark.png"] { .article-nav-placeholder { @apply text-text-dim opacity-40; } + + /* ---- Footer badge variants ---- */ + .badge-footer-accent { + @apply inline-flex items-center px-2.5 py-1 rounded text-xs font-mono font-semibold whitespace-nowrap; + border: 1px solid rgba(168, 85, 247, 0.35); + background: rgba(168, 85, 247, 0.1); + color: var(--accent); + } + + .badge-footer-accent2 { + @apply inline-flex items-center px-2.5 py-1 rounded text-xs font-mono font-semibold whitespace-nowrap; + border: 1px solid rgba(0, 255, 136, 0.35); + background: rgba(0, 255, 136, 0.1); + color: var(--accent2); + } } /* Prose overrides for light theme */ diff --git a/themes/danix-xyz-hacker/assets/css/main.min.css b/themes/danix-xyz-hacker/assets/css/main.min.css index 942d401..4e6e482 100644 --- a/themes/danix-xyz-hacker/assets/css/main.min.css +++ b/themes/danix-xyz-hacker/assets/css/main.min.css @@ -1284,6 +1284,10 @@ button, color: var(--accent); } +.text-accent2 { + color: var(--accent2); +} + .text-text { color: var(--text); } @@ -1682,6 +1686,44 @@ article.border.border-border\/30.rounded-lg.card.group.bg-bg { opacity: 0.4; } +/* ---- Footer badge variants ---- */ + +.badge-footer-accent { + display: inline-flex; + align-items: center; + white-space: nowrap; + border-radius: 0.25rem; + padding-left: 0.625rem; + padding-right: 0.625rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + font-family: JetBrains Mono, monospace; + font-size: 0.75rem; + line-height: 1rem; + font-weight: 600; + border: 1px solid rgba(168, 85, 247, 0.35); + background: rgba(168, 85, 247, 0.1); + color: var(--accent); +} + +.badge-footer-accent2 { + display: inline-flex; + align-items: center; + white-space: nowrap; + border-radius: 0.25rem; + padding-left: 0.625rem; + padding-right: 0.625rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + font-family: JetBrains Mono, monospace; + font-size: 0.75rem; + line-height: 1rem; + font-weight: 600; + border: 1px solid rgba(0, 255, 136, 0.35); + background: rgba(0, 255, 136, 0.1); + color: var(--accent2); +} + .sr-only { position: absolute; width: 1px; @@ -1807,6 +1849,10 @@ article.border.border-border\/30.rounded-lg.card.group.bg-bg { margin-bottom: 2rem; } +.mb-1 { + margin-bottom: 0.25rem; +} + .mb-12 { margin-bottom: 3rem; } @@ -1957,6 +2003,10 @@ article.border.border-border\/30.rounded-lg.card.group.bg-bg { width: 0.25rem; } +.w-20 { + width: 5rem; +} + .w-32 { width: 8rem; } @@ -2013,6 +2063,10 @@ article.border.border-border\/30.rounded-lg.card.group.bg-bg { flex-shrink: 0; } +.shrink-0 { + flex-shrink: 0; +} + .translate-x-0 { --tw-translate-x: 0px; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -2067,6 +2121,10 @@ article.border.border-border\/30.rounded-lg.card.group.bg-bg { gap: 0.25rem; } +.gap-1\.5 { + gap: 0.375rem; +} + .gap-2 { gap: 0.5rem; } @@ -2087,6 +2145,12 @@ article.border.border-border\/30.rounded-lg.card.group.bg-bg { gap: 2rem; } +.space-y-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); +} + .space-y-2 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); @@ -2229,6 +2293,11 @@ article.border.border-border\/30.rounded-lg.card.group.bg-bg { padding: 2rem; } +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; @@ -2401,6 +2470,10 @@ article.border.border-border\/30.rounded-lg.card.group.bg-bg { color: var(--accent); } +.text-accent2 { + color: var(--accent2); +} + .text-bg { color: var(--bg); } @@ -3650,6 +3723,10 @@ article.toast.border-border\/30.rounded-lg.overflow-hidden.group.bg-bg { color: var(--accent); } +.hover\:text-accent2:hover { + color: var(--accent2); +} + .hover\:text-text:hover { color: var(--text); } @@ -3698,6 +3775,10 @@ article.toast.border-border\/30.rounded-lg.overflow-hidden.group.bg-bg { color: var(--accent); } +.hover\:text-accent2:hover { + color: var(--accent2); +} + .hover\:text-text:hover { color: var(--text); } diff --git a/themes/danix-xyz-hacker/assets/js/fortune.js b/themes/danix-xyz-hacker/assets/js/fortune.js new file mode 100644 index 0000000..d4f981b --- /dev/null +++ b/themes/danix-xyz-hacker/assets/js/fortune.js @@ -0,0 +1,9 @@ +(function() { + const el = document.getElementById('fortune-quote'); + if (!el) return; + const quotes = JSON.parse(el.dataset.quotes); + if (!quotes || quotes.length === 0) return; + const q = quotes[Math.floor(Math.random() * quotes.length)]; + el.querySelector('.fortune-text').textContent = '"' + q.text + '"'; + el.querySelector('.fortune-author').textContent = '— ' + q.author; +})(); diff --git a/themes/danix-xyz-hacker/layouts/partials/footer.html b/themes/danix-xyz-hacker/layouts/partials/footer.html index c649e84..006714b 100644 --- a/themes/danix-xyz-hacker/layouts/partials/footer.html +++ b/themes/danix-xyz-hacker/layouts/partials/footer.html @@ -1,38 +1,81 @@ +{{- $quotes := .Site.Data.quotes.quotes -}} + <footer class="mt-16 frosted-bar border-t py-12 relative z-20"> <div class="container mx-auto px-4"> - <div class="grid md:grid-cols-3 gap-8 mb-8"> - <!-- About --> + <div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8"> + + <!-- Column 1: Fortune Cookie --> <div> - <h3 class="font-bold text-accent mb-3 font-oxanium">{{ .Site.Title }}</h3> - <p class="text-sm text-text-dim">{{ .Site.Params.siteDescription }}</p> + <p class="font-mono text-xs text-text-dim mb-2">$ fortune danix</p> + <div id="fortune-quote" aria-live="polite" data-quotes='{{ $quotes | jsonify }}'> + <blockquote> + <p class="fortune-text font-mono text-sm text-text italic leading-relaxed"> + "{{ (index $quotes 0).text }}" + </p> + <cite class="fortune-author font-mono text-xs text-text-dim not-italic mt-2 block"> + — {{ (index $quotes 0).author }} + </cite> + </blockquote> + </div> </div> - <!-- Quick links --> + <!-- Column 2: About (Terminal Readout) --> <div> - <h4 class="font-semibold text-accent mb-3">{{ i18n "links" }}</h4> - <ul class="space-y-2"> - {{ range .Site.Menus.main }} - <li> - <a href="{{ .URL }}" class="text-sm text-text-dim hover:text-accent transition-colors"> - {{ i18n .Name }} - </a> - </li> - {{ end }} - </ul> + <dl class="space-y-1"> + <div class="flex gap-2"> + <dt class="text-text-dim font-mono text-xs w-20 shrink-0">role:</dt> + <dd class="text-text font-mono text-xs">Cybersecurity Specialist</dd> + </div> + <div class="flex gap-2"> + <dt class="text-text-dim font-mono text-xs w-20 shrink-0">cert:</dt> + <dd class="text-accent2 font-mono text-xs font-semibold">eJPT</dd> + </div> + <div class="flex gap-2"> + <dt class="text-text-dim font-mono text-xs w-20 shrink-0">os:</dt> + <dd class="text-text font-mono text-xs">Slackware <span class="text-text-dim">(2005–present)</span></dd> + </div> + <div class="flex gap-2"> + <dt class="text-text-dim font-mono text-xs w-20 shrink-0">focus:</dt> + <dd class="text-text font-mono text-xs">open-source · privacy</dd> + </div> + </dl> </div> - <!-- Social (if configured) --> + <!-- Column 3: Stack & Feature Badges --> <div> - <h4 class="font-semibold text-accent mb-3">{{ i18n "contact" }}</h4> - <a href="mailto:{{ .Site.Params.email }}" class="text-sm text-text-dim hover:text-accent transition-colors"> - {{ i18n "email" }}: {{ .Site.Params.email }} - </a> + <p class="text-text-dim font-mono text-xs mb-1">{{ i18n "footer_built_with" }}</p> + <div class="flex flex-wrap gap-1.5 mb-3"> + <span class="badge-footer-accent">Hugo</span> + <span class="badge-footer-accent">Tailwind CSS</span> + <span class="badge-footer-accent">Alpine.js</span> + <span class="badge-footer-accent">HTML5</span> + <span class="badge-footer-accent">CSS3</span> + <span class="badge-footer-accent">JavaScript</span> + </div> + + <p class="text-text-dim font-mono text-xs mb-1">{{ i18n "footer_features" }}</p> + <div class="flex flex-wrap gap-1.5"> + <span class="badge-footer-accent2">WCAG 2.1 AA</span> + <span class="badge-footer-accent2">Open Source</span> + <span class="badge-footer-accent2">Privacy Friendly</span> + <span class="badge-footer-accent2">Claude Code</span> + </div> </div> </div> - <!-- Copyright --> - <div class="pt-8 border-t border-border text-center text-xs text-text-dim"> + <!-- Copyright Bar --> + <div class="pt-8 border-t border-border text-center text-xs text-text-dim space-y-1"> + <p> + Made with <span aria-hidden="true">❤️</span><span class="sr-only">love</span>, + lack of <span aria-hidden="true">😴</span><span class="sr-only">sleep</span>, + lots of <span aria-hidden="true">☕</span><span class="sr-only">coffee</span> + by <a href="{{ .Site.LanguagePrefix }}/is/" class="text-accent hover:text-accent2 transition-colors py-2 px-1">danix</a> + </p> <p>© {{ now.Year }} {{ .Site.Params.author }}. {{ i18n "allRightsReserved" }}</p> </div> </div> + + <!-- Fortune.js: Pick a random quote on each page load --> + {{- $fortuneJS := resources.Get "js/fortune.js" | minify -}} + <script src="{{ $fortuneJS.RelPermalink }}"></script> </footer> |
