fix: resolve dark mode code blocks and JavaScript errors after Tailwind migration

- Fix dark mode code block styling in chroma.css (background stays bright issue)
- Resolve JavaScript errors with window.hbb object initialization
- Fix Mermaid.js color parsing errors with proper CSS variable fallbacks
- Move inline JavaScript to external files for CSP compliance
- Create dedicated hb-search.js for search functionality
- Add proper error handling and validation for PagefindUI initialization
- Update .stylelintrc.yaml to disable no-descending-specificity rule
- Fix remaining lint issues: flex-flow shorthand and kebab-case IDs
- Ensure theme toggler functionality works correctly

Resolves regressions introduced during Tailwind 3→4 migration.
All functionality now works in both light and dark modes.
This commit is contained in:
George Cushen committed 2025-09-06 18:19:08 +01:00
1 parent f1bd41ffa2
commit 3b314fe4b2
20 files changed
+146 -110

No files matched your search

+5 -3
View File
@@ -1,7 +1,6 @@
env:
browser: true
es2021: true
jquery: true
es6: true
node: true
extends:
- 'eslint:recommended'
@@ -9,7 +8,9 @@ extends:
parserOptions:
ecmaVersion: 12
sourceType: module
rules: {}
rules:
no-unused-vars:
- error
globals:
hljs: true
imagesLoaded: true
@@ -18,3 +19,4 @@ globals:
GMaps: true
Fuse: true
instantsearch: true
PagefindUI: readonly
+5 -9
View File
@@ -1,9 +1,5 @@
**/*.min.css
**/vendor/
**/_vendor/
**/libs/
**/dist/
public/
blox-bootstrap/assets/scss/main.scss
blox-bootstrap/assets/css/reveal_custom.css
**/public/**
**/dist/**
**/libs/**
modules/blox-tailwind/assets/css/color-utilities.css
starters/**/static/**
+1 -1
View File
@@ -12,10 +12,10 @@ rules:
property-no-vendor-prefix: null # Necessary unless postcss prefix
value-no-vendor-prefix: null # Necessary unless postcss prefix
at-rule-no-unknown: null # Use PostCSS "at" rules
no-descending-specificity: null # Allow nested and grouped rules without false positives
# PREVIOUS RULES - no longer needed now porting from Bootstrap to Tailwind
# at-rule-no-unknown: null # Use SCSS "at" rules
# scss/at-rule-no-unknown: true # Use SCSS "at" rules
# no-descending-specificity: null # Todo: remove once investigated/resolved
# color-function-notation: null # Todo: refactor code
# alpha-value-notation: null # Todo: refactor code
# number-max-precision: null # Todo: refactor code
@@ -15,8 +15,7 @@
.network-icon {
display: inline-flex;
flex-direction: row;
flex-wrap: wrap;
flex-flow: row wrap;
justify-content: center;
list-style: none;
padding: 0;
@@ -51,18 +50,18 @@
font-size: 0.9rem;
}
/* .ul-edu { */
/* list-style: none; */
/* } */
/* .ul-edu { */
/* list-style: none; */
/* } */
/* .ul-edu li { */
/* position: relative; */
/* padding: 0 15px 4px 3px; */
/* } */
/* .ul-edu li { */
/* position: relative; */
/* padding: 0 15px 4px 3px; */
/* } */
/* .ul-edu li .description p { */
/* margin: 0; */
/* } */
/* .ul-edu li .description p { */
/* margin: 0; */
/* } */
.ul-edu li .description p.course {
font-size: 0.9rem;
@@ -115,7 +114,6 @@
box-shadow: 0 10px 20px rgb(0 0 0 / 4%), 0 2px 6px rgb(0 0 0 / 4%), 0 0 1px rgb(0 0 0 / 4%);
}
.dark .avatar-emoji {
background-color: #000;
box-shadow: 0 10px 20px rgb(0 0 0 / 4%), 0 2px 6px rgb(0 0 0 / 4%), 0 0 1px rgb(255 255 255 / 96%);
@@ -20,7 +20,7 @@
/* Ensure interactive controls show hand cursor */
.theme-toggle,
[data-search-toggle],
#search_toggle,
#search-toggle,
[data-te-collapse-init],
.nav-dropdown > .nav-link[role='button'] {
@apply cursor-pointer;
+5 -3
View File
@@ -1,11 +1,12 @@
@import "./libs/chroma/light.css";
@import "./libs/chroma/dark.css";
@import './libs/chroma/light.css';
@import './libs/chroma/dark.css';
/* Code Blocks: Apply theme background */
/* Requires relative position for Code Copy button */
.prose .chroma,
.prose pre {
@apply relative rounded-md dark:bg-slate-700 dark:text-white;
@apply relative rounded-md;
color: rgb(var(--color-neutral-700));
background-color: rgb(var(--color-neutral-50));
}
@@ -13,4 +14,5 @@
.dark .prose .chroma,
.dark .prose pre {
color: white;
background-color: rgb(71 85 105); /* slate-700 equivalent */
}
@@ -10,6 +10,7 @@
.hb-card {
@apply flex flex-col justify-start overflow-hidden rounded-lg border border-gray-200 text-current no-underline dark:shadow-transparent hover:shadow-gray-100 dark:hover:shadow-transparent shadow-gray-100 active:shadow-sm active:shadow-gray-200 transition-all duration-200;
@apply hover:border-gray-300 bg-transparent shadow-sm hover:bg-slate-50 hover:shadow-md;
border-color: rgb(var(--color-neutral-700));
}
@@ -24,6 +25,7 @@
.hb-card-title {
@apply flex font-semibold items-start gap-2 text-gray-700 hover:text-gray-900 items-center;
color: rgb(var(--color-neutral-700));
}
@@ -88,6 +88,7 @@
/* For ToC shortcode, Spoiler shortcode, and direct HTML Details snippets. */
details > summary {
@apply cursor-pointer font-semibold;
color: rgb(var(--color-primary-700));
}
@@ -70,7 +70,7 @@
}
.dark .hb-docs-link:hover {
background-color: rgb(var(--color-primary-100) / 0.05);
background-color: rgb(var(--color-primary-100) / 5%);
}
.hb-sidebar-mobile-menu {
@@ -1,5 +1,6 @@
.hb-steps {
@apply ml-4 mb-12 pl-6 border-l border-gray-200 [counter-reset:hb_step];
border-left-color: rgb(var(--color-neutral-200));
}
@@ -16,8 +17,8 @@
@apply bg-gray-100 dark:bg-hb-dark;
@apply text-base font-normal text-center -indent-px;
@apply mt-[3px] ml-[-41px];
color: rgb(var(--color-neutral-700));
color: rgb(var(--color-neutral-700));
content: counter(hb-step);
}
}
@@ -45,7 +45,7 @@
--color-secondary-950: 8 51 68;
/* Font family */
--font-family-sans: Inter var, ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
--font-family-sans: inter var, ui-sans-serif, system-ui, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji;
/* Font sizes */
--font-size-xs: 0.75rem;
@@ -3,7 +3,7 @@
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
text-rendering: optimizelegibility;
}
body {
@@ -1,5 +1,6 @@
.hb-attachment-link {
@apply inline-block font-semibold uppercase outline-none mr-4 mb-1.5 transition-all text-sm tracking-wide focus:outline-none;
color: rgb(var(--color-primary-600));
}
+18 -4
View File
@@ -1,7 +1,21 @@
import { initTheme, applyHugoStyleFixes } from './hb-init.js'
initTheme()
applyHugoStyleFixes()
import {initTheme, applyHugoStyleFixes} from './hb-init.js';
// Initialize Hugo Blox Builder global object
const root = document.documentElement;
const defaultTheme = root.dataset.wcThemeDefault || 'system';
// Create global HBB object with theme functions
window.hbb = {
defaultTheme: defaultTheme,
setDarkTheme: function () {
root.classList.add('dark');
root.style.colorScheme = 'dark';
},
setLightTheme: function () {
root.classList.remove('dark');
root.style.colorScheme = 'light';
},
};
initTheme();
applyHugoStyleFixes();
@@ -1,19 +1,20 @@
function getCssVar(name) {
return "rgb(" + getComputedStyle(document.documentElement).getPropertyValue(name) + ")";
const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
return value ? 'rgb(' + value + ')' : null;
}
window.mermaid.initialize({
theme: "base",
theme: 'base',
themeVariables: {
background: getCssVar("--color-neutral"),
primaryColor: getCssVar("--color-primary-200"),
secondaryColor: getCssVar("--color-secondary-200"),
tertiaryColor: getCssVar("--color-neutral-100"),
primaryBorderColor: getCssVar("--color-primary-400"),
secondaryBorderColor: getCssVar("--color-secondary-400"),
tertiaryBorderColor: getCssVar("--color-neutral-400"),
lineColor: getCssVar("--color-neutral-600"),
background: getCssVar('--color-neutral-50') || '#fafafa',
primaryColor: getCssVar('--color-primary-200') || '#bfdbfe',
secondaryColor: getCssVar('--color-secondary-200') || '#a5f3fc',
tertiaryColor: getCssVar('--color-neutral-100') || '#f5f5f5',
primaryBorderColor: getCssVar('--color-primary-400') || '#60a5fa',
secondaryBorderColor: getCssVar('--color-secondary-400') || '#22d3ee',
tertiaryBorderColor: getCssVar('--color-neutral-400') || '#a3a3a3',
lineColor: getCssVar('--color-neutral-600') || '#525252',
fontFamily: getComputedStyle(document.documentElement).getPropertyValue('font-family'),
fontSize: "16px",
fontSize: '16px',
},
});
@@ -0,0 +1,69 @@
// Hugo Blox Search functionality
// Initialize search wrapper element globally
let searchWrapper = null;
window.addEventListener('DOMContentLoaded', () => {
const configScript = document.getElementById('search-config');
if (!configScript) {
// No search on this page.
return;
}
const searchConfig = JSON.parse(configScript.textContent);
if (!searchConfig || typeof searchConfig.baseUrl === 'undefined') {
console.error('Pagefind search config is missing or invalid.', searchConfig);
return;
}
try {
new PagefindUI({
element: '#search',
showSubResults: true,
baseUrl: searchConfig.baseUrl,
bundlePath: searchConfig.baseUrl + 'pagefind/',
});
} catch (e) {
console.error('Failed to initialize PagefindUI:', e);
return;
}
// Get search wrapper element
searchWrapper = document.getElementById('search-wrapper');
// Add click handlers to triggers
let triggers = document.querySelectorAll('[data-search-toggle]');
triggers.forEach((trigger) => trigger.addEventListener('click', handleSearchToggle));
});
function handleSearchToggle() {
if (!searchWrapper) return;
const isHidden = searchWrapper.classList.contains('hidden');
searchWrapper.classList.toggle('hidden');
document.body.style.overflow = isHidden ? 'hidden' : '';
const searchInput = searchWrapper.querySelector('input');
if (searchInput) {
searchInput.value = '';
searchInput.focus();
}
if (!searchWrapper.classList.contains('hidden')) {
let clearTrigger = document.querySelector('.pagefind-ui__search-clear');
if (clearTrigger && !clearTrigger.hasAttribute('listenerOnClick')) {
clearTrigger.setAttribute('listenerOnClick', 'true');
clearTrigger.addEventListener('click', () => {
searchInput.focus();
});
}
}
}
// Add keyboard shortcut to close search modal with Escape key
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && searchWrapper && !searchWrapper.classList.contains('hidden')) {
searchWrapper.classList.add('hidden');
}
});
@@ -141,7 +141,7 @@
{{ end }}
{{ with $block.content.button }}
<a href="{{.url}}" target="_blank" class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:ring-4 focus:outline-none focus:ring-gray-200 focus:text-primary-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-gray-700"><svg class="w-3.5 h-3.5 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<a href="{{.url}}" target="_blank" class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg transition-colors hover:bg-gray-100 hover:text-gray-900 focus:z-10 focus:ring-4 focus:outline-none focus:ring-gray-200 focus:text-gray-900 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 dark:focus:text-white dark:focus:ring-gray-700"><svg class="w-3.5 h-3.5 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M14.707 7.793a1 1 0 0 0-1.414 0L11 10.086V1.5a1 1 0 0 0-2 0v8.586L6.707 7.793a1 1 0 1 0-1.414 1.414l4 4a1 1 0 0 0 1.416 0l4-4a1 1 0 0 0-.002-1.414Z"/>
<path d="M18 12h-2.55l-2.975 2.975a3.5 3.5 0 0 1-4.95 0L4.55 12H2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2Zm-3 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"/>
</svg> {{.text}}</a>
@@ -52,7 +52,7 @@
<div class="mb-4 text-base font-normal text-gray-500 dark:text-gray-300 prose max-w-none prose-slate dark:prose-invert">{{.summary | $page.RenderString}}</div>
{{ with .button }}
<a href="{{.url}}" target="_blank" rel="noopener" class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:ring-4 focus:outline-none focus:ring-gray-200 focus:text-primary-700 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-gray-700"><svg class="w-3.5 h-3.5 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<a href="{{.url}}" target="_blank" rel="noopener" class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg transition-colors hover:bg-gray-100 hover:text-gray-900 focus:z-10 focus:ring-4 focus:outline-none focus:ring-gray-200 focus:text-gray-900 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 dark:focus:text-white dark:focus:ring-gray-700"><svg class="w-3.5 h-3.5 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M14.707 7.793a1 1 0 0 0-1.414 0L11 10.086V1.5a1 1 0 0 0-2 0v8.586L6.707 7.793a1 1 0 1 0-1.414 1.414l4 4a1 1 0 0 0 1.416 0l4-4a1 1 0 0 0-.002-1.414Z"/>
<path d="M18 12h-2.55l-2.975 2.975a3.5 3.5 0 0 1-4.95 0L4.55 12H2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2Zm-3 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"/>
</svg> {{.text}}</a>
@@ -91,7 +91,7 @@
<div class="mb-4 text-base font-normal text-gray-500 dark:text-gray-300 prose max-w-none prose-slate dark:prose-invert">{{.summary | $page.RenderString}}</div>
{{ with .button }}
<a href="{{.url}}" target="_blank" rel="noopener" class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:ring-4 focus:outline-none focus:ring-gray-200 focus:text-primary-700 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-gray-700"><svg class="w-3.5 h-3.5 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<a href="{{.url}}" target="_blank" rel="noopener" class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg transition-colors hover:bg-gray-100 hover:text-gray-900 focus:z-10 focus:ring-4 focus:outline-none focus:ring-gray-200 focus:text-gray-900 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 dark:focus:text-white dark:focus:ring-gray-700"><svg class="w-3.5 h-3.5 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M14.707 7.793a1 1 0 0 0-1.414 0L11 10.086V1.5a1 1 0 0 0-2 0v8.586L6.707 7.793a1 1 0 1 0-1.414 1.414l4 4a1 1 0 0 0 1.416 0l4-4a1 1 0 0 0-.002-1.414Z"/>
<path d="M18 12h-2.55l-2.975 2.975a3.5 3.5 0 0 1-4.95 0L4.55 12H2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2Zm-3 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"/>
</svg> {{.text}}</a>
@@ -67,7 +67,7 @@
[&.active]:font-bold [&.active]:text-black/90 dark:[&.active]:text-white"
aria-current="page"
href="#"
id="search_toggle"
id="search-toggle"
>{{- i18n "search" -}}</a
>
</li>
@@ -17,7 +17,7 @@
<script src="{{ $pf_js.RelPermalink }}" integrity="{{ $pf_js.Data.Integrity }}"></script>
{{ $search_config := dict "baseUrl" (relURL "") }}
{{ printf "<script>window.hbb.pagefind = %s;</script>" ($search_config | jsonify) | safeHTML }}
<script id="search-config" type="application/json">{{ $search_config | jsonify | safeJS }}</script>
<style>
html.dark {
@@ -35,62 +35,11 @@
}
</style>
<script>
// Initialize search wrapper element globally
let searchWrapper = null;
window.addEventListener('DOMContentLoaded', (event) => {
// Initialize PagefindUI
new PagefindUI({
element: "#search",
showSubResults: true,
baseUrl: window.hbb.pagefind.baseUrl,
bundlePath: window.hbb.pagefind.baseUrl + "pagefind/",
});
// Get search wrapper element
searchWrapper = document.getElementById('search-wrapper');
// Add click handlers to triggers
let triggers = document.querySelectorAll('[data-search-toggle]');
triggers.forEach(trigger =>
trigger.addEventListener('click', handleSearchToggle)
);
});
function handleSearchToggle(event) {
if (!searchWrapper) return;
const isHidden = searchWrapper.classList.contains('hidden');
searchWrapper.classList.toggle('hidden');
document.body.style.overflow = isHidden ? 'hidden' : '';
const searchInput = searchWrapper.querySelector("input");
if (searchInput) {
searchInput.value = "";
searchInput.focus();
}
if (!searchWrapper.classList.contains('hidden')) {
let clearTrigger = document.querySelector('.pagefind-ui__search-clear');
if (clearTrigger && !clearTrigger.hasAttribute('listenerOnClick')) {
clearTrigger.setAttribute('listenerOnClick', 'true');
clearTrigger.addEventListener('click', () => {
searchInput.focus();
});
}
}
}
// Add keyboard shortcut to close search modal with Escape key
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && searchWrapper && !searchWrapper.classList.contains('hidden')) {
searchWrapper.classList.add('hidden');
}
});
</script>
{{ $search_js := resources.Get "js/hb-search.js" | resources.Minify }}
{{- if hugo.IsProduction -}}
{{ $search_js = $search_js | fingerprint "sha256" }}
{{- end -}}
<script defer src="{{ $search_js.RelPermalink }}"{{ if hugo.IsProduction }} integrity="{{ $search_js.Data.Integrity }}" crossorigin="anonymous"{{ end }}></script>
{{ end }}
{{/* Mermaid */}}