From 8917bf6216afba1ff8d689064a75bec8d4b01d88 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 23:54:36 +0000 Subject: [PATCH 1/6] Initial plan From dcc3327f7c3a81eeb3c6b52a6a6206b73694d514 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 23:57:01 +0000 Subject: [PATCH 2/6] Fix FA6KITCODE to FA7KITCODE language variable Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- src/language/en-GB/tpl_moko-cassiopeia.ini | 4 ++-- src/language/en-US/tpl_moko-cassiopeia.ini | 4 ++-- src/templates/templateDetails.xml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/language/en-GB/tpl_moko-cassiopeia.ini b/src/language/en-GB/tpl_moko-cassiopeia.ini index d896db4..2a5cc5a 100644 --- a/src/language/en-GB/tpl_moko-cassiopeia.ini +++ b/src/language/en-GB/tpl_moko-cassiopeia.ini @@ -57,8 +57,8 @@ TPL_MOKO-CASSIOPEIA_LOGO_LABEL="Logo" TPL_MOKO-CASSIOPEIA_TITLE="Title (alternative to logo)" TPL_MOKO-CASSIOPEIA_TAGLINE_LABEL="Tagline" TPL_MOKO-CASSIOPEIA_TAGLINE_DESC="Optional text to show as a subheading" -TPL_MOKO-CASSIOPEIA_FAKITCODE_LABEL="Font Awesome Kit Unique Code" -TPL_MOKO-CASSIOPEIA_FAKITCODE_DESC="If left blank, Font Awesome 7 Free will be used.
Copy the unique Kit embed code above and paste it into the <head> of your project's HTML file or template.
More information at the Font Awesome website." +TPL_MOKO-CASSIOPEIA_FA7KITCODE_LABEL="Font Awesome 7 Kit Unique Code" +TPL_MOKO-CASSIOPEIA_FA7KITCODE_DESC="If left blank, Font Awesome 7 Free will be used.
Copy the unique Kit embed code above and paste it into the <head> of your project's HTML file or template.
More information at the Font Awesome website." ; ===== Typography (Theme tab) ===== TPL_MOKO-CASSIOPEIA_FONT_LABEL="Fonts Scheme" diff --git a/src/language/en-US/tpl_moko-cassiopeia.ini b/src/language/en-US/tpl_moko-cassiopeia.ini index eee2092..6df0ef7 100644 --- a/src/language/en-US/tpl_moko-cassiopeia.ini +++ b/src/language/en-US/tpl_moko-cassiopeia.ini @@ -57,8 +57,8 @@ TPL_MOKO-CASSIOPEIA_LOGO_LABEL="Logo" TPL_MOKO-CASSIOPEIA_TITLE="Title (alternative to logo)" TPL_MOKO-CASSIOPEIA_TAGLINE_LABEL="Tagline" TPL_MOKO-CASSIOPEIA_TAGLINE_DESC="Optional text to show as a subheading" -TPL_MOKO-CASSIOPEIA_FAKITCODE_LABEL="Font Awesome Kit Unique Code" -TPL_MOKO-CASSIOPEIA_FAKITCODE_DESC="If left blank, Font Awesome 7 Free will be used.
Copy the unique Kit embed code above and paste it into the <head> of your project's HTML file or template.
More information at the Font Awesome website." +TPL_MOKO-CASSIOPEIA_FA7KITCODE_LABEL="Font Awesome 7 Kit Unique Code" +TPL_MOKO-CASSIOPEIA_FA7KITCODE_DESC="If left blank, Font Awesome 7 Free will be used.
Copy the unique Kit embed code above and paste it into the <head> of your project's HTML file or template.
More information at the Font Awesome website." ; ===== Typography (Theme tab) ===== TPL_MOKO-CASSIOPEIA_FONT_LABEL="Fonts Scheme" diff --git a/src/templates/templateDetails.xml b/src/templates/templateDetails.xml index 9aa2481..9d98863 100644 --- a/src/templates/templateDetails.xml +++ b/src/templates/templateDetails.xml @@ -209,7 +209,7 @@ - + From f6ededf2b392d0f9bdc9c9f3d04344562a9d9643 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:04:24 +0000 Subject: [PATCH 3/6] Consolidate JS files and add theme functionality to index.php Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- src/media/js/darkmode-toggle.js | 155 --------------------- src/media/js/template.js | 230 ++++++++++++++++++++++++++++++-- src/media/js/theme-init.js | 93 ------------- src/media/js/user.js | 21 --- src/templates/index.php | 15 ++- src/templates/joomla.asset.json | 24 ---- 6 files changed, 236 insertions(+), 302 deletions(-) delete mode 100644 src/media/js/darkmode-toggle.js delete mode 100644 src/media/js/theme-init.js delete mode 100644 src/media/js/user.js diff --git a/src/media/js/darkmode-toggle.js b/src/media/js/darkmode-toggle.js deleted file mode 100644 index 84b8281..0000000 --- a/src/media/js/darkmode-toggle.js +++ /dev/null @@ -1,155 +0,0 @@ -/* Copyright (C) 2025 Moko Consulting - - This file is part of a Moko Consulting project. - - SPDX-License-Identifier: GPL-3.0-or-later - - # FILE INFORMATION - DEFGROUP: Joomla.Template.Site - INGROUP: Moko-Cassiopeia - PATH: ./media/templates/site/moko-cassiopeia/js/darkmode-toggle.js - VERSION: 03.05.00 - BRIEF: JavaScript logic for dark mode toggle functionality in Moko-Cassiopeia - */ - -(function () { - 'use strict'; - - var STORAGE_KEY = 'theme'; - var docEl = document.documentElement; - var mql = window.matchMedia('(prefers-color-scheme: dark)'); - - function getStored() { try { return localStorage.getItem(STORAGE_KEY); } catch (e) { return null; } } - function setStored(v) { try { localStorage.setItem(STORAGE_KEY, v); } catch (e) {} } - function clearStored() { try { localStorage.removeItem(STORAGE_KEY); } catch (e) {} } - function systemTheme() { return mql.matches ? 'dark' : 'light'; } - - function applyTheme(theme) { - docEl.setAttribute('data-bs-theme', theme); - docEl.setAttribute('data-aria-theme', theme); - var meta = document.querySelector('meta[name="theme-color"]'); - if (meta) { - meta.setAttribute('content', theme === 'dark' ? '#0f1115' : '#ffffff'); - } - var sw = document.getElementById('mokoThemeSwitch'); - if (sw) { - sw.setAttribute('aria-checked', theme === 'dark' ? 'true' : 'false'); - } - } - - function initTheme() { - var stored = getStored(); - applyTheme(stored ? stored : systemTheme()); - } - - function posClassFromBody() { - var pos = (document.body.getAttribute('data-theme-fab-pos') || 'br').toLowerCase(); - if (!/^(br|bl|tr|tl)$/.test(pos)) pos = 'br'; - return 'pos-' + pos; - } - - function buildToggle() { - if (document.getElementById('mokoThemeFab')) return; - - var wrap = document.createElement('div'); - wrap.id = 'mokoThemeFab'; - wrap.className = posClassFromBody(); - - // Light label - var lblL = document.createElement('span'); - lblL.className = 'label'; - lblL.textContent = 'Light'; - - // Switch - var switchWrap = document.createElement('button'); - switchWrap.id = 'mokoThemeSwitch'; - switchWrap.type = 'button'; - switchWrap.setAttribute('role', 'switch'); - switchWrap.setAttribute('aria-label', 'Toggle dark mode'); - switchWrap.setAttribute('aria-checked', 'false'); // updated after init - - var track = document.createElement('span'); - track.className = 'switch'; - var knob = document.createElement('span'); - knob.className = 'knob'; - track.appendChild(knob); - switchWrap.appendChild(track); - - // Dark label - var lblD = document.createElement('span'); - lblD.className = 'label'; - lblD.textContent = 'Dark'; - - // Auto button - var auto = document.createElement('button'); - auto.id = 'mokoThemeAuto'; - auto.type = 'button'; - auto.className = 'btn btn-sm btn-link text-decoration-none px-2'; - auto.setAttribute('aria-label', 'Follow system theme'); - auto.textContent = 'Auto'; - - // Behavior - switchWrap.addEventListener('click', function () { - var current = (docEl.getAttribute('data-bs-theme') || 'light').toLowerCase(); - var next = current === 'dark' ? 'light' : 'dark'; - applyTheme(next); - setStored(next); - }); - - auto.addEventListener('click', function () { - clearStored(); - applyTheme(systemTheme()); - }); - - // Respond to OS changes only when not user-forced - var onMql = function () { - if (!getStored()) applyTheme(systemTheme()); - }; - if (typeof mql.addEventListener === 'function') mql.addEventListener('change', onMql); - else if (typeof mql.addListener === 'function') mql.addListener(onMql); - - // Initial state - var initial = getStored() || systemTheme(); - switchWrap.setAttribute('aria-checked', initial === 'dark' ? 'true' : 'false'); - - // Mount - wrap.appendChild(lblL); - wrap.appendChild(switchWrap); - wrap.appendChild(lblD); - wrap.appendChild(auto); - document.body.appendChild(wrap); - - // Debug helper - window.mokoThemeFabStatus = function () { - var el = document.getElementById('mokoThemeFab'); - if (!el) return { mounted: false }; - var r = el.getBoundingClientRect(); - return { - mounted: true, - rect: { top: r.top, left: r.left, width: r.width, height: r.height }, - zIndex: window.getComputedStyle(el).zIndex, - posClass: el.className - }; - }; - - // Outline if invisible - setTimeout(function () { - var r = wrap.getBoundingClientRect(); - if (r.width < 10 || r.height < 10) { - wrap.classList.add('debug-outline'); - console.warn('[moko] Theme FAB mounted but appears too small — check CSS collisions.'); - } - }, 50); - } - - function init() { - initTheme(); - buildToggle(); - } - - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', init); - } else { - init(); - } -})(); diff --git a/src/media/js/template.js b/src/media/js/template.js index 0d0639f..334db94 100644 --- a/src/media/js/template.js +++ b/src/media/js/template.js @@ -8,13 +8,174 @@ DEFGROUP: Joomla.Template.Site INGROUP: Moko-Cassiopeia PATH: ./media/templates/site/moko-cassiopeia/js/template.js - VERSION: 03.05.00 - BRIEF: Core JavaScript utilities and behaviors for Moko-Cassiopeia template + VERSION: 03.06.01 + BRIEF: Consolidated JavaScript for Moko-Cassiopeia template including theme, TOC, and utilities */ -!function(a){"use strict";window.Toc={helpers:{findOrFilter:function(e,t){var n=e.find(t);return e.filter(t).add(n).filter(":not([data-toc-skip])")},generateUniqueIdBase:function(e){return a(e).text().trim().replace(/\'/gi,"").replace(/[& +$,:;=?@"#{}|^~[`%!'<>\]\.\/\(\)\*\\\n\t\b\v]/g,"-").replace(/-{2,}/g,"-").substring(0,64).replace(/^-+|-+$/gm,"").toLowerCase()||e.tagName.toLowerCase()},generateUniqueId:function(e){for(var t=this.generateUniqueIdBase(e),n=0;;n++){var r=t;if(0')},createChildNavList:function(e){var t=this.createNavList();return e.append(t),t},generateNavEl:function(e,t){var n=a('');n.attr("href","#"+e),n.text(t);var r=a("
  • ");return r.append(n),r},generateNavItem:function(e){var t=this.generateAnchor(e),n=a(e),r=n.data("toc-text")||n.text();return this.generateNavEl(t,r)},getTopLevel:function(e){for(var t=1;t<=6;t++){if(1\]\.\/\(\)\*\\\n\t\b\v]/g,"-").replace(/-{2,}/g,"-").substring(0,64).replace(/^-+|-+$/gm,"").toLowerCase()||e.tagName.toLowerCase()},generateUniqueId:function(e){for(var t=this.generateUniqueIdBase(e),n=0;;n++){var r=t;if(0')},createChildNavList:function(e){var t=this.createNavList();return e.append(t),t},generateNavEl:function(e,t){var n=a('');n.attr("href","#"+e),n.text(t);var r=a("
  • ");return r.append(n),r},generateNavItem:function(e){var t=this.generateAnchor(e),n=a(e),r=n.data("toc-text")||n.text();return this.generateNavEl(t,r)},getTopLevel:function(e){for(var t=1;t<=6;t++){if(1, syncing both data-bs-theme and data-aria-theme. + * @param {"light"|"dark"} theme + */ + function applyTheme(theme) { + root.setAttribute("data-bs-theme", theme); + root.setAttribute("data-aria-theme", theme); + try { localStorage.setItem(storageKey, theme); } catch (e) {} + } + + /** + * Clear stored preference so system preference is followed. + */ + function clearStored() { + try { localStorage.removeItem(storageKey); } catch (e) {} + } + + /** + * Determine system theme. + */ + function systemTheme() { + return mql.matches ? "dark" : "light"; + } + + /** + * Get stored theme preference. + */ + function getStored() { + try { return localStorage.getItem(storageKey); } catch (e) { return null; } + } + + // ======================================================================== + // FLOATING THEME TOGGLE (FAB) + // ======================================================================== + function posClassFromBody() { + var pos = (doc.body.getAttribute('data-theme-fab-pos') || 'br').toLowerCase(); + if (!/^(br|bl|tr|tl)$/.test(pos)) pos = 'br'; + return 'pos-' + pos; + } + + function buildThemeToggle() { + if (doc.getElementById('mokoThemeFab')) return; + + var wrap = doc.createElement('div'); + wrap.id = 'mokoThemeFab'; + wrap.className = posClassFromBody(); + + // Light label + var lblL = doc.createElement('span'); + lblL.className = 'label'; + lblL.textContent = 'Light'; + + // Switch + var switchWrap = doc.createElement('button'); + switchWrap.id = 'mokoThemeSwitch'; + switchWrap.type = 'button'; + switchWrap.setAttribute('role', 'switch'); + switchWrap.setAttribute('aria-label', 'Toggle dark mode'); + switchWrap.setAttribute('aria-checked', 'false'); + + var track = doc.createElement('span'); + track.className = 'switch'; + var knob = doc.createElement('span'); + knob.className = 'knob'; + track.appendChild(knob); + switchWrap.appendChild(track); + + // Dark label + var lblD = doc.createElement('span'); + lblD.className = 'label'; + lblD.textContent = 'Dark'; + + // Auto button + var auto = doc.createElement('button'); + auto.id = 'mokoThemeAuto'; + auto.type = 'button'; + auto.className = 'btn btn-sm btn-link text-decoration-none px-2'; + auto.setAttribute('aria-label', 'Follow system theme'); + auto.textContent = 'Auto'; + + // Behavior + switchWrap.addEventListener('click', function () { + var current = (root.getAttribute('data-bs-theme') || 'light').toLowerCase(); + var next = current === 'dark' ? 'light' : 'dark'; + applyTheme(next); + switchWrap.setAttribute('aria-checked', next === 'dark' ? 'true' : 'false'); + // Update meta theme color + var meta = doc.querySelector('meta[name="theme-color"]'); + if (meta) { + meta.setAttribute('content', next === 'dark' ? '#0f1115' : '#ffffff'); + } + }); + + auto.addEventListener('click', function () { + clearStored(); + var sys = systemTheme(); + applyTheme(sys); + switchWrap.setAttribute('aria-checked', sys === 'dark' ? 'true' : 'false'); + }); + + // Respond to OS changes only when not user-forced + var onMql = function () { + if (!getStored()) { + var sys = systemTheme(); + applyTheme(sys); + switchWrap.setAttribute('aria-checked', sys === 'dark' ? 'true' : 'false'); + } + }; + if (typeof mql.addEventListener === 'function') mql.addEventListener('change', onMql); + else if (typeof mql.addListener === 'function') mql.addListener(onMql); + + // Initial state + var initial = getStored() || systemTheme(); + switchWrap.setAttribute('aria-checked', initial === 'dark' ? 'true' : 'false'); + + // Mount + wrap.appendChild(lblL); + wrap.appendChild(switchWrap); + wrap.appendChild(lblD); + wrap.appendChild(auto); + doc.body.appendChild(wrap); + + // Debug helper + win.mokoThemeFabStatus = function () { + var el = doc.getElementById('mokoThemeFab'); + if (!el) return { mounted: false }; + var r = el.getBoundingClientRect(); + return { + mounted: true, + rect: { top: r.top, left: r.left, width: r.width, height: r.height }, + zIndex: win.getComputedStyle(el).zIndex, + posClass: el.className + }; + }; + + // Outline if invisible + setTimeout(function () { + var r = wrap.getBoundingClientRect(); + if (r.width < 10 || r.height < 10) { + wrap.classList.add('debug-outline'); + console.warn('[moko] Theme FAB mounted but appears too small — check CSS collisions.'); + } + }, 50); + } + + // ======================================================================== + // TEMPLATE UTILITIES + // ======================================================================== + /** * Utility: smooth scroll to top */ @@ -35,10 +196,9 @@ /** * Initialize Bootstrap TOC if #toc element exists. - * Requires bootstrap-toc.min.js to be loaded. */ function initTOC() { - if (typeof win.Toc === "function" && doc.querySelector("#toc")) { + if (typeof win.Toc !== "undefined" && doc.querySelector("#toc")) { win.Toc.init({ $nav: $("#toc"), $scope: $("main") @@ -48,7 +208,6 @@ /** * Initialize offcanvas drawer buttons for left/right drawers. - * Uses Bootstrap's offcanvas component. */ function initDrawers() { var leftBtn = doc.querySelector(".drawer-toggle-left"); @@ -56,13 +215,13 @@ if (leftBtn) { leftBtn.addEventListener("click", function () { var target = doc.querySelector(leftBtn.getAttribute("data-bs-target")); - if (target) new bootstrap.Offcanvas(target).show(); + if (target && typeof bootstrap !== 'undefined') new bootstrap.Offcanvas(target).show(); }); } if (rightBtn) { rightBtn.addEventListener("click", function () { var target = doc.querySelector(rightBtn.getAttribute("data-bs-target")); - if (target) new bootstrap.Offcanvas(target).show(); + if (target && typeof bootstrap !== 'undefined') new bootstrap.Offcanvas(target).show(); }); } } @@ -80,10 +239,65 @@ } } + /** + * Initialize theme based on stored preference or system setting + */ + function initTheme() { + var stored = getStored(); + var theme = stored ? stored : systemTheme(); + applyTheme(theme); + + // Listen for system changes only if Auto mode (no stored) + var onChange = function () { + if (!getStored()) { + applyTheme(systemTheme()); + } + }; + if (typeof mql.addEventListener === "function") { + mql.addEventListener("change", onChange); + } else if (typeof mql.addListener === "function") { + mql.addListener(onChange); + } + + // Hook toggle UI if present (for inline switch, not FAB) + var switchEl = doc.getElementById("themeSwitch"); + var autoBtn = doc.getElementById("themeAuto"); + + if (switchEl) { + switchEl.checked = (theme === "dark"); + switchEl.addEventListener("change", function () { + var choice = switchEl.checked ? "dark" : "light"; + applyTheme(choice); + }); + } + + if (autoBtn) { + autoBtn.addEventListener("click", function () { + clearStored(); + applyTheme(systemTheme()); + }); + } + } + + /** + * Check if theme FAB should be enabled based on body data attribute + */ + function shouldEnableThemeFab() { + return doc.body.getAttribute('data-theme-fab-enabled') === '1'; + } + /** * Run all template JS initializations */ function init() { + // Initialize theme first + initTheme(); + + // Build floating theme toggle if enabled + if (shouldEnableThemeFab()) { + buildThemeToggle(); + } + // Sticky header behavior handleScroll(); win.addEventListener("scroll", handleScroll); diff --git a/src/media/js/theme-init.js b/src/media/js/theme-init.js deleted file mode 100644 index f6486da..0000000 --- a/src/media/js/theme-init.js +++ /dev/null @@ -1,93 +0,0 @@ -/* Copyright (C) 2025 Moko Consulting - - This file is part of a Moko Consulting project. - - SPDX-License-Identifier: GPL-3.0-or-later - - # FILE INFORMATION - DEFGROUP: Joomla.Template.Site - INGROUP: Moko-Cassiopeia - PATH: ./media/templates/site/moko-cassiopeia/js/theme-init.js - VERSION: 03.05.00 - BRIEF: Initialization script for Moko-Cassiopeia theme features and behaviors - */ - -(function (win, doc) { - "use strict"; - - var storageKey = "theme"; // localStorage key - var mql = win.matchMedia("(prefers-color-scheme: dark)"); - var root = doc.documentElement; - - /** - * Apply theme to , syncing both data-bs-theme and data-aria-theme. - * @param {"light"|"dark"} theme - */ - function applyTheme(theme) { - root.setAttribute("data-bs-theme", theme); - root.setAttribute("data-aria-theme", theme); - try { localStorage.setItem(storageKey, theme); } catch (e) {} - } - - /** - * Clear stored preference so system preference is followed. - */ - function clearStored() { - try { localStorage.removeItem(storageKey); } catch (e) {} - } - - /** - * Determine system theme. - */ - function systemTheme() { - return mql.matches ? "dark" : "light"; - } - - /** - * Initialize theme on load. - */ - function init() { - var stored = null; - try { stored = localStorage.getItem(storageKey); } catch (e) {} - - var theme = stored ? stored : systemTheme(); - applyTheme(theme); - - // Listen for system changes only if Auto mode (no stored) - var onChange = function () { - if (!localStorage.getItem(storageKey)) { - applyTheme(systemTheme()); - } - }; - if (typeof mql.addEventListener === "function") { - mql.addEventListener("change", onChange); - } else if (typeof mql.addListener === "function") { - mql.addListener(onChange); - } - - // Hook toggle UI if present - var switchEl = doc.getElementById("themeSwitch"); - var autoBtn = doc.getElementById("themeAuto"); - - if (switchEl) { - switchEl.checked = (theme === "dark"); - switchEl.addEventListener("change", function () { - var choice = switchEl.checked ? "dark" : "light"; - applyTheme(choice); - }); - } - - if (autoBtn) { - autoBtn.addEventListener("click", function () { - clearStored(); - applyTheme(systemTheme()); - }); - } - } - - if (doc.readyState === "loading") { - doc.addEventListener("DOMContentLoaded", init); - } else { - init(); - } -})(window, document); diff --git a/src/media/js/user.js b/src/media/js/user.js deleted file mode 100644 index b85974c..0000000 --- a/src/media/js/user.js +++ /dev/null @@ -1,21 +0,0 @@ -/* Copyright (C) 2025 Moko Consulting - - This file is part of a Moko Consulting project. - - SPDX-License-Identifier: GPL-3.0-or-later - - This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/ . - - # FILE INFORMATION - DEFGROUP: Joomla.Template.Site - INGROUP: Moko-Cassiopeia - REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia - PATH: ./media/templates/site/moko-cassiopeia/js/user.js - VERSION: 03.00.00 - BRIEF: JavaScript for handling user-specific interactions in Moko-Cassiopeia template - */ diff --git a/src/templates/index.php b/src/templates/index.php index 02539fa..255ec24 100644 --- a/src/templates/index.php +++ b/src/templates/index.php @@ -42,6 +42,11 @@ $params_googleanalyticsid = $this->params->get('googleanalyticsid', null); $params_custom_head_start = $this->params->get('custom_head_start', null); $params_custom_head_end = $this->params->get('custom_head_end', null); $params_developmentmode = $this->params->get('developmentmode', false); + +// Theme params +$params_theme_enabled = $this->params->get('theme_enabled', 1); +$params_theme_fab_enabled = $this->params->get('theme_fab_enabled', 1); +$params_theme_fab_pos = $this->params->get('theme_fab_pos', 'br'); /* // Bootstrap behaviors (assets handled via WAM) HTMLHelper::_('bootstrap.framework'); @@ -117,6 +122,11 @@ try { // Scripts $wa->useScript('template.js'); +// Load GTM script if GTM is enabled +if (!empty($params_googletagmanager) && !empty($params_googletagmanagerid)) { + $wa->useScript('gtm.js'); +} + /** * VirtueMart detection: * - Component must exist and be enabled @@ -289,7 +299,10 @@ $wa->useStyle('template.user'); // css/user.css - Date: Fri, 30 Jan 2026 00:05:31 +0000 Subject: [PATCH 4/6] Add user.css placeholder and update version to 03.06.01 Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- src/language/en-GB/tpl_moko-cassiopeia.ini | 2 +- src/language/en-US/tpl_moko-cassiopeia.ini | 2 +- src/media/css/user.css | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 src/media/css/user.css diff --git a/src/language/en-GB/tpl_moko-cassiopeia.ini b/src/language/en-GB/tpl_moko-cassiopeia.ini index 2a5cc5a..06909e1 100644 --- a/src/language/en-GB/tpl_moko-cassiopeia.ini +++ b/src/language/en-GB/tpl_moko-cassiopeia.ini @@ -8,7 +8,7 @@ ; DEFGROUP: Joomla.Template.Site ; INGROUP: Moko-Cassiopeia ; PATH: ./language/en-GB/tpl_moko-cassiopeia.ini -; VERSION: 03.05.00 +; VERSION: 03.06.01 ; BRIEF: English (GB) language strings for the Moko-Cassiopeia Joomla template ; ; ===== Template meta ===== diff --git a/src/language/en-US/tpl_moko-cassiopeia.ini b/src/language/en-US/tpl_moko-cassiopeia.ini index 6df0ef7..4aaa519 100644 --- a/src/language/en-US/tpl_moko-cassiopeia.ini +++ b/src/language/en-US/tpl_moko-cassiopeia.ini @@ -8,7 +8,7 @@ ; DEFGROUP: Joomla.Template.Site ; INGROUP: Moko-Cassiopeia ; PATH: ./language/en-US/tpl_moko-cassiopeia.ini -; VERSION: 03.05.00 +; VERSION: 03.06.01 ; BRIEF: English (US) language strings for the Moko-Cassiopeia Joomla template ; ; ===== Template meta ===== diff --git a/src/media/css/user.css b/src/media/css/user.css new file mode 100644 index 0000000..8514fbc --- /dev/null +++ b/src/media/css/user.css @@ -0,0 +1,16 @@ +@charset "UTF-8"; +/* Copyright (C) 2025 Moko Consulting + + This file is part of a Moko Consulting project. + + SPDX-License-Identifier: GPL-3.0-or-later + + # FILE INFORMATION + DEFGROUP: Joomla.Template.Site + INGROUP: Moko-Cassiopeia + PATH: ./media/templates/site/moko-cassiopeia/css/user.css + VERSION: 03.06.01 + BRIEF: User custom styles for Moko-Cassiopeia template - add your custom CSS here + */ + +/* Add your custom CSS here */ From 4e0d96d522a9adfef27ea7919d67c67103403bf6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:13:21 +0000 Subject: [PATCH 5/6] Fix code review issues: field name consistency and theme_enabled conditional Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com> --- src/templates/index.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/templates/index.php b/src/templates/index.php index 255ec24..f2adfea 100644 --- a/src/templates/index.php +++ b/src/templates/index.php @@ -214,8 +214,8 @@ $stickyHeader = $this->params->get('stickyHeader') ? 'position-sticky sticky-top // Meta $this->setMetaData('viewport', 'width=device-width, initial-scale=1'); -if ($this->params->get('faKitCode')) { - $faKit = "https://kit.fontawesome.com/" . $this->params->get('faKitCode') . ".js"; +if ($this->params->get('fA6KitCode')) { + $faKit = "https://kit.fontawesome.com/" . $this->params->get('fA6KitCode') . ".js"; HTMLHelper::_('script', $faKit, ['crossorigin' => 'anonymous']); } else { try { @@ -260,6 +260,7 @@ $wa->useStyle('template.user'); // css/user.css + +