diff --git a/src/view/frontend/web/css/toolbar/_footer.css b/src/view/frontend/web/css/toolbar/_footer.css index 73fbb219..1b84e650 100644 --- a/src/view/frontend/web/css/toolbar/_footer.css +++ b/src/view/frontend/web/css/toolbar/_footer.css @@ -13,9 +13,9 @@ padding-top: 6px; } -/* ── Theme toggle ────────────────────────────────────────────────────────── */ +/* ── Footer toolbar row (credit + theme + export) ─────────────────────────── */ -.mageforge-footer-theme-row { +.mageforge-footer-toolbar-row { display: flex; align-items: center; justify-content: space-between; @@ -23,7 +23,7 @@ gap: 8px; } -.mageforge-footer-theme-label { +.mageforge-footer-toolbar-label { font-family: var(--mageforge-font-family); font-size: 10px; font-weight: 500; @@ -33,7 +33,7 @@ padding: 3px 6px 3px 8px; } -.mageforge-theme-toggle { +.mageforge-segmented-toggle { display: flex; align-items: center; gap: 2px; @@ -42,7 +42,7 @@ padding: 2px; } -.mageforge-theme-btn { +.mageforge-segmented-btn { padding: 3px 8px; border-radius: 4px; border: none; @@ -56,23 +56,23 @@ line-height: 1; } -.mageforge-theme-btn:hover { +.mageforge-segmented-btn:hover { color: var(--mageforge-color-white); background: rgba(var(--mageforge-color-white-rgb), 0.08); } -.mageforge-theme-btn--active, -.mageforge-theme-btn[aria-pressed="true"] { +.mageforge-segmented-btn--active, +.mageforge-segmented-btn[aria-pressed="true"] { background: var(--mageforge-color-blue); color: var(--mageforge-color-white); } -.mageforge-theme-btn:focus-visible { +.mageforge-segmented-btn:focus-visible { outline: 2px solid var(--mageforge-color-blue); outline-offset: 1px; } -.mageforge-theme-btn:disabled { +.mageforge-segmented-btn:disabled { opacity: 0.35; cursor: not-allowed; pointer-events: none; diff --git a/src/view/frontend/web/css/toolbar/_health.css b/src/view/frontend/web/css/toolbar/_health.css index 2599fc25..ece52a25 100644 --- a/src/view/frontend/web/css/toolbar/_health.css +++ b/src/view/frontend/web/css/toolbar/_health.css @@ -127,3 +127,25 @@ text-align: center; line-height: 1; } + +/* ============================================================================ + Dynamic score colours + ========================================================================== */ + +.mageforge-score--low .mageforge-toolbar-health-score-number, +.mageforge-score--low .mageforge-score-number, +.mageforge-score--low.mageforge-dashboard-category-score { + color: var(--mageforge-color-red); +} + +.mageforge-score--mid .mageforge-toolbar-health-score-number, +.mageforge-score--mid .mageforge-score-number, +.mageforge-score--mid.mageforge-dashboard-category-score { + color: var(--mageforge-color-amber); +} + +.mageforge-score--high .mageforge-toolbar-health-score-number, +.mageforge-score--high .mageforge-score-number, +.mageforge-score--high.mageforge-dashboard-category-score { + color: var(--mageforge-color-green); +} diff --git a/src/view/frontend/web/css/toolbar/_menu.css b/src/view/frontend/web/css/toolbar/_menu.css index 4526a7fd..3b2d62ad 100644 --- a/src/view/frontend/web/css/toolbar/_menu.css +++ b/src/view/frontend/web/css/toolbar/_menu.css @@ -122,6 +122,57 @@ outline-offset: 2px; } +/* ============================================================================ + Menu Header Actions (theme toggle + close) + ========================================================================== */ + +.mageforge-toolbar-menu-actions { + display: flex; + align-items: center; + gap: 6px; +} + +.mageforge-theme-icon-group { + display: flex; + align-items: center; + gap: 2px; + background: rgba(var(--mageforge-color-white-rgb), 0.06); + border-radius: 6px; + padding: 2px; +} + +.mageforge-theme-icon-btn { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 0; + border: none; + border-radius: 4px; + background: transparent; + color: var(--mageforge-color-slate-400); + cursor: pointer; + transition: all 0.15s ease; + line-height: 0; +} + +.mageforge-theme-icon-btn:hover { + color: var(--mageforge-color-white); + background: rgba(var(--mageforge-color-white-rgb), 0.08); +} + +.mageforge-theme-icon-btn--active, +.mageforge-theme-icon-btn[aria-pressed="true"] { + background: var(--mageforge-color-blue); + color: var(--mageforge-color-white); +} + +.mageforge-theme-icon-btn:focus-visible { + outline: 2px solid var(--mageforge-color-blue); + outline-offset: 2px; +} + /* ============================================================================ Menu Items ========================================================================== */ diff --git a/src/view/frontend/web/css/toolbar/_themes.css b/src/view/frontend/web/css/toolbar/_themes.css index ad099fd8..c3292e3b 100644 --- a/src/view/frontend/web/css/toolbar/_themes.css +++ b/src/view/frontend/web/css/toolbar/_themes.css @@ -59,11 +59,13 @@ } /* Theme toggle pill: replace hardcoded white alpha with dark */ -.mageforge-toolbar[data-theme="light"] .mageforge-theme-toggle { +.mageforge-toolbar[data-theme="light"] .mageforge-segmented-toggle, +.mageforge-toolbar[data-theme="light"] .mageforge-theme-icon-group { background: rgba(var(--mageforge-color-black-rgb), 0.06); } -.mageforge-toolbar[data-theme="light"] .mageforge-theme-btn:hover { +.mageforge-toolbar[data-theme="light"] .mageforge-segmented-btn:hover, +.mageforge-toolbar[data-theme="light"] .mageforge-theme-icon-btn:hover { background: rgba(var(--mageforge-color-black-rgb), 0.08); } @@ -130,11 +132,13 @@ background: rgba(var(--mageforge-color-teal-rgb), 0.14); } - .mageforge-toolbar[data-theme="auto"] .mageforge-theme-toggle { + .mageforge-toolbar[data-theme="auto"] .mageforge-segmented-toggle, + .mageforge-toolbar[data-theme="auto"] .mageforge-theme-icon-group { background: rgba(var(--mageforge-color-black-rgb), 0.06); } - .mageforge-toolbar[data-theme="auto"] .mageforge-theme-btn:hover { + .mageforge-toolbar[data-theme="auto"] .mageforge-segmented-btn:hover, + .mageforge-toolbar[data-theme="auto"] .mageforge-theme-icon-btn:hover { background: rgba(var(--mageforge-color-black-rgb), 0.08); } diff --git a/src/view/frontend/web/js/toolbar/ui/build.js b/src/view/frontend/web/js/toolbar/ui/build.js index 402434cf..923c602d 100644 --- a/src/view/frontend/web/js/toolbar/ui/build.js +++ b/src/view/frontend/web/js/toolbar/ui/build.js @@ -24,6 +24,9 @@ import { createLogoSvg, generateId, ICON_HOME, + ICON_THEME_DARK, + ICON_THEME_LIGHT, + ICON_THEME_AUTO, GROUP_ICONS, GAUGE_ARC_LENGTH, SCORE_RING_CIRCUMFERENCE, @@ -93,7 +96,7 @@ export const buildMethods = { }, /** - * Sticky title bar: logo + name + close button. + * Sticky title bar: logo + name + theme toggle + close button. * * @returns {HTMLDivElement} */ @@ -105,15 +108,54 @@ export const buildMethods = {
${createLogoSvg("#E5622A")}
MageForge - +
`; - header.querySelector(".mageforge-toolbar-menu-close").onclick = (e) => { + + const actions = header.querySelector(".mageforge-toolbar-menu-actions"); + + // Theme toggle + const themeGroup = document.createElement("div"); + themeGroup.className = "mageforge-theme-icon-group"; + themeGroup.setAttribute("role", "group"); + themeGroup.setAttribute("aria-label", "Colour theme"); + [ + ["dark", ICON_THEME_DARK, "Dark theme"], + ["auto", ICON_THEME_AUTO, "Auto theme"], + ["light", ICON_THEME_LIGHT, "Light theme"], + ].forEach(([value, icon, label]) => { + const btn = document.createElement("button"); + btn.type = "button"; + btn.className = "mageforge-theme-icon-btn"; + btn.dataset.themeValue = value; + btn.innerHTML = icon; + btn.setAttribute("aria-label", label); + btn.title = label; + btn.setAttribute("aria-pressed", "false"); + btn.onclick = (e) => { + e.stopPropagation(); + this.setTheme(/** @type {'dark'|'auto'|'light'} */ (value)); + }; + themeGroup.appendChild(btn); + }); + actions.appendChild(themeGroup); + this._themeToggleGroup = themeGroup; + + // Close button + const closeBtn = document.createElement("button"); + closeBtn.type = "button"; + closeBtn.className = "mageforge-toolbar-menu-close"; + closeBtn.title = "Close & deactivate all"; + closeBtn.setAttribute("aria-label", "Close & deactivate all"); + closeBtn.innerHTML = + ''; + closeBtn.onclick = (e) => { e.stopPropagation(); this.deactivateAllAudits(); this.closeMenu(); }; + actions.appendChild(closeBtn); + + this._updateThemeToggle(); return header; }, @@ -718,16 +760,26 @@ export const buildMethods = { const footer = document.createElement("div"); footer.className = "mageforge-toolbar-menu-footer"; - // Export format row ────────────────────────────────────────────────── - const exportRow = document.createElement("div"); - exportRow.className = "mageforge-footer-theme-row"; - this._exportBtnRow = exportRow; - const exportGroup = document.createElement("div"); - exportGroup.className = "mageforge-theme-toggle"; + // Footer toolbar row: credit + theme toggle + export formats + const footerRow = document.createElement("div"); + footerRow.className = "mageforge-footer-toolbar-row"; + + // Credit line + const credit = document.createElement("div"); + credit.className = "mageforge-toolbar-menu-credit"; + credit.innerHTML = + 'Built with \u2764 by MageForge'; + footerRow.appendChild(credit); + + // Export format toggle + this._exportBtnRow = document.createElement("div"); + this._exportBtnRow.className = "mageforge-segmented-toggle"; + this._exportBtnRow.setAttribute("role", "group"); + this._exportBtnRow.setAttribute("aria-label", "Export format"); const exportLabel = document.createElement("span"); - exportLabel.className = "mageforge-footer-theme-label"; + exportLabel.className = "mageforge-footer-toolbar-label"; exportLabel.textContent = "Export"; - exportGroup.appendChild(exportLabel); + this._exportBtnRow.appendChild(exportLabel); [ ["json", "JSON"], ["md", "MD"], @@ -735,7 +787,7 @@ export const buildMethods = { ].forEach(([fmt, label]) => { const btn = document.createElement("button"); btn.type = "button"; - btn.className = "mageforge-theme-btn mageforge-export-btn--disabled"; + btn.className = "mageforge-segmented-btn mageforge-export-btn--disabled"; btn.dataset.exportFormat = fmt; btn.textContent = label; btn.disabled = true; @@ -745,17 +797,11 @@ export const buildMethods = { e.stopPropagation(); this.exportFindings(fmt); }; - exportGroup.appendChild(btn); + this._exportBtnRow.appendChild(btn); }); - exportRow.appendChild(exportGroup); - footer.appendChild(exportRow); + footerRow.appendChild(this._exportBtnRow); - // Credit line (left side of export row) ───────────────────────────── - const credit = document.createElement("div"); - credit.className = "mageforge-toolbar-menu-credit"; - credit.innerHTML = - 'Built with \u2764 by MageForge'; - exportRow.insertBefore(credit, exportRow.firstChild); + footer.appendChild(footerRow); // Populate nav action bar for the initially active tab (home). // footerActionBar was already created in _buildTabNav(). diff --git a/src/view/frontend/web/js/toolbar/ui/constants.js b/src/view/frontend/web/js/toolbar/ui/constants.js index c5f5bc87..3cb99971 100644 --- a/src/view/frontend/web/js/toolbar/ui/constants.js +++ b/src/view/frontend/web/js/toolbar/ui/constants.js @@ -6,6 +6,15 @@ export const LOGO_SVG_PATH = export const ICON_HOME = ''; +export const ICON_THEME_DARK = + ''; + +export const ICON_THEME_LIGHT = + ''; + +export const ICON_THEME_AUTO = + ''; + export const GROUP_ICONS = { wcag: '', "html-quality": diff --git a/src/view/frontend/web/js/toolbar/ui/controls.js b/src/view/frontend/web/js/toolbar/ui/controls.js index 33838406..c26ff993 100644 --- a/src/view/frontend/web/js/toolbar/ui/controls.js +++ b/src/view/frontend/web/js/toolbar/ui/controls.js @@ -96,6 +96,7 @@ export const controls = { this.runAllButton = null; this.resetButton = null; this._exportBtnRow = null; + this._themeToggleGroup = null; this.menuOpen = false; }, @@ -110,5 +111,20 @@ export const controls = { localStorage.setItem("mageforge-theme", theme); } catch (_) {} if (this.container) this.container.setAttribute("data-theme", theme); + this._updateThemeToggle(); + }, + + /** + * Reflect the current theme in the header icon toggle buttons. + */ + _updateThemeToggle() { + if (!this._themeToggleGroup) return; + this._themeToggleGroup + .querySelectorAll(".mageforge-theme-icon-btn") + .forEach((btn) => { + const active = btn.dataset.themeValue === this.currentTheme; + btn.classList.toggle("mageforge-theme-icon-btn--active", active); + btn.setAttribute("aria-pressed", String(active)); + }); }, }; diff --git a/src/view/frontend/web/js/toolbar/ui/score.js b/src/view/frontend/web/js/toolbar/ui/score.js index b34ded85..da0f9c65 100644 --- a/src/view/frontend/web/js/toolbar/ui/score.js +++ b/src/view/frontend/web/js/toolbar/ui/score.js @@ -7,6 +7,30 @@ import { GAUGE_ARC_LENGTH, SCORE_RING_CIRCUMFERENCE } from "./constants.js"; export const scoreMethods = { + /** + * Apply a colour class to a score element based on its value. + * < 50 = low (red), 50–89 = mid (amber), >= 90 = high (green). + * + * @param {HTMLElement|null} el + * @param {number} score + */ + _applyScoreColorClass(el, score) { + if (!el) return; + el.classList.remove( + "mageforge-score--low", + "mageforge-score--mid", + "mageforge-score--high", + ); + if (score < 0) return; + if (score < 50) { + el.classList.add("mageforge-score--low"); + } else if (score < 90) { + el.classList.add("mageforge-score--mid"); + } else { + el.classList.add("mageforge-score--high"); + } + }, + /** * Animate all score gauges and rings to the given score (0-100). * @@ -32,9 +56,13 @@ export const scoreMethods = { needle.setAttribute("opacity", "1"); } this.menu - .querySelectorAll(".mageforge-toolbar-health-score-number") + .querySelectorAll(".mageforge-toolbar-health-score-value") .forEach((el) => { - el.textContent = score; + const number = el.querySelector( + ".mageforge-toolbar-health-score-number", + ); + if (number) number.textContent = score; + this._applyScoreColorClass(el, score); }); // Circular rings in audit panel headers @@ -69,9 +97,13 @@ export const scoreMethods = { ); } const number = panel.querySelector(".mageforge-score-number"); + const valueEl = panel.querySelector(".mageforge-score-value"); if (number) { number.textContent = score; } + if (valueEl) { + this._applyScoreColorClass(valueEl, score); + } // Also update the dashboard category badge const dashboardScore = this.menu.querySelector( @@ -83,6 +115,7 @@ export const scoreMethods = { "mageforge-dashboard-category-score--active", score > 0, ); + this._applyScoreColorClass(dashboardScore, score); } }, @@ -111,6 +144,13 @@ export const scoreMethods = { this.menu.querySelectorAll(".mageforge-score-number").forEach((el) => { el.textContent = "--"; }); + this.menu + .querySelectorAll( + ".mageforge-toolbar-health-score-value, .mageforge-score-value, .mageforge-dashboard-category-score", + ) + .forEach((el) => { + this._applyScoreColorClass(el, -1); + }); // Reset dashboard category badges this.menu.querySelectorAll("[data-dashboard-group-score]").forEach((el) => {