Merge pull request #68 from mokoconsulting-tech/copilot/fix-missing-language-variables
Ignore colors_custom.css to prevent fork customization commits
This commit was merged in pull request #68.
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -204,6 +204,13 @@ htdocs/cache/
|
|||||||
htdocs/tmp/
|
htdocs/tmp/
|
||||||
htdocs/logs/
|
htdocs/logs/
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Template Customizations
|
||||||
|
# ============================================================
|
||||||
|
# Ignore custom color scheme files to prevent fork-specific customizations
|
||||||
|
src/media/css/colors/light/colors_custom.css
|
||||||
|
src/media/css/colors/dark/colors_custom.css
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Joomla Core
|
# Joomla Core
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
; DEFGROUP: Joomla.Template.Site
|
; DEFGROUP: Joomla.Template.Site
|
||||||
; INGROUP: Moko-Cassiopeia
|
; INGROUP: Moko-Cassiopeia
|
||||||
; PATH: ./language/en-GB/tpl_moko-cassiopeia.ini
|
; 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
|
; BRIEF: English (GB) language strings for the Moko-Cassiopeia Joomla template
|
||||||
;
|
;
|
||||||
; ===== Template meta =====
|
; ===== Template meta =====
|
||||||
@@ -57,8 +57,8 @@ TPL_MOKO-CASSIOPEIA_LOGO_LABEL="Logo"
|
|||||||
TPL_MOKO-CASSIOPEIA_TITLE="Title (alternative to logo)"
|
TPL_MOKO-CASSIOPEIA_TITLE="Title (alternative to logo)"
|
||||||
TPL_MOKO-CASSIOPEIA_TAGLINE_LABEL="Tagline"
|
TPL_MOKO-CASSIOPEIA_TAGLINE_LABEL="Tagline"
|
||||||
TPL_MOKO-CASSIOPEIA_TAGLINE_DESC="Optional text to show as a subheading"
|
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_FA7KITCODE_LABEL="Font Awesome 7 Kit Unique Code"
|
||||||
TPL_MOKO-CASSIOPEIA_FAKITCODE_DESC="<i>If left blank, Font Awesome 7 Free will be used.</i><br>Copy the unique Kit embed code above and paste it into the <head> of your project's HTML file or template.<br><a href='https://fontawesome.com/' target='_blank'>More information at the Font Awesome website.</a>"
|
TPL_MOKO-CASSIOPEIA_FA7KITCODE_DESC="<i>If left blank, Font Awesome 7 Free will be used.</i><br>Copy the unique Kit embed code above and paste it into the <head> of your project's HTML file or template.<br><a href='https://fontawesome.com/' target='_blank'>More information at the Font Awesome website.</a>"
|
||||||
|
|
||||||
; ===== Typography (Theme tab) =====
|
; ===== Typography (Theme tab) =====
|
||||||
TPL_MOKO-CASSIOPEIA_FONT_LABEL="Fonts Scheme"
|
TPL_MOKO-CASSIOPEIA_FONT_LABEL="Fonts Scheme"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
; DEFGROUP: Joomla.Template.Site
|
; DEFGROUP: Joomla.Template.Site
|
||||||
; INGROUP: Moko-Cassiopeia
|
; INGROUP: Moko-Cassiopeia
|
||||||
; PATH: ./language/en-US/tpl_moko-cassiopeia.ini
|
; 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
|
; BRIEF: English (US) language strings for the Moko-Cassiopeia Joomla template
|
||||||
;
|
;
|
||||||
; ===== Template meta =====
|
; ===== Template meta =====
|
||||||
@@ -57,8 +57,8 @@ TPL_MOKO-CASSIOPEIA_LOGO_LABEL="Logo"
|
|||||||
TPL_MOKO-CASSIOPEIA_TITLE="Title (alternative to logo)"
|
TPL_MOKO-CASSIOPEIA_TITLE="Title (alternative to logo)"
|
||||||
TPL_MOKO-CASSIOPEIA_TAGLINE_LABEL="Tagline"
|
TPL_MOKO-CASSIOPEIA_TAGLINE_LABEL="Tagline"
|
||||||
TPL_MOKO-CASSIOPEIA_TAGLINE_DESC="Optional text to show as a subheading"
|
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_FA7KITCODE_LABEL="Font Awesome 7 Kit Unique Code"
|
||||||
TPL_MOKO-CASSIOPEIA_FAKITCODE_DESC="<i>If left blank, Font Awesome 7 Free will be used.</i><br>Copy the unique Kit embed code above and paste it into the <head> of your project's HTML file or template.<br><a href='https://fontawesome.com/' target='_blank'>More information at the Font Awesome website.</a>"
|
TPL_MOKO-CASSIOPEIA_FA7KITCODE_DESC="<i>If left blank, Font Awesome 7 Free will be used.</i><br>Copy the unique Kit embed code above and paste it into the <head> of your project's HTML file or template.<br><a href='https://fontawesome.com/' target='_blank'>More information at the Font Awesome website.</a>"
|
||||||
|
|
||||||
; ===== Typography (Theme tab) =====
|
; ===== Typography (Theme tab) =====
|
||||||
TPL_MOKO-CASSIOPEIA_FONT_LABEL="Fonts Scheme"
|
TPL_MOKO-CASSIOPEIA_FONT_LABEL="Fonts Scheme"
|
||||||
|
|||||||
16
src/media/css/user.css
Normal file
16
src/media/css/user.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
@charset "UTF-8";
|
||||||
|
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
|
||||||
|
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 */
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
@@ -8,13 +8,174 @@
|
|||||||
DEFGROUP: Joomla.Template.Site
|
DEFGROUP: Joomla.Template.Site
|
||||||
INGROUP: Moko-Cassiopeia
|
INGROUP: Moko-Cassiopeia
|
||||||
PATH: ./media/templates/site/moko-cassiopeia/js/template.js
|
PATH: ./media/templates/site/moko-cassiopeia/js/template.js
|
||||||
VERSION: 03.05.00
|
VERSION: 03.06.01
|
||||||
BRIEF: Core JavaScript utilities and behaviors for Moko-Cassiopeia template
|
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<n&&(r+="-"+n),!document.getElementById(r))return r}},generateAnchor:function(e){if(e.id)return e.id;var t=this.generateUniqueId(e);return e.id=t},createNavList:function(){return a('<ul class="nav navbar-nav"></ul>')},createChildNavList:function(e){var t=this.createNavList();return e.append(t),t},generateNavEl:function(e,t){var n=a('<a class="nav-link"></a>');n.attr("href","#"+e),n.text(t);var r=a("<li></li>");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<this.findOrFilter(e,"h"+t).length)return t}return 1},getHeadings:function(e,t){var n="h"+t,r="h"+(t+1);return this.findOrFilter(e,n+","+r)},getNavLevel:function(e){return parseInt(e.tagName.charAt(1),10)},populateNav:function(r,a,e){var i,s=r,c=this;e.each(function(e,t){var n=c.generateNavItem(t);c.getNavLevel(t)===a?s=r:i&&s===r&&(s=c.createChildNavList(i)),s.append(n),i=n})},parseOps:function(e){var t;return(t=e.jquery?{$nav:e}:e).$scope=t.$scope||a(document.body),t}},init:function(e){(e=this.helpers.parseOps(e)).$nav.attr("data-toggle","toc");var t=this.helpers.createChildNavList(e.$nav),n=this.helpers.getTopLevel(e.$scope),r=this.helpers.getHeadings(e.$scope,n);this.helpers.populateNav(t,n,r)}},a(function(){a('nav[data-toggle="toc"]').each(function(e,t){var n=a(t);Toc.init(n)})})}(jQuery);
|
|
||||||
(function (win, doc) {
|
(function (win, doc) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// BOOTSTRAP TOC (inline minified version)
|
||||||
|
// ========================================================================
|
||||||
|
!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<n&&(r+="-"+n),!document.getElementById(r))return r}},generateAnchor:function(e){if(e.id)return e.id;var t=this.generateUniqueId(e);return e.id=t},createNavList:function(){return a('<ul class="nav navbar-nav"></ul>')},createChildNavList:function(e){var t=this.createNavList();return e.append(t),t},generateNavEl:function(e,t){var n=a('<a class="nav-link"></a>');n.attr("href","#"+e),n.text(t);var r=a("<li></li>");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<this.findOrFilter(e,"h"+t).length)return t}return 1},getHeadings:function(e,t){var n="h"+t,r="h"+(t+1);return this.findOrFilter(e,n+","+r)},getNavLevel:function(e){return parseInt(e.tagName.charAt(1),10)},populateNav:function(r,a,e){var i,s=r,c=this;e.each(function(e,t){var n=c.generateNavItem(t);c.getNavLevel(t)===a?s=r:i&&s===r&&(s=c.createChildNavList(i)),s.append(n),i=n})},parseOps:function(e){var t;return(t=e.jquery?{$nav:e}:e).$scope=t.$scope||a(document.body),t}},init:function(e){(e=this.helpers.parseOps(e)).$nav.attr("data-toggle","toc");var t=this.helpers.createChildNavList(e.$nav),n=this.helpers.getTopLevel(e.$scope),r=this.helpers.getHeadings(e.$scope,n);this.helpers.populateNav(t,n,r)}},a(function(){a('nav[data-toggle="toc"]').each(function(e,t){var n=a(t);Toc.init(n)})})}(jQuery);
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// THEME INITIALIZATION (Early theme application)
|
||||||
|
// ========================================================================
|
||||||
|
var storageKey = "theme";
|
||||||
|
var mql = win.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
var root = doc.documentElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply theme to <html>, 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
|
* Utility: smooth scroll to top
|
||||||
*/
|
*/
|
||||||
@@ -35,10 +196,9 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize Bootstrap TOC if #toc element exists.
|
* Initialize Bootstrap TOC if #toc element exists.
|
||||||
* Requires bootstrap-toc.min.js to be loaded.
|
|
||||||
*/
|
*/
|
||||||
function initTOC() {
|
function initTOC() {
|
||||||
if (typeof win.Toc === "function" && doc.querySelector("#toc")) {
|
if (typeof win.Toc !== "undefined" && doc.querySelector("#toc")) {
|
||||||
win.Toc.init({
|
win.Toc.init({
|
||||||
$nav: $("#toc"),
|
$nav: $("#toc"),
|
||||||
$scope: $("main")
|
$scope: $("main")
|
||||||
@@ -48,7 +208,6 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize offcanvas drawer buttons for left/right drawers.
|
* Initialize offcanvas drawer buttons for left/right drawers.
|
||||||
* Uses Bootstrap's offcanvas component.
|
|
||||||
*/
|
*/
|
||||||
function initDrawers() {
|
function initDrawers() {
|
||||||
var leftBtn = doc.querySelector(".drawer-toggle-left");
|
var leftBtn = doc.querySelector(".drawer-toggle-left");
|
||||||
@@ -56,13 +215,13 @@
|
|||||||
if (leftBtn) {
|
if (leftBtn) {
|
||||||
leftBtn.addEventListener("click", function () {
|
leftBtn.addEventListener("click", function () {
|
||||||
var target = doc.querySelector(leftBtn.getAttribute("data-bs-target"));
|
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) {
|
if (rightBtn) {
|
||||||
rightBtn.addEventListener("click", function () {
|
rightBtn.addEventListener("click", function () {
|
||||||
var target = doc.querySelector(rightBtn.getAttribute("data-bs-target"));
|
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
|
* Run all template JS initializations
|
||||||
*/
|
*/
|
||||||
function init() {
|
function init() {
|
||||||
|
// Initialize theme first
|
||||||
|
initTheme();
|
||||||
|
|
||||||
|
// Build floating theme toggle if enabled
|
||||||
|
if (shouldEnableThemeFab()) {
|
||||||
|
buildThemeToggle();
|
||||||
|
}
|
||||||
|
|
||||||
// Sticky header behavior
|
// Sticky header behavior
|
||||||
handleScroll();
|
handleScroll();
|
||||||
win.addEventListener("scroll", handleScroll);
|
win.addEventListener("scroll", handleScroll);
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
|
|
||||||
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 <html>, 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);
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
@@ -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_start = $this->params->get('custom_head_start', null);
|
||||||
$params_custom_head_end = $this->params->get('custom_head_end', null);
|
$params_custom_head_end = $this->params->get('custom_head_end', null);
|
||||||
$params_developmentmode = $this->params->get('developmentmode', false);
|
$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)
|
// Bootstrap behaviors (assets handled via WAM)
|
||||||
HTMLHelper::_('bootstrap.framework');
|
HTMLHelper::_('bootstrap.framework');
|
||||||
@@ -117,6 +122,11 @@ try {
|
|||||||
// Scripts
|
// Scripts
|
||||||
$wa->useScript('template.js');
|
$wa->useScript('template.js');
|
||||||
|
|
||||||
|
// Load GTM script if GTM is enabled
|
||||||
|
if (!empty($params_googletagmanager) && !empty($params_googletagmanagerid)) {
|
||||||
|
$wa->useScript('gtm.js');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VirtueMart detection:
|
* VirtueMart detection:
|
||||||
* - Component must exist and be enabled
|
* - Component must exist and be enabled
|
||||||
@@ -204,8 +214,8 @@ $stickyHeader = $this->params->get('stickyHeader') ? 'position-sticky sticky-top
|
|||||||
// Meta
|
// Meta
|
||||||
$this->setMetaData('viewport', 'width=device-width, initial-scale=1');
|
$this->setMetaData('viewport', 'width=device-width, initial-scale=1');
|
||||||
|
|
||||||
if ($this->params->get('faKitCode')) {
|
if ($this->params->get('fA6KitCode')) {
|
||||||
$faKit = "https://kit.fontawesome.com/" . $this->params->get('faKitCode') . ".js";
|
$faKit = "https://kit.fontawesome.com/" . $this->params->get('fA6KitCode') . ".js";
|
||||||
HTMLHelper::_('script', $faKit, ['crossorigin' => 'anonymous']);
|
HTMLHelper::_('script', $faKit, ['crossorigin' => 'anonymous']);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
@@ -250,6 +260,7 @@ $wa->useStyle('template.user'); // css/user.css
|
|||||||
<?php if (trim($params_custom_head_start)) : ?><?php echo $params_custom_head_start; ?><?php endif; ?>
|
<?php if (trim($params_custom_head_start)) : ?><?php echo $params_custom_head_start; ?><?php endif; ?>
|
||||||
<jdoc:include type="head" />
|
<jdoc:include type="head" />
|
||||||
|
|
||||||
|
<?php if ($params_theme_enabled) : ?>
|
||||||
<script>
|
<script>
|
||||||
// Early theme application to avoid FOUC
|
// Early theme application to avoid FOUC
|
||||||
(function () {
|
(function () {
|
||||||
@@ -258,9 +269,11 @@ $wa->useStyle('template.user'); // css/user.css
|
|||||||
var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
var theme = stored ? stored : (prefersDark ? 'dark' : 'light');
|
var theme = stored ? stored : (prefersDark ? 'dark' : 'light');
|
||||||
document.documentElement.setAttribute('data-bs-theme', theme);
|
document.documentElement.setAttribute('data-bs-theme', theme);
|
||||||
|
document.documentElement.setAttribute('data-aria-theme', theme);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Facebook in-app browser warning banner
|
// Facebook in-app browser warning banner
|
||||||
@@ -289,7 +302,10 @@ $wa->useStyle('template.user'); // css/user.css
|
|||||||
|
|
||||||
<?php if (trim($params_custom_head_end)) : ?><?php echo $params_custom_head_end; ?><?php endif; ?>
|
<?php if (trim($params_custom_head_end)) : ?><?php echo $params_custom_head_end; ?><?php endif; ?>
|
||||||
</head>
|
</head>
|
||||||
<body data-bs-spy="scroll" data-bs-target="#toc" class="site <?php
|
<body data-bs-spy="scroll" data-bs-target="#toc"
|
||||||
|
data-theme-fab-enabled="<?php echo $params_theme_fab_enabled ? '1' : '0'; ?>"
|
||||||
|
data-theme-fab-pos="<?php echo htmlspecialchars($params_theme_fab_pos, ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
class="site <?php
|
||||||
echo $option . ' ' . $wrapper
|
echo $option . ' ' . $wrapper
|
||||||
. ' view-' . $view
|
. ' view-' . $view
|
||||||
. ($layout ? ' layout-' . $layout : ' no-layout')
|
. ($layout ? ' layout-' . $layout : ' no-layout')
|
||||||
|
|||||||
@@ -141,30 +141,6 @@
|
|||||||
"uri": "media/templates/site/moko-cassiopeia/js/template.min.js",
|
"uri": "media/templates/site/moko-cassiopeia/js/template.min.js",
|
||||||
"attributes": {"defer": true}
|
"attributes": {"defer": true}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "theme-init.js",
|
|
||||||
"type": "script",
|
|
||||||
"uri": "media/templates/site/moko-cassiopeia/js/theme-init.js",
|
|
||||||
"attributes": {"defer": true}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "theme-init.min.js",
|
|
||||||
"type": "script",
|
|
||||||
"uri": "media/templates/site/moko-cassiopeia/js/theme-init.min.js",
|
|
||||||
"attributes": {"defer": true}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "darkmode-toggle.js",
|
|
||||||
"type": "script",
|
|
||||||
"uri": "media/templates/site/moko-cassiopeia/js/darkmode-toggle.js",
|
|
||||||
"attributes": {"defer": true}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "darkmode-toggle.min.js",
|
|
||||||
"type": "script",
|
|
||||||
"uri": "media/templates/site/moko-cassiopeia/js/darkmode-toggle.min.js",
|
|
||||||
"attributes": {"defer": true}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "gtm.js",
|
"name": "gtm.js",
|
||||||
"type": "script",
|
"type": "script",
|
||||||
|
|||||||
@@ -209,7 +209,7 @@
|
|||||||
<field name="logoFile" type="media" default="media/templates/site/moko-cassiopeia/images/logo.svg" label="TPL_MOKO-CASSIOPEIA_LOGO_LABEL" showon="brand:1" />
|
<field name="logoFile" type="media" default="media/templates/site/moko-cassiopeia/images/logo.svg" label="TPL_MOKO-CASSIOPEIA_LOGO_LABEL" showon="brand:1" />
|
||||||
<field name="siteTitle" type="text" default="" label="TPL_MOKO-CASSIOPEIA_TITLE" filter="string" showon="brand:1" />
|
<field name="siteTitle" type="text" default="" label="TPL_MOKO-CASSIOPEIA_TITLE" filter="string" showon="brand:1" />
|
||||||
<field name="siteDescription" type="text" default="" label="TPL_MOKO-CASSIOPEIA_TAGLINE_LABEL" description="TPL_MOKO-CASSIOPEIA_TAGLINE_DESC" filter="string" showon="brand:1" />
|
<field name="siteDescription" type="text" default="" label="TPL_MOKO-CASSIOPEIA_TAGLINE_LABEL" description="TPL_MOKO-CASSIOPEIA_TAGLINE_DESC" filter="string" showon="brand:1" />
|
||||||
<field name="fA6KitCode" type="text" default="" label="TPL_MOKO-CASSIOPEIA_FA6KITCODE_LABEL" description="TPL_MOKO-CASSIOPEIA_FA6KITCODE_DESC" filter="string" />
|
<field name="fA6KitCode" type="text" default="" label="TPL_MOKO-CASSIOPEIA_FA7KITCODE_LABEL" description="TPL_MOKO-CASSIOPEIA_FA7KITCODE_DESC" filter="string" />
|
||||||
|
|
||||||
<!-- Header & Navigation UI -->
|
<!-- Header & Navigation UI -->
|
||||||
<field name="theme_sep_header" type="spacer" label="Header & Navigation" hr="false" class="text fw-bold" />
|
<field name="theme_sep_header" type="spacer" label="Header & Navigation" hr="false" class="text fw-bold" />
|
||||||
|
|||||||
Reference in New Issue
Block a user