From 66366682608ef9757657824f0b62f0a882a94ae9 Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Tue, 14 Apr 2026 20:50:10 -0500
Subject: [PATCH 001/120] Add Unicode icon fallback for a11y, fix duplicate
label color
- Add wheelchair symbol fallback when FA7 glyph fails to render
- Fix duplicate #mokoThemeFab .label rule missing color: #fff
Co-Authored-By: Claude Opus 4.6 (1M context)
---
src/media/css/template.css | 1 +
src/media/js/template.js | 12 +++++++++++-
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/media/css/template.css b/src/media/css/template.css
index 5b82fc3..1365d55 100644
--- a/src/media/css/template.css
+++ b/src/media/css/template.css
@@ -17389,6 +17389,7 @@ button#mokoThemeSwitch {
#mokoThemeFab .label {
user-select: none;
font-size: .875rem;
+ color: #fff;
}
#mokoThemeFab.debug-outline {
diff --git a/src/media/js/template.js b/src/media/js/template.js
index f1fb442..49ae3ae 100644
--- a/src/media/js/template.js
+++ b/src/media/js/template.js
@@ -292,7 +292,17 @@
toggle.className = "a11y-toggle";
toggle.setAttribute("aria-label", "Accessibility options");
toggle.setAttribute("aria-expanded", "false");
- toggle.appendChild(faIcon("fa-solid fa-universal-access"));
+ var a11yIcon = faIcon("fa-solid fa-universal-access");
+ // Unicode fallback if FA7 glyph doesn't render (e.g. FA6/FA7 conflict)
+ setTimeout(function () {
+ var cs = win.getComputedStyle(a11yIcon, "::before");
+ if (!cs.content || cs.content === "none" || cs.content === '""' || cs.content === '"" / ""') {
+ a11yIcon.className = "";
+ a11yIcon.textContent = "\u267F";
+ a11yIcon.style.fontSize = "1.1rem";
+ }
+ }, 500);
+ toggle.appendChild(a11yIcon);
// Panel
var panel = doc.createElement("div");
From 37943e28dc41a203a001df68259d8c87e796adce Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Tue, 14 Apr 2026 20:59:10 -0500
Subject: [PATCH 002/120] Fix updates.xml: GitHub fallback URLs, update dev
SHA-256
Co-Authored-By: Claude Opus 4.6 (1M context)
---
updates.xml | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/updates.xml b/updates.xml
index 7c41b38..1742731 100644
--- a/updates.xml
+++ b/updates.xml
@@ -18,9 +18,9 @@
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/development
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.14-dev.zip
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.14-dev.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.14-dev.zip
- 4cbe4fc379182ef17580396e7d12ce4ce95a90017ef364b922bdc2d04b0b3d97
+ 20c49e0940d99e7d682127cd8cb4c1787047e41ab974da4c6535f581b000b615
development
Moko Consulting
https://mokoconsulting.tech
@@ -40,7 +40,7 @@
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/alpha
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.14-alpha.zip
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.14-alpha.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.14-alpha.zip
c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932
alpha
@@ -62,7 +62,7 @@
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/beta
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.14-beta.zip
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.14-beta.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.14-beta.zip
4cbe4fc379182ef17580396e7d12ce4ce95a90017ef364b922bdc2d04b0b3d97
beta
@@ -84,7 +84,7 @@
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/release-candidate
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.14-rc.zip
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.14-rc.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.14-rc.zip
c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932
rc
@@ -106,7 +106,7 @@
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/v03
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.14.zip
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.14.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.14.zip
c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932
stable
From a570b5d4680de9a1517b6b0d000d49bbbc81c1dc Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Tue, 14 Apr 2026 21:04:02 -0500
Subject: [PATCH 003/120] =?UTF-8?q?chore(version):=20bump=2003.09.14=20?=
=?UTF-8?q?=E2=86=92=2003.09.15?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-Authored-By: Claude Opus 4.6 (1M context)
---
README.md | 2 +-
src/joomla.asset.json | 2 +-
src/templateDetails.xml | 4 ++--
updates.xml | 32 ++++++++++++++++----------------
4 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/README.md b/README.md
index 00684c6..5eeac79 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
INGROUP: MokoCassiopeia.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia
FILE: ./README.md
- VERSION: 03.09.14
+ VERSION: 03.09.15
BRIEF: Documentation for MokoCassiopeia template
-->
diff --git a/src/joomla.asset.json b/src/joomla.asset.json
index 16a8f1a..37d692f 100644
--- a/src/joomla.asset.json
+++ b/src/joomla.asset.json
@@ -17,7 +17,7 @@
"defgroup": "Joomla.Template.Site",
"ingroup": "MokoCassiopeia.Template.Assets",
"path": "./media/templates/site/mokocassiopeia/joomla.asset.json",
- "version": "03.09.14",
+ "version": "03.09.15",
"brief": "Joomla asset registry for MokoCassiopeia"
}
},
diff --git a/src/templateDetails.xml b/src/templateDetails.xml
index f928b11..1706f11 100644
--- a/src/templateDetails.xml
+++ b/src/templateDetails.xml
@@ -39,13 +39,13 @@
MokoCassiopeia
- 03.09.14
+ 03.09.15
script.php
2026-04-14
Jonathan Miller || Moko Consulting
hello@mokoconsulting.tech
(C)GNU General Public License Version 3 - 2026 Moko Consulting
-
MokoCassiopeia Template Description MokoCassiopeia continues Joomla's tradition of space-themed default templates— building on the legacy of Solarflare (Joomla 1.0), Milkyway (Joomla 1.5), and Protostar (Joomla 3.0).
This template is a customized fork of the Cassiopeia template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting.
Custom Colour Themes Starter palette files are included with the template. To create a custom colour scheme, copy templates/mokocassiopeia/templates/light.custom.css to media/templates/site/mokocassiopeia/css/theme/light.custom.css, or templates/mokocassiopeia/templates/dark.custom.css to media/templates/site/mokocassiopeia/css/theme/dark.custom.css. Customise the CSS variables to match your brand, then activate your palette in System → Site Templates → MokoCassiopeia → Theme tab by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the CSS Variables tab in template options.
Custom CSS & JavaScript For site-specific styles and scripts that should survive template updates, create the following files:
media/templates/site/mokocassiopeia/css/user.css — loaded on every page for custom CSS overrides. media/templates/site/mokocassiopeia/js/user.js — loaded on every page for custom JavaScript. These files are gitignored and will not be overwritten by template updates.
Code Attribution This template is based on the original Cassiopeia template developed by the Joomla! Project and released under the GNU General Public License.
Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards.
It includes integration with Bootstrap TOC , an open-source table of contents generator by A. Feld, licensed under the MIT License.
All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable.
]]>
+ MokoCassiopeia Template Description MokoCassiopeia continues Joomla's tradition of space-themed default templates— building on the legacy of Solarflare (Joomla 1.0), Milkyway (Joomla 1.5), and Protostar (Joomla 3.0).
This template is a customized fork of the Cassiopeia template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting.
Custom Colour Themes Starter palette files are included with the template. To create a custom colour scheme, copy templates/mokocassiopeia/templates/light.custom.css to media/templates/site/mokocassiopeia/css/theme/light.custom.css, or templates/mokocassiopeia/templates/dark.custom.css to media/templates/site/mokocassiopeia/css/theme/dark.custom.css. Customise the CSS variables to match your brand, then activate your palette in System → Site Templates → MokoCassiopeia → Theme tab by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the CSS Variables tab in template options.
Custom CSS & JavaScript For site-specific styles and scripts that should survive template updates, create the following files:
media/templates/site/mokocassiopeia/css/user.css — loaded on every page for custom CSS overrides. media/templates/site/mokocassiopeia/js/user.js — loaded on every page for custom JavaScript. These files are gitignored and will not be overwritten by template updates.
Code Attribution This template is based on the original Cassiopeia template developed by the Joomla! Project and released under the GNU General Public License.
Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards.
It includes integration with Bootstrap TOC , an open-source table of contents generator by A. Feld, licensed under the MIT License.
All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable.
]]>
1
component.php
diff --git a/updates.xml b/updates.xml
index 1742731..cf3529c 100644
--- a/updates.xml
+++ b/updates.xml
@@ -1,7 +1,7 @@
@@ -13,12 +13,12 @@
mokocassiopeia
template
site
- 03.09.14
+ 03.09.15
2026-04-14
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/development
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.14-dev.zip
- https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.14-dev.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.15-dev.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.15-dev.zip
20c49e0940d99e7d682127cd8cb4c1787047e41ab974da4c6535f581b000b615
development
@@ -35,12 +35,12 @@
mokocassiopeia
template
site
- 03.09.14
+ 03.09.15
2026-04-14
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/alpha
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.14-alpha.zip
- https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.14-alpha.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.15-alpha.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.15-alpha.zip
c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932
alpha
@@ -57,12 +57,12 @@
mokocassiopeia
template
site
- 03.09.14
+ 03.09.15
2026-04-14
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/beta
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.14-beta.zip
- https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.14-beta.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.15-beta.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.15-beta.zip
4cbe4fc379182ef17580396e7d12ce4ce95a90017ef364b922bdc2d04b0b3d97
beta
@@ -79,12 +79,12 @@
mokocassiopeia
template
site
- 03.09.14
+ 03.09.15
2026-04-14
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/release-candidate
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.14-rc.zip
- https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.14-rc.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.15-rc.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.15-rc.zip
c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932
rc
@@ -101,12 +101,12 @@
mokocassiopeia
template
site
- 03.09.14
+ 03.09.15
2026-04-14
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/v03
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.14.zip
- https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.14.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.15.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.15.zip
c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932
stable
From d97ed3bded5b38fcbe514b04ad1e7f5f5362ee68 Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Tue, 14 Apr 2026 21:07:54 -0500
Subject: [PATCH 004/120] chore: update dev SHA-256 for 03.09.15 [skip ci]
Co-Authored-By: Claude Opus 4.6 (1M context)
---
updates.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/updates.xml b/updates.xml
index cf3529c..3fb3a81 100644
--- a/updates.xml
+++ b/updates.xml
@@ -20,7 +20,7 @@
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.15-dev.zip
https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.15-dev.zip
- 20c49e0940d99e7d682127cd8cb4c1787047e41ab974da4c6535f581b000b615
+ 310fc79b042a042c0b518a806018011d5243893f239d000224fb197943fee5e3
development
Moko Consulting
https://mokoconsulting.tech
From 336208826893d1e8bd5733113705d4acb4146fcd Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Tue, 14 Apr 2026 21:18:43 -0500
Subject: [PATCH 005/120] Style a11y button: blue bg, white icon, theme-aware
shade
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Light mode uses darker blue (#1565c0), dark mode uses lighter blue
(#42a5f5). No hover effect — only active ring when panel is open.
Co-Authored-By: Claude Opus 4.6 (1M context)
---
src/media/css/template.css | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/media/css/template.css b/src/media/css/template.css
index 1365d55..5c05495 100644
--- a/src/media/css/template.css
+++ b/src/media/css/template.css
@@ -17207,6 +17207,15 @@ button#mokoThemeSwitch {
}
/* Inline a11y toggle inside theme FAB */
+/* Light mode: darker blue */
+:root[data-bs-theme="light"] .a11y-toggle-inline {
+ --a11y-btn-bg: #1565c0;
+}
+/* Dark mode: lighter blue */
+:root[data-bs-theme="dark"] .a11y-toggle-inline {
+ --a11y-btn-bg: #42a5f5;
+}
+
.a11y-toggle-inline {
display: flex;
align-items: center;
@@ -17214,25 +17223,16 @@ button#mokoThemeSwitch {
width: 28px;
height: 28px;
border-radius: 50%;
- border: 1.5px solid currentColor;
- background: transparent;
- color: inherit;
+ border: none;
+ background: var(--a11y-btn-bg, #1976d2);
+ color: #fff;
font-size: 1rem;
cursor: pointer;
padding: 0;
- transition: background .2s, color .2s;
- opacity: .8;
-}
-
-.a11y-toggle-inline:hover,
-.a11y-toggle-inline:focus-visible {
- opacity: 1;
- background: rgba(255,255,255,.15);
}
.a11y-toggle-inline.active {
- opacity: 1;
- background: rgba(255,255,255,.25);
+ box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--a11y-btn-bg, #1976d2);
}
/* Floating a11y panel when inline */
From 4e66562958faa5532667e5d2c99194490bc4c868 Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Tue, 14 Apr 2026 21:34:42 -0500
Subject: [PATCH 006/120] Remove redundant #mokoThemeFab button color rule
Co-Authored-By: Claude Opus 4.6 (1M context)
---
src/media/css/template.css | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/media/css/template.css b/src/media/css/template.css
index 5c05495..5a5daa9 100644
--- a/src/media/css/template.css
+++ b/src/media/css/template.css
@@ -17136,10 +17136,6 @@ button#mokoThemeSwitch {
color: #fff;
}
-#mokoThemeFab button {
- color: #fff;
-}
-
/* Auto toggle switch (on/off style) */
.auto-toggle-wrap {
display: flex;
From 2a7c173f7bc30c768cc7520816621b4c7fcf4e0b Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Tue, 14 Apr 2026 21:43:28 -0500
Subject: [PATCH 007/120] Replace light/dark switch with sun/moon icon button
- Remove toggle switch, Light/Dark labels, knob/track CSS
- Single button with sun (light) and moon (dark) FA icons
- Icons cross-fade with rotation transition on theme change
- Compact circular button matches FAB aesthetic
- Removed duplicate old switch CSS rules
Co-Authored-By: Claude Opus 4.6 (1M context)
---
src/media/css/template.css | 110 ++++++++++++++-----------------------
src/media/js/template.js | 49 +++++++++--------
2 files changed, 65 insertions(+), 94 deletions(-)
diff --git a/src/media/css/template.css b/src/media/css/template.css
index 5a5daa9..b144a33 100644
--- a/src/media/css/template.css
+++ b/src/media/css/template.css
@@ -17064,7 +17064,7 @@ form .form-select {
display: flex;
align-items: center;
gap: .5rem;
- padding: calc(var(--padding-x, 0.25rem) * 2) calc(var(--padding-y, 0.25rem) * 3) calc(var(--padding-x, 0.25rem) * 2) calc(var(--padding-y, 0.25rem) * 8);
+ padding: .4rem .6rem;
border-radius: 999px;
border: none;
background: var(--muted-color, #6d757e);
@@ -17094,46 +17094,47 @@ form .form-select {
top: 1rem;
}
-#mokoThemeFab .switch {
- display: inline-flex;
+/* Sun/Moon theme toggle button */
+.theme-icon-btn {
+ display: flex;
align-items: center;
- position: relative;
- width: 44px;
- height: 24px;
- background: var(--secondary-color, #e6ebf1bf);
- transition: background .2s, border-color .2s;
- border-radius: var(--border-radius-xxl, 2rem);
-}
-
-#mokoThemeFab .knob {
- position: absolute;
- top: 2px;
- left: 2px;
- width: 20px;
- height: 20px;
- border-radius: var(--border-radius-xxl, 2rem);
- background: var(--bs-body-bg, #fff);
- box-shadow: var(--box-shadow, 0 .5rem 1rem #00000066);
- transition: transform .2s ease;
-}
-
-#mokoThemeFab [role="switch"][aria-checked="true"] .knob {
- transform: translateX(20px);
-}
-
-#mokoThemeFab [role="switch"][aria-checked="true"] .switch {
- background: rgba(var(--secondary-color, #e6ebf1bf), .15);
-}
-
-button#mokoThemeSwitch {
- border: unset;
- background-color: unset;
-}
-
-#mokoThemeFab .label {
- user-select: none;
- font-size: .875rem;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border: none;
+ border-radius: 50%;
+ background: rgba(255,255,255,.15);
color: #fff;
+ font-size: 1.1rem;
+ cursor: pointer;
+ padding: 0;
+ position: relative;
+}
+
+.theme-icon-btn .fa-sun,
+.theme-icon-btn .fa-moon {
+ position: absolute;
+ transition: opacity .2s, transform .2s;
+}
+
+/* Light mode: show sun, hide moon */
+.theme-icon-btn.is-light .fa-sun {
+ opacity: 1;
+ transform: rotate(0deg);
+}
+.theme-icon-btn.is-light .fa-moon {
+ opacity: 0;
+ transform: rotate(-90deg);
+}
+
+/* Dark mode: show moon, hide sun */
+.theme-icon-btn.is-dark .fa-moon {
+ opacity: 1;
+ transform: rotate(0deg);
+}
+.theme-icon-btn.is-dark .fa-sun {
+ opacity: 0;
+ transform: rotate(90deg);
}
/* Auto toggle switch (on/off style) */
@@ -17357,37 +17358,6 @@ body.site.error-page {
text-decoration: none;
}
-#mokoThemeFab .knob {
- position: absolute;
- top: 2px;
- left: 2px;
- width: 20px;
- height: 20px;
- border-radius: var(--border-radius-xxl, 2rem);
- background: var(--bs-body-bg, #fff);
- box-shadow: var(--box-shadow, 0 .5rem 1rem #00000066);
- transition: transform .2s ease;
-}
-
-#mokoThemeFab [role="switch"][aria-checked="true"] .knob {
- transform: translateX(20px);
-}
-
-#mokoThemeFab [role="switch"][aria-checked="true"] .switch {
- background: rgba(var(--secondary-color, #e6ebf1bf), .15);
-}
-
-button#mokoThemeSwitch {
- border: unset;
- background-color: unset;
-}
-
-#mokoThemeFab .label {
- user-select: none;
- font-size: .875rem;
- color: #fff;
-}
-
#mokoThemeFab.debug-outline {
outline: 2px dashed var(--pink, #ff8fc0);
outline-offset: 2px;
diff --git a/src/media/js/template.js b/src/media/js/template.js
index 49ae3ae..94a6909 100644
--- a/src/media/js/template.js
+++ b/src/media/js/template.js
@@ -62,30 +62,33 @@
wrap.id = 'mokoThemeFab';
wrap.className = posClassFromBody();
- // Light label
- var lblL = doc.createElement('span');
- lblL.className = 'label';
- lblL.textContent = 'Light';
-
- // Switch
+ // Sun/Moon toggle button
var switchWrap = doc.createElement('button');
switchWrap.id = 'mokoThemeSwitch';
switchWrap.type = 'button';
- switchWrap.setAttribute('role', 'switch');
+ switchWrap.className = 'theme-icon-btn';
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);
+ var sunIcon = doc.createElement('i');
+ sunIcon.className = 'fa-solid fa-sun';
+ sunIcon.setAttribute('aria-hidden', 'true');
- // Dark label
- var lblD = doc.createElement('span');
- lblD.className = 'label';
- lblD.textContent = 'Dark';
+ var moonIcon = doc.createElement('i');
+ moonIcon.className = 'fa-solid fa-moon';
+ moonIcon.setAttribute('aria-hidden', 'true');
+
+ switchWrap.appendChild(sunIcon);
+ switchWrap.appendChild(moonIcon);
+
+ function updateThemeIcon(theme) {
+ if (theme === 'dark') {
+ switchWrap.classList.add('is-dark');
+ switchWrap.classList.remove('is-light');
+ } else {
+ switchWrap.classList.add('is-light');
+ switchWrap.classList.remove('is-dark');
+ }
+ }
// Auto toggle (on/off switch style)
var autoWrap = doc.createElement('div');
@@ -127,7 +130,7 @@
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');
+ updateThemeIcon(next);
// Turn off auto when manually switching
auto.classList.remove('on');
auto.setAttribute('aria-checked', 'false');
@@ -145,7 +148,7 @@
clearStored();
var sys = systemTheme();
applyTheme(sys);
- switchWrap.setAttribute('aria-checked', sys === 'dark' ? 'true' : 'false');
+ updateThemeIcon(sys);
}
});
@@ -154,7 +157,7 @@
if (!getStored()) {
var sys = systemTheme();
applyTheme(sys);
- switchWrap.setAttribute('aria-checked', sys === 'dark' ? 'true' : 'false');
+ updateThemeIcon(sys);
}
};
if (typeof mql.addEventListener === 'function') mql.addEventListener('change', onMql);
@@ -162,12 +165,10 @@
// Initial state
var initial = getStored() || systemTheme();
- switchWrap.setAttribute('aria-checked', initial === 'dark' ? 'true' : 'false');
+ updateThemeIcon(initial);
// Mount
- wrap.appendChild(lblL);
wrap.appendChild(switchWrap);
- wrap.appendChild(lblD);
wrap.appendChild(autoWrap);
wrap.appendChild(divider);
wrap.appendChild(a11ySlot);
From 3c9cd8383c43646f0c285ab9a50b45ff88ea0ee8 Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Tue, 14 Apr 2026 23:31:18 -0500
Subject: [PATCH 008/120] Upated Module: footer
---
src/media/css/template.css | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/media/css/template.css b/src/media/css/template.css
index b144a33..889b8a3 100644
--- a/src/media/css/template.css
+++ b/src/media/css/template.css
@@ -15771,7 +15771,13 @@ body.wrapper-fluid header>.grid-child {
}
footer .grid-child>div {
- padding: var(--navbar-padding-y, 1rem) var(--navbar-padding-x, 1rem) 0;
+ padding: calc(var(--navbar-padding-y, 1rem) * 3)
+ calc(var(--navbar-padding-x, 1rem) * 1)
+ 0;
+}
+
+.mod-footer {
+ border-top: 1px solid var(--border-gray, #b2bfcds);
}
header .grid-child .navbar-brand {
From cb4468af19a83daaff23db29b31a5ed16c7d0b59 Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Thu, 16 Apr 2026 00:56:20 -0500
Subject: [PATCH 009/120] v03.09.16: brand-aside columns, offline page
redesign, variable click-to-copy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Brand-aside position now uses flex columns like top-a (card style, equal-width)
- Offline page: external offline.css with theme variables, 3-column centered card
layout, Osaka font loading, full-screen on mobile
- CSS variable click-to-copy: scans text for --var patterns, wraps in clickable
chips with toast notification on copy
- Search button border matches input border (--input-border-color)
- mod_stats override: converted from dl to table layout
- Patch bump 03.09.15 → 03.09.16
Co-Authored-By: Claude Opus 4.6 (1M context)
---
README.md | 2 +-
src/html/mod_stats/default.php | 16 ++-
src/index.php | 2 +-
src/joomla.asset.json | 2 +-
src/media/css/offline.css | 237 +++++++++++++++++++++++++++++++++
src/media/css/template.css | 53 +++++++-
src/media/js/template.js | 149 +++++++++++++++++++++
src/offline.php | 204 ++++++++++++++--------------
src/templateDetails.xml | 6 +-
updates.xml | 32 ++---
10 files changed, 568 insertions(+), 135 deletions(-)
create mode 100644 src/media/css/offline.css
diff --git a/README.md b/README.md
index 5eeac79..6497f4f 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
INGROUP: MokoCassiopeia.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia
FILE: ./README.md
- VERSION: 03.09.15
+ VERSION: 03.09.16
BRIEF: Documentation for MokoCassiopeia template
-->
diff --git a/src/html/mod_stats/default.php b/src/html/mod_stats/default.php
index d6aa1fd..7b6fdcf 100644
--- a/src/html/mod_stats/default.php
+++ b/src/html/mod_stats/default.php
@@ -26,10 +26,14 @@ $headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'U
showtitle) : ?>
< class="mod-stats__title">title; ?>>
-
-
- title; ?>
- data; ?>
-
-
+
+
+
+
+ title; ?>
+ data; ?>
+
+
+
+
diff --git a/src/index.php b/src/index.php
index 1198dca..e30eb97 100644
--- a/src/index.php
+++ b/src/index.php
@@ -406,7 +406,7 @@ $wa->useScript('user.js'); // js/user.js
countModules('brand-aside', true)) : ?>
-
+
diff --git a/src/joomla.asset.json b/src/joomla.asset.json
index 37d692f..f51468b 100644
--- a/src/joomla.asset.json
+++ b/src/joomla.asset.json
@@ -17,7 +17,7 @@
"defgroup": "Joomla.Template.Site",
"ingroup": "MokoCassiopeia.Template.Assets",
"path": "./media/templates/site/mokocassiopeia/joomla.asset.json",
- "version": "03.09.15",
+ "version": "03.09.16",
"brief": "Joomla asset registry for MokoCassiopeia"
}
},
diff --git a/src/media/css/offline.css b/src/media/css/offline.css
new file mode 100644
index 0000000..19c27f8
--- /dev/null
+++ b/src/media/css/offline.css
@@ -0,0 +1,237 @@
+/* Copyright (C) 2026 Moko Consulting
+
+ This file is part of a Moko Consulting project.
+
+ SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+/* === Offline Page Layout === */
+.moko-offline-wrap {
+ min-height: 100vh;
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+ background-color: var(--body-bg, #0e1318);
+ color: var(--body-font-color, #e6ebf1);
+ font-family: var(--body-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);
+}
+
+.moko-offline-main {
+ display: grid;
+ place-items: center;
+ padding: 2rem 1rem;
+}
+
+/* === Centered Card Grid === */
+.moko-offline-grid {
+ display: grid;
+ grid-template-columns: 1fr minmax(0, 720px) 1fr;
+ width: 100%;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.moko-offline-grid__side {
+ display: block;
+}
+
+.moko-offline-grid__center {
+ min-width: 0;
+}
+
+@media (max-width: 767.98px) {
+ .moko-offline-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .moko-offline-grid__side {
+ display: none;
+ }
+
+ .moko-offline-main {
+ padding: 0;
+ }
+
+ .moko-offline-card {
+ border-radius: 0;
+ border: none;
+ min-height: 100%;
+ }
+}
+
+/* === Card === */
+.moko-offline-card {
+ background-color: var(--card-bg, var(--secondary-bg, #151b22));
+ border: var(--card-border-width, 1px) solid var(--card-border-color, var(--border-color, #2b323b));
+ border-radius: var(--border-radius, 0.25rem);
+ box-shadow: var(--box-shadow, 0 0.5rem 1rem rgba(0, 0, 0, 0.26));
+ padding: 2.5rem;
+}
+
+@media (min-width: 768px) {
+ .moko-offline-card {
+ padding: 3rem;
+ }
+}
+
+/* === Brand / Logo === */
+.moko-offline-brand {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ gap: 0.75rem;
+ text-decoration: none;
+ color: var(--body-font-color, #e6ebf1);
+ margin-bottom: 2rem;
+}
+
+.moko-offline-brand:hover {
+ color: var(--color-link, #8ab4f8);
+}
+
+.moko-offline-brand img {
+ max-width: 200px;
+ max-height: 80px;
+ width: auto;
+ height: auto;
+}
+
+.moko-offline-brand .site-title {
+ font-size: 1.75rem;
+ font-weight: 700;
+ font-family: 'Osaka', var(--body-font-family, sans-serif);
+}
+
+.moko-offline-brand .brand-tagline {
+ display: block;
+ opacity: 0.75;
+ font-size: 0.875rem;
+ line-height: 1.2;
+}
+
+/* === Header === */
+.moko-offline-header {
+ background-color: var(--header-bg, var(--color-primary, #112855));
+ color: var(--mainmenu-nav-link-color, #fff);
+ padding: 1rem 0;
+}
+
+.moko-offline-header .container {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 1rem;
+}
+
+.moko-offline-header .moko-brand {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ text-decoration: none;
+ color: var(--mainmenu-nav-link-color, #fff);
+}
+
+.moko-offline-header .moko-brand img {
+ max-width: 180px;
+ max-height: 60px;
+ width: auto;
+ height: auto;
+}
+
+.moko-offline-header .moko-brand .brand-tagline {
+ display: block;
+ opacity: 0.75;
+ font-size: 0.875rem;
+ line-height: 1.2;
+}
+
+/* === Content Typography === */
+.moko-offline-card h1 {
+ color: var(--heading-color, var(--body-font-color, #f1f5f9));
+ font-weight: 700;
+}
+
+.moko-offline-card .lead {
+ color: var(--gray-600, #48525d);
+ line-height: 1.6;
+}
+
+/* === Accordion === */
+.moko-offline-card .accordion-item {
+ background-color: var(--card-bg, var(--secondary-bg, #151b22));
+ border-color: var(--border-color, #2b323b);
+}
+
+.moko-offline-card .accordion-button {
+ background-color: var(--card-bg, var(--secondary-bg, #151b22));
+ color: var(--body-font-color, #e6ebf1);
+}
+
+.moko-offline-card .accordion-button:not(.collapsed) {
+ background-color: var(--card-cap-bg, rgba(255, 255, 255, 0.03));
+ color: var(--body-font-color, #e6ebf1);
+}
+
+.moko-offline-card .accordion-body {
+ background-color: var(--card-bg, var(--secondary-bg, #151b22));
+}
+
+/* === Form Controls === */
+.moko-offline-card .form-control {
+ background-color: var(--input-bg, #1a2332);
+ border-color: var(--input-border-color, #3a4250);
+ color: var(--input-color, #e6ebf1);
+}
+
+.moko-offline-card .form-control:focus {
+ border-color: var(--input-focus-border-color, #5472ff);
+ box-shadow: var(--input-focus-box-shadow, 0 0 0 0.25rem rgba(84, 114, 255, 0.25));
+}
+
+.moko-offline-card .form-label {
+ color: var(--body-font-color, #e6ebf1);
+}
+
+.moko-offline-card .form-check-label {
+ color: var(--body-font-color, #e6ebf1);
+}
+
+/* === Button === */
+.moko-offline-card .btn-primary {
+ background-color: var(--color-primary, #112855);
+ border-color: var(--color-primary, #112855);
+ color: var(--mainmenu-nav-link-color, #fff);
+}
+
+.moko-offline-card .btn-primary:hover {
+ background-color: var(--color-hover, gray);
+ border-color: var(--color-hover, gray);
+}
+
+/* === Links === */
+.moko-offline-card a {
+ color: var(--link-color, var(--color-link, #8ab4f8));
+}
+
+.moko-offline-card a:hover {
+ color: var(--link-hover-color, #c3d6ff);
+}
+
+/* === Skip Link === */
+.skip-link {
+ position: absolute;
+ left: -9999px;
+ top: auto;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
+}
+
+.skip-link:focus {
+ position: static;
+ width: auto;
+ height: auto;
+ padding: 0.5rem 1rem;
+}
diff --git a/src/media/css/template.css b/src/media/css/template.css
index 889b8a3..40ffadd 100644
--- a/src/media/css/template.css
+++ b/src/media/css/template.css
@@ -14233,6 +14233,28 @@ fieldset>* {
margin-inline-start: auto;
display: flex;
align-items: center;
+ gap: 1em;
+}
+
+.container-brand-aside>* {
+ flex: 1;
+ margin: 0.5em 0;
+}
+
+@media (max-width: 991.98px) {
+ .header-brand-wrap {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .container-brand-aside {
+ margin-inline-start: 0;
+ flex-direction: column;
+ }
+
+ .container-brand-aside>* {
+ flex: 0 1 auto;
+ }
}
.container-header .navbar-brand {
@@ -18659,7 +18681,7 @@ nav[data-toggle=toc] .nav-link.active+ul{
flex: 0 0 auto;
background-color: var(--color-primary, #112855);
color: var(--mainmenu-nav-link-color, #fff);
- border-color: var(--color-primary, #112855);
+ border: 1px solid var(--input-border-color, #3a4250);
padding: 0.6rem 1rem;
border-radius: 0 0.25rem 0.25rem 0;
transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
@@ -18668,7 +18690,7 @@ nav[data-toggle=toc] .nav-link.active+ul{
.mod-finder__search.input-group button:hover,
.container-search button[type="submit"]:hover {
background-color: var(--color-hover, gray);
- border-color: var(--color-hover, gray);
+ border-color: var(--input-border-color, #3a4250);
}
.mod-finder__search.input-group button:focus,
@@ -21637,6 +21659,33 @@ nav[data-toggle=toc] .nav-link.active+ul{
color: var(--gray-600, #48525d);
}
+/* === mod_stats === */
+.mod_stats__table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.mod_stats__table tr {
+ border-bottom: 1px solid var(--border-color, #2b323b);
+}
+
+.mod_stats__table tr:last-child {
+ border-bottom: none;
+}
+
+.mod_stats__label {
+ text-align: start;
+ font-weight: 600;
+ padding: 0.6rem 1rem 0.6rem 0;
+ color: var(--body-font-color, #e6ebf1);
+}
+
+.mod_stats__data {
+ text-align: end;
+ padding: 0.6rem 0;
+ color: var(--gray-600, #48525d);
+}
+
/* === Mobile Responsive Adjustments === */
@media (max-width: 575.98px) {
.mod-kunena-login__input {
diff --git a/src/media/js/template.js b/src/media/js/template.js
index 94a6909..7d65301 100644
--- a/src/media/js/template.js
+++ b/src/media/js/template.js
@@ -641,6 +641,154 @@
});
}
+ // ========================================================================
+ // CSS VARIABLE CLICK-TO-COPY
+ // ========================================================================
+
+ /**
+ * Inject toast + variable-chip styles once.
+ */
+ function injectVarCopyStyles() {
+ if (doc.getElementById("moko-var-copy-styles")) return;
+ var style = doc.createElement("style");
+ style.id = "moko-var-copy-styles";
+ style.textContent =
+ ".moko-var-chip{cursor:pointer;font-family:var(--font-monospace,monospace);font-size:.875em;" +
+ "background:var(--secondary-bg,#151b22);color:var(--link-color,#8ab4f8);" +
+ "border:1px solid var(--border-color,#2b323b);border-radius:.25rem;padding:.1em .4em;" +
+ "transition:background .15s,border-color .15s;white-space:nowrap;display:inline}" +
+ ".moko-var-chip:hover{background:var(--color-primary,#112855);color:#fff;border-color:var(--color-primary,#112855)}" +
+ ".moko-toast{position:fixed;bottom:1.5rem;left:50%;transform:translateX(-50%);z-index:10000;" +
+ "background:var(--color-primary,#112855);color:#fff;padding:.6rem 1.25rem;" +
+ "border-radius:.375rem;font-size:.875rem;box-shadow:0 4px 12px rgba(0,0,0,.25);" +
+ "opacity:0;transition:opacity .2s;pointer-events:none}" +
+ ".moko-toast--show{opacity:1}";
+ doc.head.appendChild(style);
+ }
+
+ /**
+ * Show a brief "Copied to clipboard" toast.
+ * @param {string} text - The variable name that was copied
+ */
+ function showCopyToast(text) {
+ var existing = doc.querySelector(".moko-toast");
+ if (existing) existing.remove();
+
+ var toast = doc.createElement("div");
+ toast.className = "moko-toast";
+ toast.textContent = "Copied to clipboard: " + text;
+ doc.body.appendChild(toast);
+
+ // Trigger reflow then show
+ void toast.offsetWidth;
+ toast.classList.add("moko-toast--show");
+
+ setTimeout(function () {
+ toast.classList.remove("moko-toast--show");
+ setTimeout(function () { toast.remove(); }, 200);
+ }, 2000);
+ }
+
+ /**
+ * Copy text to clipboard and show toast.
+ * @param {string} text
+ */
+ function copyVariable(text) {
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(text).then(function () {
+ showCopyToast(text);
+ });
+ } else {
+ // Fallback for older browsers using deprecated API
+ var ta = doc.createElement("textarea");
+ ta.value = text;
+ ta.style.cssText = "position:fixed;left:-9999px";
+ doc.body.appendChild(ta);
+ ta.select();
+ try { doc.execCommand("copy"); } catch (e) { /* noop */ }
+ ta.remove();
+ showCopyToast(text);
+ }
+ }
+
+ /**
+ * Scan text nodes for CSS variable patterns (--variable-name) and wrap
+ * each match in a clickable chip that copies the variable to clipboard.
+ */
+ function initVarCopy() {
+ injectVarCopyStyles();
+
+ // Pattern: --[a-zA-Z] followed by word/hyphen chars
+ var varPattern = /--[a-zA-Z][\w-]*/g;
+
+ // Elements to skip (inputs, scripts, styles, already-processed, code editors)
+ var SKIP_TAGS = { SCRIPT: 1, STYLE: 1, TEXTAREA: 1, INPUT: 1, SELECT: 1, NOSCRIPT: 1 };
+
+ var walker = doc.createTreeWalker(
+ doc.body,
+ NodeFilter.SHOW_TEXT,
+ {
+ acceptNode: function (node) {
+ if (SKIP_TAGS[node.parentNode.tagName]) return NodeFilter.FILTER_REJECT;
+ if (node.parentNode.classList && node.parentNode.classList.contains("moko-var-chip")) return NodeFilter.FILTER_REJECT;
+ if (!varPattern.test(node.nodeValue)) return NodeFilter.FILTER_REJECT;
+ varPattern.lastIndex = 0;
+ return NodeFilter.FILTER_ACCEPT;
+ }
+ }
+ );
+
+ var textNodes = [];
+ while (walker.nextNode()) textNodes.push(walker.currentNode);
+
+ textNodes.forEach(function (node) {
+ var text = node.nodeValue;
+ var frag = doc.createDocumentFragment();
+ var lastIndex = 0;
+ var match;
+
+ varPattern.lastIndex = 0;
+ while ((match = varPattern.exec(text)) !== null) {
+ // Text before the match
+ if (match.index > lastIndex) {
+ frag.appendChild(doc.createTextNode(text.slice(lastIndex, match.index)));
+ }
+
+ // Clickable chip
+ var chip = doc.createElement("span");
+ chip.className = "moko-var-chip";
+ chip.textContent = match[0];
+ chip.setAttribute("role", "button");
+ chip.setAttribute("tabindex", "0");
+ chip.setAttribute("title", "Click to copy " + match[0]);
+ chip.addEventListener("click", (function (varName) {
+ return function (e) {
+ e.preventDefault();
+ copyVariable(varName);
+ };
+ })(match[0]));
+ chip.addEventListener("keydown", (function (varName) {
+ return function (e) {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ copyVariable(varName);
+ }
+ };
+ })(match[0]));
+ frag.appendChild(chip);
+
+ lastIndex = match.index + match[0].length;
+ }
+
+ // Remaining text after last match
+ if (lastIndex < text.length) {
+ frag.appendChild(doc.createTextNode(text.slice(lastIndex)));
+ }
+
+ node.parentNode.replaceChild(frag, node);
+ });
+ }
+
/**
* Run all template JS initializations
*/
@@ -667,6 +815,7 @@
initBackTop();
initSearchToggle();
initSidebarAccordion();
+ initVarCopy();
}
if (doc.readyState === "loading") {
diff --git a/src/offline.php b/src/offline.php
index 004dabb..196eea3 100644
--- a/src/offline.php
+++ b/src/offline.php
@@ -38,6 +38,10 @@ $base = rtrim(Uri::root(true), '/') . '/templates/' . $this->template . '
$jsBase = rtrim(Uri::root(true), '/') . '/templates/' . $this->template . '/js/';
$doc->addStyleSheet($base . 'template' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-template']);
+$doc->addStyleSheet($base . 'offline' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-offline']);
+
+/* Load Osaka font for site title */
+$doc->addStyleSheet($base . 'fonts/osaka.css', ['version' => 'auto'], ['id' => 'moko-font-osaka']);
/* Load theme palettes */
$doc->addStyleSheet($base . 'theme/light.standard' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-light-standard']);
@@ -168,15 +172,6 @@ if (class_exists('\Joomla\Component\Users\Site\Helper\RouteHelper')) {
-
-
+ countModules('offline-header')) : ?>
+
+
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
- countModules('offline')) : ?>
-
-
+
+
+
+
+
-
-
-
diff --git a/src/templateDetails.xml b/src/templateDetails.xml
index 1706f11..5499cb6 100644
--- a/src/templateDetails.xml
+++ b/src/templateDetails.xml
@@ -39,13 +39,13 @@
MokoCassiopeia
-
03.09.15
+
03.09.16
script.php
-
2026-04-14
+
2026-04-15
Jonathan Miller || Moko Consulting
hello@mokoconsulting.tech
(C)GNU General Public License Version 3 - 2026 Moko Consulting
-
MokoCassiopeia Template Description MokoCassiopeia continues Joomla's tradition of space-themed default templates— building on the legacy of Solarflare (Joomla 1.0), Milkyway (Joomla 1.5), and Protostar (Joomla 3.0).
This template is a customized fork of the Cassiopeia template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting.
Custom Colour Themes Starter palette files are included with the template. To create a custom colour scheme, copy templates/mokocassiopeia/templates/light.custom.css to media/templates/site/mokocassiopeia/css/theme/light.custom.css, or templates/mokocassiopeia/templates/dark.custom.css to media/templates/site/mokocassiopeia/css/theme/dark.custom.css. Customise the CSS variables to match your brand, then activate your palette in System → Site Templates → MokoCassiopeia → Theme tab by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the CSS Variables tab in template options.
Custom CSS & JavaScript For site-specific styles and scripts that should survive template updates, create the following files:
media/templates/site/mokocassiopeia/css/user.css — loaded on every page for custom CSS overrides. media/templates/site/mokocassiopeia/js/user.js — loaded on every page for custom JavaScript. These files are gitignored and will not be overwritten by template updates.
Code Attribution This template is based on the original Cassiopeia template developed by the Joomla! Project and released under the GNU General Public License.
Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards.
It includes integration with Bootstrap TOC , an open-source table of contents generator by A. Feld, licensed under the MIT License.
All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable.
]]>
+
MokoCassiopeia Template Description MokoCassiopeia continues Joomla's tradition of space-themed default templates— building on the legacy of Solarflare (Joomla 1.0), Milkyway (Joomla 1.5), and Protostar (Joomla 3.0).
This template is a customized fork of the Cassiopeia template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting.
Custom Colour Themes Starter palette files are included with the template. To create a custom colour scheme, copy templates/mokocassiopeia/templates/light.custom.css to media/templates/site/mokocassiopeia/css/theme/light.custom.css, or templates/mokocassiopeia/templates/dark.custom.css to media/templates/site/mokocassiopeia/css/theme/dark.custom.css. Customise the CSS variables to match your brand, then activate your palette in System → Site Templates → MokoCassiopeia → Theme tab by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the CSS Variables tab in template options.
Custom CSS & JavaScript For site-specific styles and scripts that should survive template updates, create the following files:
media/templates/site/mokocassiopeia/css/user.css — loaded on every page for custom CSS overrides. media/templates/site/mokocassiopeia/js/user.js — loaded on every page for custom JavaScript. These files are gitignored and will not be overwritten by template updates.
Code Attribution This template is based on the original Cassiopeia template developed by the Joomla! Project and released under the GNU General Public License.
Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards.
It includes integration with Bootstrap TOC , an open-source table of contents generator by A. Feld, licensed under the MIT License.
All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable.
]]>
1
component.php
diff --git a/updates.xml b/updates.xml
index 3fb3a81..76ab8b4 100644
--- a/updates.xml
+++ b/updates.xml
@@ -1,7 +1,7 @@
@@ -13,12 +13,12 @@
mokocassiopeia
template
site
- 03.09.15
+ 03.09.16
2026-04-14
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/development
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.15-dev.zip
- https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.15-dev.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.16-dev.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.16-dev.zip
310fc79b042a042c0b518a806018011d5243893f239d000224fb197943fee5e3
development
@@ -35,12 +35,12 @@
mokocassiopeia
template
site
- 03.09.15
+ 03.09.16
2026-04-14
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/alpha
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.15-alpha.zip
- https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.15-alpha.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.16-alpha.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.16-alpha.zip
c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932
alpha
@@ -57,12 +57,12 @@
mokocassiopeia
template
site
- 03.09.15
+ 03.09.16
2026-04-14
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/beta
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.15-beta.zip
- https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.15-beta.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.16-beta.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.16-beta.zip
4cbe4fc379182ef17580396e7d12ce4ce95a90017ef364b922bdc2d04b0b3d97
beta
@@ -79,12 +79,12 @@
mokocassiopeia
template
site
- 03.09.15
+ 03.09.16
2026-04-14
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/release-candidate
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.15-rc.zip
- https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.15-rc.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.16-rc.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.16-rc.zip
c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932
rc
@@ -101,12 +101,12 @@
mokocassiopeia
template
site
- 03.09.15
+ 03.09.16
2026-04-14
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/v03
- https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.15.zip
- https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.15.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.16.zip
+ https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.16.zip
c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932
stable
From c35e76f554974916e4c8a0221e800def7d4d2ae4 Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Thu, 16 Apr 2026 17:18:06 -0500
Subject: [PATCH 010/120] chore: update dev SHA-256 for 03.09.16 [skip ci]
Co-Authored-By: Claude Opus 4.6 (1M context)
---
updates.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/updates.xml b/updates.xml
index 76ab8b4..865ffb9 100644
--- a/updates.xml
+++ b/updates.xml
@@ -14,13 +14,13 @@
template
site
03.09.16
- 2026-04-14
+ 2026-04-16
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/development
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.16-dev.zip
https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.16-dev.zip
- 310fc79b042a042c0b518a806018011d5243893f239d000224fb197943fee5e3
+ sha256:781448d2d332a734fc5d0f7aec5f62127688b705b632deded2d9814e3897d991
development
Moko Consulting
https://mokoconsulting.tech
From 5e5d6a4e897764ff8e948d4ad5447d4bc5dc08a8 Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Thu, 16 Apr 2026 18:29:43 -0500
Subject: [PATCH 011/120] Offline page redesign, remove brand showcase tab,
force a11y/theme to bottom-right
- Offline page: full-viewport Joomla offline_image background, centered glass card
overlay with logo glow, message, offline position, login accordion, copyright
- Favicon: added to offline page, HTML-escaped paths in helper
- Removed brand showcase tab, added footer CSS vars to CSS Variables tab
- Forced a11y toolbar and theme FAB to bottom-right (removed position selectors)
Co-Authored-By: Claude Opus 4.6 (1M context)
---
src/helper/favicon.php | 2 +-
src/index.php | 4 +-
src/language/en-GB/tpl_mokocassiopeia.ini | 8 +-
src/language/en-US/tpl_mokocassiopeia.ini | 8 +-
src/media/css/offline.css | 273 +++++++++++-----------
src/offline.php | 266 +++++++++++----------
src/templateDetails.xml | 13 +-
7 files changed, 298 insertions(+), 276 deletions(-)
diff --git a/src/helper/favicon.php b/src/helper/favicon.php
index 2567e86..028eaf2 100644
--- a/src/helper/favicon.php
+++ b/src/helper/favicon.php
@@ -162,7 +162,7 @@ class MokoFaviconHelper
*/
public static function getHeadTags(string $basePath): string
{
- $basePath = rtrim($basePath, '/');
+ $basePath = htmlspecialchars(rtrim($basePath, '/'), ENT_QUOTES, 'UTF-8');
return ' ' . "\n"
. ' ' . "\n"
diff --git a/src/index.php b/src/index.php
index e30eb97..70db6f6 100644
--- a/src/index.php
+++ b/src/index.php
@@ -41,7 +41,7 @@ $params_favicon_source = (string) $this->params->get('favicon_source', '');
$params_theme_enabled = $this->params->get('theme_enabled', 1);
$params_theme_control_type = (string) $this->params->get('theme_control_type', 'radios');
$params_theme_fab_enabled = $this->params->get('theme_fab_enabled', 1);
-$params_theme_fab_pos = $this->params->get('theme_fab_pos', 'br');
+$params_theme_fab_pos = 'br';
// Accessibility params
$params_a11y_toolbar = $this->params->get('a11y_toolbar_enabled', 1);
@@ -51,7 +51,7 @@ $params_a11y_contrast = $this->params->get('a11y_high_contrast', 1);
$params_a11y_links = $this->params->get('a11y_highlight_links', 1);
$params_a11y_font = $this->params->get('a11y_readable_font', 1);
$params_a11y_animations = $this->params->get('a11y_pause_animations', 1);
-$params_a11y_pos = (string) $this->params->get('a11y_toolbar_pos', 'tl');
+$params_a11y_pos = 'br';
// Detecting Active Variables
$option = $input->getCmd('option', '');
diff --git a/src/language/en-GB/tpl_mokocassiopeia.ini b/src/language/en-GB/tpl_mokocassiopeia.ini
index 945950b..0116bc3 100644
--- a/src/language/en-GB/tpl_mokocassiopeia.ini
+++ b/src/language/en-GB/tpl_mokocassiopeia.ini
@@ -259,16 +259,14 @@ TPL_MOKOCASSIOPEIA_CSS_VARS_VM_DESC="Surfaces & text Spacing--footer-padding-top — Top padding (default: 1rem)--footer-padding-bottom — Bottom padding (default: 80px)--footer-grid-padding-y — Grid vertical padding (default: 2.5rem)--footer-grid-padding-x — Grid horizontal padding (default: 0.5em)"
+
; ===== Theme Preview tab =====
TPL_MOKOCASSIOPEIA_THEME_PREVIEW_FIELDSET_LABEL="Theme Preview"
TPL_MOKOCASSIOPEIA_THEME_PREVIEW_INTRO="Live preview of all CSS variables, hero variants, block colours, and Bootstrap components rendered with your active theme. Use the Toggle Light / Dark button inside the preview to switch modes. This page is also available as a standalone file at templates/mokocassiopeia/templates/theme-test.html.
"
TPL_MOKOCASSIOPEIA_THEME_PREVIEW_FRAME=""
-; ===== Brand Showcase tab =====
-TPL_MOKOCASSIOPEIA_BRAND_SHOWCASE_FIELDSET_LABEL="Brand Showcase"
-TPL_MOKOCASSIOPEIA_BRAND_SHOWCASE_INTRO="Interactive brand and Bootstrap 5 component showcase with colour system gradients. Hover over any gradient to sample the exact pixel colour at that point. Use the Toggle Light / Dark button to switch themes. This page is also available standalone at templates/mokocassiopeia/templates/brand-showcase.html.
"
-TPL_MOKOCASSIOPEIA_BRAND_SHOWCASE_FRAME=""
-
; ===== Misc =====
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
diff --git a/src/language/en-US/tpl_mokocassiopeia.ini b/src/language/en-US/tpl_mokocassiopeia.ini
index f70f948..9413976 100644
--- a/src/language/en-US/tpl_mokocassiopeia.ini
+++ b/src/language/en-US/tpl_mokocassiopeia.ini
@@ -259,16 +259,14 @@ TPL_MOKOCASSIOPEIA_CSS_VARS_VM_DESC="Surfaces & text Spacing--footer-padding-top — Top padding (default: 1rem)--footer-padding-bottom — Bottom padding (default: 80px)--footer-grid-padding-y — Grid vertical padding (default: 2.5rem)--footer-grid-padding-x — Grid horizontal padding (default: 0.5em)"
+
; ===== Theme Preview tab =====
TPL_MOKOCASSIOPEIA_THEME_PREVIEW_FIELDSET_LABEL="Theme Preview"
TPL_MOKOCASSIOPEIA_THEME_PREVIEW_INTRO="Live preview of all CSS variables, hero variants, block colors, and Bootstrap components rendered with your active theme. Use the Toggle Light / Dark button inside the preview to switch modes. This page is also available as a standalone file at templates/mokocassiopeia/templates/theme-test.html.
"
TPL_MOKOCASSIOPEIA_THEME_PREVIEW_FRAME=""
-; ===== Brand Showcase tab =====
-TPL_MOKOCASSIOPEIA_BRAND_SHOWCASE_FIELDSET_LABEL="Brand Showcase"
-TPL_MOKOCASSIOPEIA_BRAND_SHOWCASE_INTRO="Interactive brand and Bootstrap 5 component showcase with color system gradients. Hover over any gradient to sample the exact pixel color at that point. Use the Toggle Light / Dark button to switch themes. This page is also available standalone at templates/mokocassiopeia/templates/brand-showcase.html.
"
-TPL_MOKOCASSIOPEIA_BRAND_SHOWCASE_FRAME=""
-
; ===== Misc =====
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
diff --git a/src/media/css/offline.css b/src/media/css/offline.css
index 19c27f8..0ba09bd 100644
--- a/src/media/css/offline.css
+++ b/src/media/css/offline.css
@@ -5,66 +5,36 @@
SPDX-License-Identifier: GPL-3.0-or-later
*/
-/* === Offline Page Layout === */
+/* === Offline Page — Full-viewport background with centered overlay card === */
+
.moko-offline-wrap {
min-height: 100vh;
- display: grid;
- grid-template-rows: auto 1fr auto;
- background-color: var(--body-bg, #0e1318);
- color: var(--body-font-color, #e6ebf1);
- font-family: var(--body-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);
-}
-
-.moko-offline-main {
- display: grid;
- place-items: center;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
padding: 2rem 1rem;
+ color: #fff;
+ font-family: var(--body-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);
+ /* Background set inline via Joomla offline_image or fallback */
+ background-color: var(--body-bg, #0e1318);
+ background-position: center;
+ background-attachment: fixed;
+ background-repeat: no-repeat;
+ background-size: cover;
}
-/* === Centered Card Grid === */
-.moko-offline-grid {
- display: grid;
- grid-template-columns: 1fr minmax(0, 720px) 1fr;
- width: 100%;
- max-width: 1200px;
- margin: 0 auto;
-}
-
-.moko-offline-grid__side {
- display: block;
-}
-
-.moko-offline-grid__center {
- min-width: 0;
-}
-
-@media (max-width: 767.98px) {
- .moko-offline-grid {
- grid-template-columns: 1fr;
- }
-
- .moko-offline-grid__side {
- display: none;
- }
-
- .moko-offline-main {
- padding: 0;
- }
-
- .moko-offline-card {
- border-radius: 0;
- border: none;
- min-height: 100%;
- }
-}
-
-/* === Card === */
+/* === Centered Card Overlay === */
.moko-offline-card {
- background-color: var(--card-bg, var(--secondary-bg, #151b22));
- border: var(--card-border-width, 1px) solid var(--card-border-color, var(--border-color, #2b323b));
- border-radius: var(--border-radius, 0.25rem);
- box-shadow: var(--box-shadow, 0 0.5rem 1rem rgba(0, 0, 0, 0.26));
- padding: 2.5rem;
+ width: 100%;
+ max-width: 640px;
+ background: rgba(0, 0, 0, 0.6);
+ backdrop-filter: blur(8px);
+ -webkit-backdrop-filter: blur(8px);
+ border-radius: 0.875rem;
+ padding: 2.5rem 2rem;
+ text-align: center;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
@media (min-width: 768px) {
@@ -73,150 +43,191 @@
}
}
-/* === Brand / Logo === */
+@media (max-width: 575.98px) {
+ .moko-offline-wrap {
+ padding: 1rem 0.75rem;
+ }
+
+ .moko-offline-card {
+ padding: 2rem 1.25rem;
+ }
+}
+
+/* === Logo (with glow effect like clarksvillefurs) === */
.moko-offline-brand {
- display: flex;
- flex-direction: column;
- align-items: center;
+ display: block;
text-align: center;
- gap: 0.75rem;
text-decoration: none;
- color: var(--body-font-color, #e6ebf1);
- margin-bottom: 2rem;
+ color: #fff;
+ margin-bottom: 1.5rem;
}
.moko-offline-brand:hover {
- color: var(--color-link, #8ab4f8);
+ color: var(--accent-color-primary, #3f8ff0);
}
.moko-offline-brand img {
- max-width: 200px;
- max-height: 80px;
+ max-width: 280px;
+ max-height: 120px;
width: auto;
height: auto;
+ filter: drop-shadow(0 0 12px rgba(255, 255, 255, 0.3))
+ drop-shadow(0 0 24px rgba(255, 255, 255, 0.15));
}
.moko-offline-brand .site-title {
- font-size: 1.75rem;
+ display: block;
+ font-size: 2rem;
font-weight: 700;
font-family: 'Osaka', var(--body-font-family, sans-serif);
+ text-shadow: 0 0 20px rgba(255, 255, 255, 0.3);
}
.moko-offline-brand .brand-tagline {
display: block;
- opacity: 0.75;
- font-size: 0.875rem;
- line-height: 1.2;
+ opacity: 0.7;
+ font-size: 0.9rem;
+ margin-top: 0.25rem;
}
-/* === Header === */
-.moko-offline-header {
- background-color: var(--header-bg, var(--color-primary, #112855));
- color: var(--mainmenu-nav-link-color, #fff);
- padding: 1rem 0;
+/* === Offline Message === */
+.moko-offline-message {
+ margin-bottom: 1.5rem;
}
-.moko-offline-header .container {
- display: flex;
- align-items: center;
- gap: 1rem;
- max-width: 1200px;
- margin: 0 auto;
- padding: 0 1rem;
-}
-
-.moko-offline-header .moko-brand {
- display: flex;
- align-items: center;
- gap: 0.75rem;
- text-decoration: none;
- color: var(--mainmenu-nav-link-color, #fff);
-}
-
-.moko-offline-header .moko-brand img {
- max-width: 180px;
- max-height: 60px;
- width: auto;
- height: auto;
-}
-
-.moko-offline-header .moko-brand .brand-tagline {
- display: block;
- opacity: 0.75;
- font-size: 0.875rem;
- line-height: 1.2;
-}
-
-/* === Content Typography === */
-.moko-offline-card h1 {
- color: var(--heading-color, var(--body-font-color, #f1f5f9));
+.moko-offline-message h1 {
+ font-size: 1.5rem;
font-weight: 700;
+ color: #fff;
+ margin-bottom: 0.5rem;
+ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
-.moko-offline-card .lead {
- color: var(--gray-600, #48525d);
+.moko-offline-message p {
+ color: rgba(255, 255, 255, 0.85);
line-height: 1.6;
+ margin: 0;
+}
+
+/* === Offline Module Position === */
+.moko-offline-modules {
+ margin-bottom: 1.5rem;
+ text-align: left;
+}
+
+/* === Copyright Footer === */
+.moko-offline-copyright {
+ font-size: 0.8rem;
+ color: rgba(255, 255, 255, 0.45);
+ margin-top: 1.5rem;
+ padding-top: 1rem;
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.moko-offline-copyright a {
+ color: rgba(255, 255, 255, 0.6);
+ text-decoration: underline;
+}
+
+.moko-offline-copyright a:hover {
+ color: #fff;
+}
+
+/* === Login Accordion (translucent on overlay) === */
+.moko-offline-card .accordion {
+ text-align: left;
}
-/* === Accordion === */
.moko-offline-card .accordion-item {
- background-color: var(--card-bg, var(--secondary-bg, #151b22));
- border-color: var(--border-color, #2b323b);
+ background: transparent;
+ border-color: rgba(255, 255, 255, 0.15);
}
.moko-offline-card .accordion-button {
- background-color: var(--card-bg, var(--secondary-bg, #151b22));
- color: var(--body-font-color, #e6ebf1);
+ background: transparent;
+ color: rgba(255, 255, 255, 0.8);
+ font-size: 0.9rem;
+ padding: 0.75rem 1rem;
}
.moko-offline-card .accordion-button:not(.collapsed) {
- background-color: var(--card-cap-bg, rgba(255, 255, 255, 0.03));
- color: var(--body-font-color, #e6ebf1);
+ background: rgba(255, 255, 255, 0.05);
+ color: #fff;
+ box-shadow: none;
+}
+
+.moko-offline-card .accordion-button::after {
+ filter: invert(1) brightness(2);
}
.moko-offline-card .accordion-body {
- background-color: var(--card-bg, var(--secondary-bg, #151b22));
+ background: transparent;
+ padding: 1rem;
}
-/* === Form Controls === */
+/* === Form Controls (glass effect) === */
.moko-offline-card .form-control {
- background-color: var(--input-bg, #1a2332);
- border-color: var(--input-border-color, #3a4250);
- color: var(--input-color, #e6ebf1);
+ background-color: rgba(255, 255, 255, 0.1);
+ border-color: rgba(255, 255, 255, 0.2);
+ color: #fff;
+}
+
+.moko-offline-card .form-control::placeholder {
+ color: rgba(255, 255, 255, 0.4);
}
.moko-offline-card .form-control:focus {
- border-color: var(--input-focus-border-color, #5472ff);
- box-shadow: var(--input-focus-box-shadow, 0 0 0 0.25rem rgba(84, 114, 255, 0.25));
+ background-color: rgba(255, 255, 255, 0.15);
+ border-color: var(--accent-color-primary, #3f8ff0);
+ color: #fff;
+ box-shadow: 0 0 0 0.25rem rgba(63, 143, 240, 0.25);
}
.moko-offline-card .form-label {
- color: var(--body-font-color, #e6ebf1);
+ color: rgba(255, 255, 255, 0.8);
+ font-size: 0.875rem;
}
.moko-offline-card .form-check-label {
- color: var(--body-font-color, #e6ebf1);
+ color: rgba(255, 255, 255, 0.7);
+}
+
+.moko-offline-card .form-check-input {
+ background-color: rgba(255, 255, 255, 0.1);
+ border-color: rgba(255, 255, 255, 0.3);
+}
+
+.moko-offline-card .form-check-input:checked {
+ background-color: var(--accent-color-primary, #3f8ff0);
+ border-color: var(--accent-color-primary, #3f8ff0);
}
/* === Button === */
.moko-offline-card .btn-primary {
background-color: var(--color-primary, #112855);
- border-color: var(--color-primary, #112855);
- color: var(--mainmenu-nav-link-color, #fff);
+ border-color: rgba(255, 255, 255, 0.15);
+ color: #fff;
}
.moko-offline-card .btn-primary:hover {
- background-color: var(--color-hover, gray);
- border-color: var(--color-hover, gray);
+ background-color: var(--accent-color-primary, #3f8ff0);
+ border-color: var(--accent-color-primary, #3f8ff0);
}
/* === Links === */
.moko-offline-card a {
- color: var(--link-color, var(--color-link, #8ab4f8));
+ color: var(--accent-color-primary, #3f8ff0);
}
.moko-offline-card a:hover {
- color: var(--link-hover-color, #c3d6ff);
+ color: #fff;
+}
+
+/* === Joomla system messages === */
+.moko-offline-messages {
+ width: 100%;
+ max-width: 640px;
+ margin-bottom: 1rem;
}
/* === Skip Link === */
diff --git a/src/offline.php b/src/offline.php
index 196eea3..ef0e668 100644
--- a/src/offline.php
+++ b/src/offline.php
@@ -1,5 +1,5 @@
+/* Copyright (C) 2026 Moko Consulting
This file is part of a Moko Consulting project.
@@ -30,7 +30,7 @@ $params = $this->params ?: $app->getTemplate(true)->params;
$direction = $this->direction ?: 'ltr';
/* -----------------------
- Load ONLY template.css + theme palettes (with min toggle)
+ Load CSS + theme palettes
------------------------ */
$useMin = !((int) $params->get('development_mode', 0) === 1);
$assetSuffix = $useMin ? '.min' : '';
@@ -59,30 +59,30 @@ if ($params_DarkColorName === 'custom' && file_exists(JPATH_ROOT . '/media/templ
$doc->addStyleSheet($base . 'theme/dark.custom' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-dark-custom']);
}
-/* Load user assets last (after all other styles and scripts) */
+/* Load user assets last */
$doc->addStyleSheet($base . 'user' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-user']);
-/* Bootstrap CSS/JS for accordion behavior; safe to keep. */
+/* Bootstrap CSS/JS for accordion */
HTMLHelper::_('bootstrap.loadCss', true, $doc);
HTMLHelper::_('bootstrap.framework');
-/* Load template.js for theme switcher and other functionality */
+/* Load template.js for theme switcher */
$doc->addScript($jsBase . 'template' . $assetSuffix . '.js', ['version' => 'auto', 'defer' => true], ['id' => 'moko-template-js']);
-/* Load user.js last for custom user scripts */
+/* Load user.js last */
$doc->addScript($jsBase . 'user' . $assetSuffix . '.js', ['version' => 'auto', 'defer' => true], ['id' => 'moko-user-js']);
/* -----------------------
- Title + Meta (Include Site Name in Page Titles)
+ Title + Meta
------------------------ */
$sitename = (string) $app->get('sitename');
$baseTitle = Text::_('JGLOBAL_OFFLINE') ?: 'Offline';
-$snSetting = (int) $app->get('sitename_pagetitles', 0); // 0=no, 1=before, 2=after
+$snSetting = (int) $app->get('sitename_pagetitles', 0);
if ($snSetting === 1) {
- $doc->setTitle(Text::sprintf('JPAGETITLE', $sitename, $baseTitle)); // Site Name BEFORE
+ $doc->setTitle(Text::sprintf('JPAGETITLE', $sitename, $baseTitle));
} elseif ($snSetting === 2) {
- $doc->setTitle(Text::sprintf('JPAGETITLE', $baseTitle, $sitename)); // Site Name AFTER
+ $doc->setTitle(Text::sprintf('JPAGETITLE', $baseTitle, $sitename));
} else {
$doc->setTitle($baseTitle);
}
@@ -91,11 +91,21 @@ $doc->setMetaData('robots', 'noindex, nofollow');
/* -----------------------
Offline content from Global Config
------------------------ */
-$displayOfflineMessage = (int) $app->get('display_offline_message', 1); // 0|1|2
+$displayOfflineMessage = (int) $app->get('display_offline_message', 1);
$offlineMessage = trim((string) $app->get('offline_message', ''));
/* -----------------------
- Brand: logo from params OR siteTitle (matches index.php)
+ Offline image from Joomla Global Config (System > Global Configuration > Site > Offline Image)
+ Used as the full-viewport background image.
+------------------------ */
+$offlineImage = trim((string) $app->get('offline_image', ''));
+$bgStyle = '';
+if ($offlineImage !== '') {
+ $bgStyle = 'background-image: url(\'' . htmlspecialchars(Uri::root(false) . $offlineImage, ENT_QUOTES, 'UTF-8') . '\');';
+}
+
+/* -----------------------
+ Brand: logo from template params OR siteTitle
------------------------ */
$brandHtml = '';
$logoFile = (string) $params->get('logoFile');
@@ -110,9 +120,8 @@ if ($logoFile !== '') {
0
);
} else {
- // If no logo file, show the title (defaults to "MokoCassiopeia" if not set)
$siteTitle = $params->get('siteTitle', 'MokoCassiopeia');
- $brandHtml = ''
+ $brandHtml = ''
. htmlspecialchars($siteTitle, ENT_COMPAT, 'UTF-8')
. ' ';
}
@@ -120,6 +129,20 @@ if ($logoFile !== '') {
$brandTagline = (string) ($params->get('brand_tagline') ?: $params->get('siteDescription') ?: '');
$showTagline = (int) $params->get('show_brand_tagline', 0);
+// Favicon
+$params_favicon_source = (string) $params->get('favicon_source', '');
+$faviconHeadTags = '';
+if ($params_favicon_source) {
+ require_once JPATH_ROOT . '/templates/' . $this->template . '/helper/favicon.php';
+ $faviconSourceAbs = JPATH_ROOT . '/' . ltrim($params_favicon_source, '/');
+ $faviconOutputDir = JPATH_ROOT . '/images/favicons';
+ $faviconUrlBase = Uri::root(true) . '/images/favicons';
+
+ if (MokoFaviconHelper::generate($faviconSourceAbs, $faviconOutputDir)) {
+ $faviconHeadTags = MokoFaviconHelper::getHeadTags($faviconUrlBase);
+ }
+}
+
// Theme params
$params_theme_enabled = (int) $params->get('theme_enabled', 1);
@@ -135,7 +158,7 @@ if (!empty($params_googlesitekey)) {
}
/* -----------------------
- Login routes & Users
+ Login routes
------------------------ */
$action = Route::_('index.php', true);
$return = base64_encode(Uri::base());
@@ -156,10 +179,12 @@ if (class_exists('\Joomla\Component\Users\Site\Helper\RouteHelper')) {
+
+
+
-
-
-
-
-
-
- countModules('offline-header')) : ?>
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
-
- countModules('offline')) : ?>
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+ countModules('offline')) : ?>
+
+
+
+
-
-
-
-
+
+
-
-
+
+
+
+ © .
+
+
-
+
+ countModules('offline-footer')) : ?>
+
+
+
+
+
diff --git a/src/templateDetails.xml b/src/templateDetails.xml
index 5499cb6..c432c8c 100644
--- a/src/templateDetails.xml
+++ b/src/templateDetails.xml
@@ -295,7 +295,8 @@
JNO
JYES
-
+
Bottom-right
@@ -312,7 +313,8 @@
JNO
JYES
-
+
Bottom-right
Bottom-left
@@ -365,6 +367,7 @@
+
@@ -372,12 +375,6 @@
-
-
-
-
-
-
From 87b5326d1d8fb93320c9bce2b5d5c4d76c3a6935 Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Thu, 16 Apr 2026 18:34:06 -0500
Subject: [PATCH 012/120] Use MOD_FOOTER_LINE2 for offline page copyright
Co-Authored-By: Claude Opus 4.6 (1M context)
---
src/offline.php | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/offline.php b/src/offline.php
index ef0e668..6b5ff8a 100644
--- a/src/offline.php
+++ b/src/offline.php
@@ -352,7 +352,8 @@ if (class_exists('\Joomla\Component\Users\Site\Helper\RouteHelper')) {
From d6383468cdc625cd40a968abfcc99ea16c5fc4b0 Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Thu, 16 Apr 2026 18:36:23 -0500
Subject: [PATCH 013/120] Load Font Awesome 7 on offline page
Co-Authored-By: Claude Opus 4.6 (1M context)
---
src/offline.php | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/offline.php b/src/offline.php
index 6b5ff8a..b11b436 100644
--- a/src/offline.php
+++ b/src/offline.php
@@ -43,6 +43,10 @@ $doc->addStyleSheet($base . 'offline' . $assetSuffix . '.css', ['version' => 'au
/* Load Osaka font for site title */
$doc->addStyleSheet($base . 'fonts/osaka.css', ['version' => 'auto'], ['id' => 'moko-font-osaka']);
+/* Load Font Awesome 7 Free (local) — Kit code not supported on offline page */
+$faBase = rtrim(Uri::root(true), '/') . '/templates/' . $this->template . '/vendor/fa7free/css/';
+$doc->addStyleSheet($faBase . 'all' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-fa7free']);
+
/* Load theme palettes */
$doc->addStyleSheet($base . 'theme/light.standard' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-light-standard']);
$doc->addStyleSheet($base . 'theme/dark.standard' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-dark-standard']);
From 6708569c6fc021d264ba502e7218b70a43748b63 Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Thu, 16 Apr 2026 18:38:10 -0500
Subject: [PATCH 014/120] Rewrite offline page to use WebAssetManager
- All CSS/JS loading now uses $wa->useStyle/useScript matching index.php
- Added template.offline and template.offline.min to joomla.asset.json
- Font Awesome, Osaka font, themes, user overrides all via WAM
Co-Authored-By: Claude Opus 4.6 (1M context)
---
src/joomla.asset.json | 12 ++++++++++
src/offline.php | 55 ++++++++++++++++++++++---------------------
2 files changed, 40 insertions(+), 27 deletions(-)
diff --git a/src/joomla.asset.json b/src/joomla.asset.json
index f51468b..b22ffd7 100644
--- a/src/joomla.asset.json
+++ b/src/joomla.asset.json
@@ -34,6 +34,18 @@
"uri": "media/templates/site/mokocassiopeia/css/template.min.css",
"attributes": {"media": "all"}
},
+ {
+ "name": "template.offline",
+ "type": "style",
+ "uri": "media/templates/site/mokocassiopeia/css/offline.css",
+ "attributes": {"media": "all"}
+ },
+ {
+ "name": "template.offline.min",
+ "type": "style",
+ "uri": "media/templates/site/mokocassiopeia/css/offline.min.css",
+ "attributes": {"media": "all"}
+ },
{
"name": "template.user",
"type": "style",
diff --git a/src/offline.php b/src/offline.php
index b11b436..bc260f8 100644
--- a/src/offline.php
+++ b/src/offline.php
@@ -26,56 +26,57 @@ use Joomla\CMS\Uri\Uri;
$app = Factory::getApplication();
$doc = Factory::getDocument();
+$wa = $doc->getWebAssetManager();
$params = $this->params ?: $app->getTemplate(true)->params;
$direction = $this->direction ?: 'ltr';
/* -----------------------
- Load CSS + theme palettes
+ Load assets via WebAssetManager (matches index.php pattern)
------------------------ */
-$useMin = !((int) $params->get('development_mode', 0) === 1);
-$assetSuffix = $useMin ? '.min' : '';
-$base = rtrim(Uri::root(true), '/') . '/templates/' . $this->template . '/css/';
-$jsBase = rtrim(Uri::root(true), '/') . '/templates/' . $this->template . '/js/';
+$params_developmentmode = (bool) $params->get('developmentmode', false) || (bool) $app->get('debug', false);
+$suffix = $params_developmentmode ? '' : '.min';
-$doc->addStyleSheet($base . 'template' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-template']);
-$doc->addStyleSheet($base . 'offline' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-offline']);
+// Core template CSS + offline overlay CSS
+$wa->useStyle('template.base' . $suffix);
+$wa->useStyle('template.offline' . $suffix);
-/* Load Osaka font for site title */
-$doc->addStyleSheet($base . 'fonts/osaka.css', ['version' => 'auto'], ['id' => 'moko-font-osaka']);
+// Osaka font
+$wa->useStyle('template.font.osaka');
-/* Load Font Awesome 7 Free (local) — Kit code not supported on offline page */
-$faBase = rtrim(Uri::root(true), '/') . '/templates/' . $this->template . '/vendor/fa7free/css/';
-$doc->addStyleSheet($faBase . 'all' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-fa7free']);
+// Font Awesome 7 Free
+$wa->useStyle('vendor.fa7free.all' . $suffix);
-/* Load theme palettes */
-$doc->addStyleSheet($base . 'theme/light.standard' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-light-standard']);
-$doc->addStyleSheet($base . 'theme/dark.standard' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-dark-standard']);
+// Theme palettes
+$wa->useStyle('template.light.standard' . $suffix);
+$wa->useStyle('template.dark.standard' . $suffix);
-/* Load custom palettes only if selected in template configuration AND files exist */
+// Custom palettes (if selected and files exist)
$params_LightColorName = (string) $params->get('colorLightName', 'standard');
$params_DarkColorName = (string) $params->get('colorDarkName', 'standard');
if ($params_LightColorName === 'custom' && file_exists(JPATH_ROOT . '/media/templates/site/mokocassiopeia/css/theme/light.custom.css'))
{
- $doc->addStyleSheet($base . 'theme/light.custom' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-light-custom']);
+ $wa->useStyle('template.light.custom' . $suffix);
}
if ($params_DarkColorName === 'custom' && file_exists(JPATH_ROOT . '/media/templates/site/mokocassiopeia/css/theme/dark.custom.css'))
{
- $doc->addStyleSheet($base . 'theme/dark.custom' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-dark-custom']);
+ $wa->useStyle('template.dark.custom' . $suffix);
}
-/* Load user assets last */
-$doc->addStyleSheet($base . 'user' . $assetSuffix . '.css', ['version' => 'auto'], ['id' => 'moko-user']);
+// User overrides (loaded last)
+$wa->useStyle('template.user');
-/* Bootstrap CSS/JS for accordion */
+// Template JS (theme switcher, var-copy, etc.)
+if ($params_developmentmode) {
+ $wa->useScript('template.js');
+} else {
+ $wa->useScript('template.js.min');
+}
+$wa->useScript('user.js');
+
+// Bootstrap (accordion)
HTMLHelper::_('bootstrap.loadCss', true, $doc);
HTMLHelper::_('bootstrap.framework');
-/* Load template.js for theme switcher */
-$doc->addScript($jsBase . 'template' . $assetSuffix . '.js', ['version' => 'auto', 'defer' => true], ['id' => 'moko-template-js']);
-
-/* Load user.js last */
-$doc->addScript($jsBase . 'user' . $assetSuffix . '.js', ['version' => 'auto', 'defer' => true], ['id' => 'moko-user-js']);
-
/* -----------------------
Title + Meta
------------------------ */
From a62d40507749b60cb6ac7c46acf648393293cc51 Mon Sep 17 00:00:00 2001
From: Jonathan Miller
Date: Thu, 16 Apr 2026 18:41:22 -0500
Subject: [PATCH 015/120] Add theme switcher, a11y toolbar, and high-contrast
CSS to offline page
- Load a11y-high-contrast.css via WAM
- Add all data-theme-fab-* and data-a11y-* attributes to body
- Theme switcher FAB and a11y toolbar now render on offline page
Co-Authored-By: Claude Opus 4.6 (1M context)
---
src/offline.php | 32 +++++++++++++++++++++++++++++---
1 file changed, 29 insertions(+), 3 deletions(-)
diff --git a/src/offline.php b/src/offline.php
index bc260f8..1d311f2 100644
--- a/src/offline.php
+++ b/src/offline.php
@@ -65,7 +65,10 @@ if ($params_DarkColorName === 'custom' && file_exists(JPATH_ROOT . '/media/templ
// User overrides (loaded last)
$wa->useStyle('template.user');
-// Template JS (theme switcher, var-copy, etc.)
+// Accessibility high-contrast stylesheet
+$wa->useStyle('template.a11y-high-contrast');
+
+// Template JS (theme switcher, a11y toolbar, var-copy, etc.)
if ($params_developmentmode) {
$wa->useScript('template.js');
} else {
@@ -149,7 +152,19 @@ if ($params_favicon_source) {
}
// Theme params
-$params_theme_enabled = (int) $params->get('theme_enabled', 1);
+$params_theme_enabled = (int) $params->get('theme_enabled', 1);
+$params_theme_fab_enabled = (int) $params->get('theme_fab_enabled', 1);
+$params_theme_fab_pos = 'br';
+
+// Accessibility params
+$params_a11y_toolbar = (int) $params->get('a11y_toolbar_enabled', 1);
+$params_a11y_resize = (int) $params->get('a11y_text_resize', 1);
+$params_a11y_invert = (int) $params->get('a11y_color_inversion', 1);
+$params_a11y_contrast = (int) $params->get('a11y_high_contrast', 1);
+$params_a11y_links = (int) $params->get('a11y_highlight_links', 1);
+$params_a11y_font = (int) $params->get('a11y_readable_font', 1);
+$params_a11y_animations = (int) $params->get('a11y_pause_animations', 1);
+$params_a11y_pos = 'br';
// Analytics params
$params_googletagmanager = $params->get('googletagmanager', false);
@@ -203,7 +218,18 @@ if (class_exists('\Joomla\Component\Users\Site\Helper\RouteHelper')) {
-style="">
+style="">