summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-04-15 21:42:04 +0200
committerDanilo M. <danix@danix.xyz>2026-04-15 21:42:04 +0200
commit9934e042c1e97765ac839f6c3f06d30f02b5d9d0 (patch)
treecf102ee79caf5d304e303956a1becd5fb680ca39
parenteb8a17482fc138c861db685f9efc86871bf2d7d2 (diff)
downloaddanixxyz-9934e042c1e97765ac839f6c3f06d30f02b5d9d0.tar.gz
danixxyz-9934e042c1e97765ac839f6c3f06d30f02b5d9d0.zip
Fix design system compliance: CSS variables, accessibility, and theme system
Core CSS improvements: - Add --surface-rgb, --type-* (tech, life, quote, link, photo) custom properties - Add --type-* semantic color classes for article badges - Convert article badges from inline styles to CSS variable system - Add prefers-color-scheme light fallback for no-JS users - Add prefers-reduced-motion support to respect user accessibility settings - Replace *:focus with *:focus-visible (keyboard-only outlines) - Add clamp() fluid typography for hero-title and section-title - Refactor container rules to mobile-first with 1060px breakpoint Theme & Icon fixes: - Fix theme toggle icon display with Alpine.js (was broken with Tailwind dark: classes) - Add aria-hidden="true" to icon elements - Update header with proper ARIA attributes on menu toggle Accessibility enhancements: - Add skip-to-main-content link in baseof.html - Update hamburger menu with aria-expanded, aria-controls, aria-hidden - Implement focus trap (Tab loops) within mobile menu - Return focus to trigger button on menu close - Add menu open/close state management with proper ARIA Semantic HTML: - Wrap article pages in <article> element (articles/single.html, _default/single.html) - Fix quote article to use --type-quote border color instead of generic accent Image optimization: - Add loading="lazy" to profile image in index.html - Add loading="lazy" to featured image in photo.html Template fixes: - Remove broken os.Getenv "THEME" runtime check from article-list-item.html - Replace inline color styles with semantic .type-* classes - Add 1060px lg: breakpoint to tailwind.config.js i18n updates: - Add skipToContent translations (en, it) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
-rw-r--r--i18n/en.yaml1
-rw-r--r--i18n/it.yaml1
-rw-r--r--tailwind.config.js3
-rw-r--r--themes/danix-xyz-hacker/assets/css/main.css106
-rw-r--r--themes/danix-xyz-hacker/assets/css/main.min.css165
-rw-r--r--themes/danix-xyz-hacker/assets/js/menu.js82
-rw-r--r--themes/danix-xyz-hacker/layouts/_default/baseof.html5
-rw-r--r--themes/danix-xyz-hacker/layouts/_default/single.html4
-rw-r--r--themes/danix-xyz-hacker/layouts/articles/single.html4
-rw-r--r--themes/danix-xyz-hacker/layouts/index.html1
-rw-r--r--themes/danix-xyz-hacker/layouts/partials/article-header.html4
-rw-r--r--themes/danix-xyz-hacker/layouts/partials/article-list-item.html5
-rw-r--r--themes/danix-xyz-hacker/layouts/partials/article-types/photo.html1
-rw-r--r--themes/danix-xyz-hacker/layouts/partials/article-types/quote.html2
-rw-r--r--themes/danix-xyz-hacker/layouts/partials/hamburger-menu.html3
-rw-r--r--themes/danix-xyz-hacker/layouts/partials/header.html8
16 files changed, 329 insertions, 66 deletions
diff --git a/i18n/en.yaml b/i18n/en.yaml
index cd061c0..2bcf579 100644
--- a/i18n/en.yaml
+++ b/i18n/en.yaml
@@ -7,6 +7,7 @@ language: "Language"
toggleTheme: "Theme"
toggleMenu: "Menu"
closeMenu: "Close"
+skipToContent: "Skip to main content"
email: "Email"
contact: "Contact"
links: "Links"
diff --git a/i18n/it.yaml b/i18n/it.yaml
index 92406d9..e22c0c5 100644
--- a/i18n/it.yaml
+++ b/i18n/it.yaml
@@ -7,6 +7,7 @@ language: "Lingua"
toggleTheme: "Tema"
toggleMenu: "Menu"
closeMenu: "Chiudi"
+skipToContent: "Salta al contenuto principale"
email: "Email"
contact: "Contatti"
links: "Link"
diff --git a/tailwind.config.js b/tailwind.config.js
index d36b0a8..978bba7 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -7,6 +7,9 @@ module.exports = {
],
theme: {
extend: {
+ screens: {
+ 'lg': '1060px',
+ },
colors: {
bg: 'var(--bg)',
'bg2': 'var(--bg2)',
diff --git a/themes/danix-xyz-hacker/assets/css/main.css b/themes/danix-xyz-hacker/assets/css/main.css
index c66092b..2b8cbde 100644
--- a/themes/danix-xyz-hacker/assets/css/main.css
+++ b/themes/danix-xyz-hacker/assets/css/main.css
@@ -7,6 +7,7 @@
--bg: #060b10;
--bg2: #0c1520;
--surface: #101e2d;
+ --surface-rgb: 16, 30, 45;
--border: #182840;
--accent: #a855f7;
--accent2: #00ff88;
@@ -14,6 +15,12 @@
--text: #c4d6e8;
--text-dim: #7a9bb8;
--muted: #304860;
+ /* Article type colors - dark */
+ --type-tech: #a855f7;
+ --type-life: #f59e0b;
+ --type-quote: #00ff88;
+ --type-link: #38bdf8;
+ --type-photo: #ec4899;
}
/* Light theme overrides */
@@ -21,6 +28,7 @@ html.theme-light {
--bg: #ffffff;
--bg2: #f8f9fa;
--surface: #f0f3f7;
+ --surface-rgb: 240, 243, 247;
--border: #d9dfe8;
--accent: #9333ea;
--accent2: #10b981;
@@ -28,6 +36,34 @@ html.theme-light {
--text: #1f2937;
--text-dim: #374151;
--muted: #d1d5db;
+ /* Article type colors - light */
+ --type-tech: #7c3aed;
+ --type-life: #d97706;
+ --type-quote: #008f5a;
+ --type-link: #0284c7;
+ --type-photo: #be185d;
+}
+
+/* No-JS fallback: prefers-color-scheme light */
+@media (prefers-color-scheme: light) {
+ html:not(.theme-dark) {
+ --bg: #ffffff;
+ --bg2: #f8f9fa;
+ --surface: #f0f3f7;
+ --surface-rgb: 240, 243, 247;
+ --border: #d9dfe8;
+ --accent: #9333ea;
+ --accent2: #10b981;
+ --accent-glow: rgba(147, 51, 234, 0.1);
+ --text: #1f2937;
+ --text-dim: #374151;
+ --muted: #d1d5db;
+ --type-tech: #7c3aed;
+ --type-life: #d97706;
+ --type-quote: #008f5a;
+ --type-link: #0284c7;
+ --type-photo: #be185d;
+ }
}
@layer base {
@@ -66,19 +102,15 @@ html.theme-light {
}
pre {
- background-color: rgba(16, 30, 45, 0.8);
+ background-color: rgba(var(--surface-rgb), 0.8);
@apply p-4 rounded border border-border overflow-x-auto;
}
- html.theme-light pre {
- background-color: rgba(240, 243, 247, 0.8);
- }
-
pre code {
@apply bg-transparent border-0 p-0 text-text;
}
- *:focus {
+ *:focus-visible {
@apply ring-2 ring-accent ring-offset-2;
ring-offset-color: var(--bg);
}
@@ -144,6 +176,41 @@ html.theme-light {
.glow-accent {
box-shadow: 0 0 20px var(--accent-glow);
}
+
+ /* Article type badge styles */
+ .type-tech {
+ color: var(--type-tech);
+ background-color: rgba(168, 85, 247, 0.1);
+ }
+
+ .type-life {
+ color: var(--type-life);
+ background-color: rgba(245, 158, 11, 0.1);
+ }
+
+ .type-quote {
+ color: var(--type-quote);
+ background-color: rgba(0, 255, 136, 0.1);
+ }
+
+ .type-link {
+ color: var(--type-link);
+ background-color: rgba(56, 189, 248, 0.1);
+ }
+
+ .type-photo {
+ color: var(--type-photo);
+ background-color: rgba(236, 72, 153, 0.1);
+ }
+
+ /* Hero typography with fluid sizing */
+ .hero-title {
+ font-size: clamp(2rem, 5vw + 1rem, 4.5rem);
+ }
+
+ .section-title {
+ font-size: clamp(1.5rem, 3vw + 0.5rem, 2.5rem);
+ }
}
/* Prose overrides for light theme */
@@ -194,15 +261,28 @@ html.theme-light .prose-invert blockquote {
border-left-color: var(--accent);
}
-/* Responsive utilities */
-@media (max-width: 768px) {
- .sm\:container {
- @apply max-w-full px-4;
+/* Responsive container utilities - mobile-first */
+.container {
+ @apply max-w-full px-4;
+}
+
+@media (min-width: 768px) {
+ .container {
+ @apply max-w-4xl px-6;
+ }
+}
+
+@media (min-width: 1060px) {
+ .container {
+ @apply max-w-5xl px-8;
}
}
-@media (min-width: 769px) {
- .md\:container {
- @apply max-w-4xl;
+/* Respect user's motion preferences */
+@media (prefers-reduced-motion: reduce) {
+ *, *::before, *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
}
}
diff --git a/themes/danix-xyz-hacker/assets/css/main.min.css b/themes/danix-xyz-hacker/assets/css/main.min.css
index 3ab93b7..858df86 100644
--- a/themes/danix-xyz-hacker/assets/css/main.min.css
+++ b/themes/danix-xyz-hacker/assets/css/main.min.css
@@ -631,7 +631,7 @@ code {
}
pre {
- background-color: rgba(16, 30, 45, 0.8);
+ background-color: rgba(var(--surface-rgb), 0.8);
overflow-x: auto;
border-radius: 0.25rem;
border-width: 1px;
@@ -639,10 +639,6 @@ pre {
padding: 1rem;
}
-html.theme-light pre {
- background-color: rgba(240, 243, 247, 0.8);
-}
-
pre code {
border-width: 0px;
background-color: transparent;
@@ -650,7 +646,7 @@ pre code {
color: var(--text);
}
-*:focus {
+*:focus-visible {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
@@ -684,9 +680,9 @@ button,
}
}
-@media (min-width: 1024px) {
+@media (min-width: 1060px) {
.container {
- max-width: 1024px;
+ max-width: 1060px;
}
}
@@ -1268,6 +1264,22 @@ button,
/* Glow effect utility */
+/* Article type badge styles */
+
+/* Hero typography with fluid sizing */
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border-width: 0;
+}
+
.pointer-events-none {
pointer-events: none;
}
@@ -1556,6 +1568,10 @@ button,
border-top-width: 1px;
}
+.border-\[--type-quote\] {
+ border-color: var(--type-quote);
+}
+
.border-accent {
border-color: var(--accent);
}
@@ -1812,6 +1828,7 @@ button,
--bg: #060b10;
--bg2: #0c1520;
--surface: #101e2d;
+ --surface-rgb: 16, 30, 45;
--border: #182840;
--accent: #a855f7;
--accent2: #00ff88;
@@ -1819,6 +1836,12 @@ button,
--text: #c4d6e8;
--text-dim: #7a9bb8;
--muted: #304860;
+ /* Article type colors - dark */
+ --type-tech: #a855f7;
+ --type-life: #f59e0b;
+ --type-quote: #00ff88;
+ --type-link: #38bdf8;
+ --type-photo: #ec4899;
}
/* Light theme overrides */
@@ -1827,6 +1850,7 @@ html.theme-light {
--bg: #ffffff;
--bg2: #f8f9fa;
--surface: #f0f3f7;
+ --surface-rgb: 240, 243, 247;
--border: #d9dfe8;
--accent: #9333ea;
--accent2: #10b981;
@@ -1834,6 +1858,35 @@ html.theme-light {
--text: #1f2937;
--text-dim: #374151;
--muted: #d1d5db;
+ /* Article type colors - light */
+ --type-tech: #7c3aed;
+ --type-life: #d97706;
+ --type-quote: #008f5a;
+ --type-link: #0284c7;
+ --type-photo: #be185d;
+}
+
+/* No-JS fallback: prefers-color-scheme light */
+
+@media (prefers-color-scheme: light) {
+ html:not(.theme-dark) {
+ --bg: #ffffff;
+ --bg2: #f8f9fa;
+ --surface: #f0f3f7;
+ --surface-rgb: 240, 243, 247;
+ --border: #d9dfe8;
+ --accent: #9333ea;
+ --accent2: #10b981;
+ --accent-glow: rgba(147, 51, 234, 0.1);
+ --text: #1f2937;
+ --text-dim: #374151;
+ --muted: #d1d5db;
+ --type-tech: #7c3aed;
+ --type-life: #d97706;
+ --type-quote: #008f5a;
+ --type-link: #0284c7;
+ --type-photo: #be185d;
+ }
}
/* Prose overrides for light theme */
@@ -1885,19 +1938,37 @@ html.theme-light .prose-invert blockquote {
border-left-color: var(--accent);
}
-/* Responsive utilities */
+/* Responsive container utilities - mobile-first */
-@media (max-width: 768px) {
- .sm\:container {
- max-width: 100%;
- padding-left: 1rem;
- padding-right: 1rem;
- }
+.container {
+ max-width: 100%;
+ padding-left: 1rem;
+ padding-right: 1rem;
}
-@media (min-width: 769px) {
- .md\:container {
+@media (min-width: 768px) {
+ .container {
max-width: 56rem;
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ }
+}
+
+@media (min-width: 1060px) {
+ .container {
+ max-width: 64rem;
+ padding-left: 2rem;
+ padding-right: 2rem;
+ }
+}
+
+/* Respect user's motion preferences */
+
+@media (prefers-reduced-motion: reduce) {
+ *, *::before, *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
}
}
@@ -1929,10 +2000,60 @@ html.theme-light .prose-invert blockquote {
opacity: 0.9;
}
+.focus\:not-sr-only:focus {
+ position: static;
+ width: auto;
+ height: auto;
+ padding: 0;
+ margin: 0;
+ overflow: visible;
+ clip: auto;
+ white-space: normal;
+}
+
+.focus\:fixed:focus {
+ position: fixed;
+}
+
+.focus\:left-4:focus {
+ left: 1rem;
+}
+
+.focus\:top-4:focus {
+ top: 1rem;
+}
+
+.focus\:z-50:focus {
+ z-index: 50;
+}
+
+.focus\:rounded:focus {
+ border-radius: 0.25rem;
+}
+
.focus\:border-accent:focus {
border-color: var(--accent);
}
+.focus\:bg-accent:focus {
+ background-color: var(--accent);
+}
+
+.focus\:px-4:focus {
+ padding-left: 1rem;
+ padding-right: 1rem;
+}
+
+.focus\:py-2:focus {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+.focus\:text-white:focus {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
+}
+
.focus\:outline-none:focus {
outline: 2px solid transparent;
outline-offset: 2px;
@@ -2016,13 +2137,3 @@ html.theme-light .prose-invert blockquote {
line-height: 1;
}
}
-
-@media (prefers-color-scheme: dark) {
- .dark\:block {
- display: block;
- }
-
- .dark\:hidden {
- display: none;
- }
-}
diff --git a/themes/danix-xyz-hacker/assets/js/menu.js b/themes/danix-xyz-hacker/assets/js/menu.js
index f61e60b..3f32642 100644
--- a/themes/danix-xyz-hacker/assets/js/menu.js
+++ b/themes/danix-xyz-hacker/assets/js/menu.js
@@ -1,26 +1,59 @@
document.addEventListener('DOMContentLoaded', () => {
const menuToggle = document.getElementById('menu-toggle');
const menuOverlay = document.getElementById('menu-overlay');
- const menuPanel = document.getElementById('menu-panel');
+ const menuPanel = document.getElementById('hamburger-menu');
- function toggleMenu() {
+ function openMenu() {
if (!menuOverlay || !menuPanel) return;
- // Toggle overlay visibility classes
- menuOverlay.classList.toggle('opacity-0');
- menuOverlay.classList.toggle('invisible');
+ // Show overlay
+ menuOverlay.classList.remove('opacity-0');
+ menuOverlay.classList.remove('invisible');
+
+ // Slide menu in
+ menuPanel.classList.remove('translate-x-full');
- // Toggle menu panel translation
- menuPanel.classList.toggle('translate-x-full');
+ // Manage accessibility
+ menuToggle.setAttribute('aria-expanded', 'true');
+ menuPanel.removeAttribute('aria-hidden');
// Control body overflow
- const isMenuOpen = !menuOverlay.classList.contains('opacity-0');
- document.body.style.overflow = isMenuOpen ? 'hidden' : '';
+ document.body.style.overflow = 'hidden';
+
+ // Focus first focusable element in menu
+ const firstFocusable = menuPanel.querySelector('a, button');
+ if (firstFocusable) {
+ setTimeout(() => firstFocusable.focus(), 50);
+ }
}
function closeMenu() {
if (!menuOverlay || menuOverlay.classList.contains('opacity-0')) return;
- toggleMenu();
+
+ // Hide overlay
+ menuOverlay.classList.add('opacity-0');
+ menuOverlay.classList.add('invisible');
+
+ // Slide menu out
+ menuPanel.classList.add('translate-x-full');
+
+ // Manage accessibility
+ menuToggle.setAttribute('aria-expanded', 'false');
+ menuPanel.setAttribute('aria-hidden', 'true');
+
+ // Restore body overflow
+ document.body.style.overflow = '';
+
+ // Return focus to toggle button
+ menuToggle.focus();
+ }
+
+ function toggleMenu() {
+ if (menuOverlay && menuOverlay.classList.contains('opacity-0')) {
+ openMenu();
+ } else {
+ closeMenu();
+ }
}
// Toggle menu when clicking the hamburger button
@@ -38,7 +71,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
// Close menu when clicking menu items
- const menuLinks = document.querySelectorAll('#menu-panel a, #menu-panel button');
+ const menuLinks = document.querySelectorAll('#hamburger-menu a, #hamburger-menu button');
menuLinks.forEach(link => {
link.addEventListener('click', closeMenu);
});
@@ -49,4 +82,31 @@ document.addEventListener('DOMContentLoaded', () => {
closeMenu();
}
});
+
+ // Focus trap: keep tab within menu when open
+ if (menuPanel) {
+ menuPanel.addEventListener('keydown', (e) => {
+ if (e.key !== 'Tab') return;
+
+ const focusableElements = menuPanel.querySelectorAll('a, button, [tabindex]:not([tabindex="-1"])');
+ if (focusableElements.length === 0) return;
+
+ const firstElement = focusableElements[0];
+ const lastElement = focusableElements[focusableElements.length - 1];
+ const isMenuOpen = !menuOverlay.classList.contains('opacity-0');
+
+ if (!isMenuOpen) return;
+
+ // Shift+Tab on first element: move to last
+ if (e.shiftKey && document.activeElement === firstElement) {
+ e.preventDefault();
+ lastElement.focus();
+ }
+ // Tab on last element: move to first
+ else if (!e.shiftKey && document.activeElement === lastElement) {
+ e.preventDefault();
+ firstElement.focus();
+ }
+ });
+ }
});
diff --git a/themes/danix-xyz-hacker/layouts/_default/baseof.html b/themes/danix-xyz-hacker/layouts/_default/baseof.html
index f0a9283..2849562 100644
--- a/themes/danix-xyz-hacker/layouts/_default/baseof.html
+++ b/themes/danix-xyz-hacker/layouts/_default/baseof.html
@@ -28,6 +28,11 @@
<link rel="stylesheet" href="{{ $chroma.RelPermalink }}">
</head>
<body class="bg-bg text-text antialiased">
+ <!-- Skip to main content link -->
+ <a href="#main" class="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-accent focus:text-white focus:rounded">
+ {{ i18n "skipToContent" }}
+ </a>
+
<!-- Dot grid background pattern -->
<div class="fixed inset-0 pointer-events-none opacity-5 dot-grid" style="
background-image: radial-gradient(circle, currentColor 1px, transparent 1px);
diff --git a/themes/danix-xyz-hacker/layouts/_default/single.html b/themes/danix-xyz-hacker/layouts/_default/single.html
index 3269dec..60cf03d 100644
--- a/themes/danix-xyz-hacker/layouts/_default/single.html
+++ b/themes/danix-xyz-hacker/layouts/_default/single.html
@@ -1,5 +1,5 @@
{{ define "main" }}
-<div class="mx-auto px-4 py-12">
+<article class="mx-auto px-4 py-12">
<div class="grid md:grid-cols-3 gap-8">
<!-- Article section -->
<div class="md:col-span-2">
@@ -37,5 +37,5 @@
<!-- Sidebar -->
{{ partial "sidebar.html" . }}
</div>
-</div>
+</article>
{{ end }}
diff --git a/themes/danix-xyz-hacker/layouts/articles/single.html b/themes/danix-xyz-hacker/layouts/articles/single.html
index 67e8a2f..c5d51e7 100644
--- a/themes/danix-xyz-hacker/layouts/articles/single.html
+++ b/themes/danix-xyz-hacker/layouts/articles/single.html
@@ -1,7 +1,7 @@
{{ define "main" }}
{{ $articleType := .Params.type | default "life" }}
{{ $template := printf "article-types/%s.html" $articleType }}
-<div class="mx-auto px-4 py-12">
+<article class="mx-auto px-4 py-12">
<div class="grid md:grid-cols-3 gap-8">
<!-- Article section -->
<div class="md:col-span-2">
@@ -37,5 +37,5 @@
<!-- Sidebar -->
{{ partial "sidebar.html" . }}
</div>
-</div>
+</article>
{{ end }}
diff --git a/themes/danix-xyz-hacker/layouts/index.html b/themes/danix-xyz-hacker/layouts/index.html
index ed151cb..d1efc53 100644
--- a/themes/danix-xyz-hacker/layouts/index.html
+++ b/themes/danix-xyz-hacker/layouts/index.html
@@ -7,6 +7,7 @@
<img
src="{{ .Params.image }}"
alt="{{ .Site.Params.author }}"
+ loading="lazy"
class="w-32 h-32 md:w-48 md:h-48 rounded-full border-4 border-accent object-cover"
>
</div>
diff --git a/themes/danix-xyz-hacker/layouts/partials/article-header.html b/themes/danix-xyz-hacker/layouts/partials/article-header.html
index 10808f9..b759909 100644
--- a/themes/danix-xyz-hacker/layouts/partials/article-header.html
+++ b/themes/danix-xyz-hacker/layouts/partials/article-header.html
@@ -6,9 +6,7 @@
<!-- Type badge -->
{{ if $typeData }}
<span
- class="inline-flex items-center px-3 py-1 rounded text-sm font-semibold mb-4 transition-colors"
- style="color: {{ $typeData.color_light }}; background-color: {{ $typeData.color_light }}20;"
- data-theme-dark-color="{{ $typeData.color_dark }}"
+ class="inline-flex items-center px-3 py-1 rounded text-sm font-semibold mb-4 transition-colors type-{{ $articleType }}"
>
{{ i18n $articleType }}
</span>
diff --git a/themes/danix-xyz-hacker/layouts/partials/article-list-item.html b/themes/danix-xyz-hacker/layouts/partials/article-list-item.html
index 652e171..1065f0b 100644
--- a/themes/danix-xyz-hacker/layouts/partials/article-list-item.html
+++ b/themes/danix-xyz-hacker/layouts/partials/article-list-item.html
@@ -1,8 +1,6 @@
{{ $articleType := .Params.type | default "life" }}
{{ $typeConfig := .Site.Params.articleTypes }}
{{ $typeData := index $typeConfig $articleType }}
-{{ $isDark := strings.Contains (os.Getenv "THEME") "dark" }}
-{{ $color := cond $isDark $typeData.color_dark $typeData.color_light }}
<a
href="{{ .RelPermalink }}"
@@ -30,8 +28,7 @@
<!-- Type badge -->
{{ if $typeData }}
<span
- class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium transition-colors"
- style="color: {{ $color }}; background-color: {{ $color }}20;"
+ class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium transition-colors type-{{ $articleType }}"
>
{{ i18n $articleType }}
</span>
diff --git a/themes/danix-xyz-hacker/layouts/partials/article-types/photo.html b/themes/danix-xyz-hacker/layouts/partials/article-types/photo.html
index f4ccf06..743e02a 100644
--- a/themes/danix-xyz-hacker/layouts/partials/article-types/photo.html
+++ b/themes/danix-xyz-hacker/layouts/partials/article-types/photo.html
@@ -3,6 +3,7 @@
<img
src="{{ .Params.featured_image }}"
alt="{{ .Title }}"
+ loading="lazy"
class="w-full h-auto rounded-lg border border-border/30"
/>
{{ if .Params.featured_image_caption }}
diff --git a/themes/danix-xyz-hacker/layouts/partials/article-types/quote.html b/themes/danix-xyz-hacker/layouts/partials/article-types/quote.html
index 3df7327..f27d189 100644
--- a/themes/danix-xyz-hacker/layouts/partials/article-types/quote.html
+++ b/themes/danix-xyz-hacker/layouts/partials/article-types/quote.html
@@ -1,4 +1,4 @@
-<blockquote class="mb-8 pl-6 border-l-4 border-accent italic text-2xl text-text">
+<blockquote class="mb-8 pl-6 border-l-4 border-[--type-quote] italic text-2xl text-text">
"{{ .Params.quote_text }}"
</blockquote>
diff --git a/themes/danix-xyz-hacker/layouts/partials/hamburger-menu.html b/themes/danix-xyz-hacker/layouts/partials/hamburger-menu.html
index bdcbca9..666db78 100644
--- a/themes/danix-xyz-hacker/layouts/partials/hamburger-menu.html
+++ b/themes/danix-xyz-hacker/layouts/partials/hamburger-menu.html
@@ -3,7 +3,8 @@
class="fixed inset-0 bg-black/50 backdrop-blur opacity-0 invisible transition-all duration-200 z-40"
>
<div
- id="menu-panel"
+ id="hamburger-menu"
+ aria-hidden="true"
class="fixed top-0 right-0 h-screen w-full max-w-sm bg-bg border-l border-border overflow-y-auto transform translate-x-full transition-transform duration-300 z-50"
>
<!-- Close button -->
diff --git a/themes/danix-xyz-hacker/layouts/partials/header.html b/themes/danix-xyz-hacker/layouts/partials/header.html
index 2ddb2e3..7f99748 100644
--- a/themes/danix-xyz-hacker/layouts/partials/header.html
+++ b/themes/danix-xyz-hacker/layouts/partials/header.html
@@ -48,17 +48,21 @@
<!-- Theme toggle button -->
<button
id="theme-toggle"
+ x-data="{ theme: localStorage.getItem('theme') || 'dark' }"
+ @click="theme = theme === 'dark' ? 'light' : 'dark'; document.documentElement.className = 'theme-' + theme; localStorage.setItem('theme', theme)"
aria-label="{{ i18n "toggleTheme" }}"
class="p-2 rounded hover:bg-surface transition-colors"
>
- <i data-feather="sun" class="w-5 h-5 hidden dark:block"></i>
- <i data-feather="moon" class="w-5 h-5 block dark:hidden"></i>
+ <i x-show="theme === 'dark'" data-feather="sun" class="w-5 h-5" aria-hidden="true"></i>
+ <i x-show="theme === 'light'" data-feather="moon" class="w-5 h-5" aria-hidden="true"></i>
</button>
<!-- Hamburger menu button (mobile only) -->
<button
id="menu-toggle"
aria-label="{{ i18n "toggleMenu" }}"
+ aria-expanded="false"
+ aria-controls="hamburger-menu"
class="md:hidden p-2 rounded hover:bg-surface transition-colors"
>
<i data-feather="menu" class="w-5 h-5"></i>