summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--data/quotes.yaml27
-rw-r--r--docs/superpowers/specs/2026-04-20-footer-redesign-design.md159
-rw-r--r--i18n/en.yaml2
-rw-r--r--i18n/it.yaml2
-rw-r--r--themes/danix-xyz-hacker/assets/css/main.css15
-rw-r--r--themes/danix-xyz-hacker/assets/css/main.min.css81
-rw-r--r--themes/danix-xyz-hacker/assets/js/fortune.js9
-rw-r--r--themes/danix-xyz-hacker/layouts/partials/footer.html87
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>&copy; {{ 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>