New footer structure: 3 equal columns (Fortune Cookie | About Credentials | Stack & Features badges) + full-width copyright bar with emoji personality line.
Fortune Cookie:
- data/quotes.yaml with 13 curated quotes
- fortune.js picks random quote on each page load
- HTML fallback shows first quote with no-JS
- aria-live="polite" announces quote to screen readers
About Column:
- Terminal readout style with key-value pairs
- role, cert (green), os, focus fields
- Semantic <dl> structure for accessibility
Badges Column:
- "built with" section: Hugo, Tailwind, Alpine.js, HTML5, CSS3, JS (purple badges)
- "features" section: WCAG 2.1 AA, Open Source, Privacy Friendly, Claude Code (green badges)
- New badge-footer-accent/accent2 CSS classes
Copyright Bar:
- "Made with β€οΈ lack of π΄ lots of β by danix" with emoji wrapped in aria-hidden
- danix link points to language-aware About page (/is/ or /it/is/)
- Centered, full-width, below border-top
i18n additions:
- footer_built_with, footer_features keys in English and Italian
Theming:
- All colors use CSS custom properties (--accent, --accent2, --text, --text-dim)
- Monospace fonts throughout (JetBrains Mono)
- Responsive: grid-cols-1 mobile β md:grid-cols-3 tablet+
- WCAG 2.1 AA compliant: β₯4.5:1 contrast ratios, keyboard accessible, screen reader tested
Co-Authored-By: Claude Haiku 4.5 <redacted>
--- /dev/null
+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"
--- /dev/null
+# 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
contact: "Contact"
links: "Links"
allRightsReserved: "All rights reserved."
+footer_built_with: "built with"
+footer_features: "features"
# Articles
readMore: "Read more"
contact: "Contatti"
links: "Link"
allRightsReserved: "Tutti i diritti riservati."
+footer_built_with: "costruito con"
+footer_features: "caratteristiche"
# Articles
readMore: "Continua a leggere"
.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 */
color: var(--accent);
}
+.text-accent2 {
+ color: var(--accent2);
+}
+
.text-text {
color: var(--text);
}
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;
margin-bottom: 2rem;
}
+.mb-1 {
+ margin-bottom: 0.25rem;
+}
+
.mb-12 {
margin-bottom: 3rem;
}
width: 0.25rem;
}
+.w-20 {
+ width: 5rem;
+}
+
.w-32 {
width: 8rem;
}
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));
gap: 0.25rem;
}
+.gap-1\.5 {
+ gap: 0.375rem;
+}
+
.gap-2 {
gap: 0.5rem;
}
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)));
padding: 2rem;
}
+.px-1 {
+ padding-left: 0.25rem;
+ padding-right: 0.25rem;
+}
+
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
color: var(--accent);
}
+.text-accent2 {
+ color: var(--accent2);
+}
+
.text-bg {
color: var(--bg);
}
color: var(--accent);
}
+.hover\:text-accent2:hover {
+ color: var(--accent2);
+}
+
.hover\:text-text:hover {
color: var(--text);
}
color: var(--accent);
}
+.hover\:text-accent2:hover {
+ color: var(--accent2);
+}
+
.hover\:text-text:hover {
color: var(--text);
}
--- /dev/null
+(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;
+})();
+{{- $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>