diff options
| author | Danilo M. <danix@danix.xyz> | 2026-04-15 21:42:04 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-04-15 21:42:04 +0200 |
| commit | 9934e042c1e97765ac839f6c3f06d30f02b5d9d0 (patch) | |
| tree | cf102ee79caf5d304e303956a1becd5fb680ca39 /themes/danix-xyz-hacker | |
| parent | eb8a17482fc138c861db685f9efc86871bf2d7d2 (diff) | |
| download | danixxyz-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>
Diffstat (limited to 'themes/danix-xyz-hacker')
13 files changed, 324 insertions, 66 deletions
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> |
