MokoOnyx v01.00.00 — initial release (successor to MokoCassiopeia)
Some checks failed
Standards Compliance / Secret Scanning (push) Successful in 3s
Standards Compliance / License Header Validation (push) Successful in 4s
Standards Compliance / Repository Structure Validation (push) Successful in 5s
Standards Compliance / Coding Standards Check (push) Failing after 3s
Standards Compliance / Version Consistency Check (push) Successful in 3s
Standards Compliance / Workflow Configuration Check (push) Failing after 2s
Standards Compliance / Documentation Quality Check (push) Successful in 3s
Standards Compliance / README Completeness Check (push) Successful in 3s
Standards Compliance / Git Repository Hygiene (push) Successful in 2s
Standards Compliance / Script Integrity Validation (push) Successful in 4s
Standards Compliance / Line Length Check (push) Failing after 4s
Standards Compliance / File Naming Standards (push) Successful in 2s
Standards Compliance / Insecure Code Pattern Detection (push) Successful in 3s
Standards Compliance / Code Complexity Analysis (push) Successful in 3s
Standards Compliance / Code Duplication Detection (push) Successful in 4s
Standards Compliance / Dead Code Detection (push) Successful in 3s
Standards Compliance / File Size Limits (push) Successful in 2s
CodeQL Security Scanning / Analyze (javascript) (push) Failing after 1m9s
Standards Compliance / Binary File Detection (push) Successful in 4s
CodeQL Security Scanning / Analyze (actions) (push) Failing after 1m11s
Standards Compliance / TODO/FIXME Tracking (push) Successful in 3s
Standards Compliance / Dependency Vulnerability Scanning (push) Successful in 5s
Standards Compliance / Broken Link Detection (push) Successful in 5s
Standards Compliance / Unused Dependencies Check (push) Successful in 7s
Standards Compliance / API Documentation Coverage (push) Successful in 3s
Standards Compliance / Accessibility Check (push) Successful in 3s
Standards Compliance / Performance Metrics (push) Successful in 3s
Standards Compliance / Enterprise Readiness Check (push) Successful in 3s
Standards Compliance / Repository Health Check (push) Successful in 4s
Standards Compliance / Terraform Configuration Validation (push) Successful in 6s
CodeQL Security Scanning / Security Scan Summary (push) Successful in 1s
Standards Compliance / Compliance Summary (push) Successful in 1s
Repo Health / Access control (push) Successful in 1s
Auto-Update SHA Hash / Update SHA-256 Hash in updates.xml (release) Successful in 4s
Repo Health / Release configuration (push) Failing after 3s
Repo Health / Scripts governance (push) Successful in 3s
Repo Health / Repository health (push) Failing after 3s

All files renamed from mokocassiopeia to mokoonyx.
Update server points to MokoOnyx repo.
Bridge migration removed (clean standalone template).
Version reset to 01.00.00.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Miller
2026-04-19 17:19:03 -05:00
parent 3ba2214614
commit 8258ed804a
238 changed files with 85443 additions and 2 deletions

170
src/component.php Normal file
View File

@@ -0,0 +1,170 @@
<?php
/* 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
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\Component\ComponentHelper;
/** @var Joomla\CMS\Document\HtmlDocument $this */
$app = Factory::getApplication();
$input = $app->getInput();
$document = $app->getDocument();
$wa = $document->getWebAssetManager();
// Template params - Component uses minimal configuration
$params_googletagmanager = $this->params->get('googletagmanager', false);
$params_googletagmanagerid = $this->params->get('googletagmanagerid', null);
$params_googleanalytics = $this->params->get('googleanalytics', false);
$params_googleanalyticsid = $this->params->get('googleanalyticsid', null);
$params_googlesitekey = $this->params->get('googlesitekey', null);
if (!empty($params_googlesitekey)) {
$this->setMetaData('google-site-verification', htmlspecialchars($params_googlesitekey, ENT_QUOTES, 'UTF-8'));
}
// Detecting Active Variables
$option = $input->getCmd('option', '');
$view = $input->getCmd('view', '');
$layout = $input->getCmd('layout', '');
$task = $input->getCmd('task', '');
$itemid = $input->getCmd('Itemid', '');
$sitenameR = $app->get('sitename'); // raw for title composition
$sitename = htmlspecialchars($sitenameR, ENT_QUOTES, 'UTF-8');
$menu = $app->getMenu()->getActive();
$pageclass = $menu !== null ? $menu->getParams()->get('pageclass_sfx', '') : '';
// Template/Media path
$templatePath = 'media/templates/site/mokoonyx';
// Core template CSS
$wa->useStyle('template.base'); // css/template.css
// Component always uses light theme only (no theme switching)
$wa->useStyle('template.light.standard'); // css/theme/light.standard.css
// Load Osaka font for site title
$wa->useStyle('template.font.osaka');
// Brand: logo from params OR siteTitle
// -------------------------------------
$brandHtml = '';
$logoFile = (string) $this->params->get('logoFile');
if ($logoFile !== '') {
$brandHtml = HTMLHelper::_(
'image',
Uri::root(false) . htmlspecialchars($logoFile, ENT_QUOTES, 'UTF-8'),
$sitename,
['class' => 'logo d-inline-block', 'loading' => 'eager', 'decoding' => 'async'],
false,
0
);
} else {
// If no logo file, show the title (defaults to "MokoOnyx" if not set)
$siteTitle = $this->params->get('siteTitle', 'MokoOnyx');
$brandHtml = '<span class="site-title" title="' . $sitename . '">'
. htmlspecialchars($siteTitle, ENT_COMPAT, 'UTF-8')
. '</span>';
}
?>
<!DOCTYPE html>
<html class="component" lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>" data-bs-theme="light">
<head>
<jdoc:include type="metas" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<jdoc:include type="styles" />
<jdoc:include type="scripts" />
</head>
<body class="<?php echo $this->direction === 'rtl' ? 'rtl' : ''; ?>">
<?php if (!empty($params_googletagmanager) && !empty($params_googletagmanagerid)) :
$gtmID = htmlspecialchars($params_googletagmanagerid, ENT_QUOTES, 'UTF-8'); ?>
<!-- Google Tag Manager -->
<script>
(function(w,d,s,l,i){
w[l]=w[l]||[];
w[l].push({'gtm.start': new Date().getTime(), event:'gtm.js'});
var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),
dl=l!='dataLayer'?'&l='+l:'';
j.async=true;
j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;
f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','<?php echo $gtmID; ?>');
</script>
<!-- End Google Tag Manager -->
<!-- Google Tag Manager (noscript) -->
<noscript>
<iframe src="https://www.googletagmanager.com/ns.html?id=<?php echo $gtmID; ?>"
height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript>
<!-- End Google Tag Manager (noscript) -->
<?php endif; ?>
<?php if (!empty($params_googleanalytics) && !empty($params_googleanalyticsid)) :
$gaId = htmlspecialchars($params_googleanalyticsid, ENT_QUOTES, 'UTF-8'); ?>
<!-- Google Analytics (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=<?php echo $gaId; ?>"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('consent', 'default', {
'ad_storage': 'denied',
'analytics_storage': 'granted',
'ad_user_data': 'denied',
'ad_personalization': 'denied'
});
(function(id){
if (/^G-/.test(id)) {
gtag('config', id, { 'anonymize_ip': true });
} else if (/^UA-/.test(id)) {
gtag('config', id, { 'anonymize_ip': true });
console.warn('Using a UA- ID. Universal Analytics is sunset; consider migrating to GA4.');
} else {
console.warn('Unrecognized Google Analytics ID format:', id);
}
})('<?php echo $gaId; ?>');
</script>
<!-- End Google Analytics -->
<?php endif; ?>
<?php if ($this->params->get('brand', 1)) : ?>
<div class="navbar-brand">
<a class="brand-logo" href="<?php echo $this->baseurl; ?>/">
<?php echo $brandHtml; ?>
</a>
</div>
<?php endif; ?>
<jdoc:include type="message" />
<jdoc:include type="component" />
<footer class="container-footer footer full-width">
<?php if ($this->countModules('footer-menu', true)) : ?>
<div class="grid-child footer-menu">
<jdoc:include type="modules" name="footer-menu" />
</div>
<?php endif; ?>
<?php if ($this->countModules('footer', true)) : ?>
<div class="grid-child">
<jdoc:include type="modules" name="footer" style="none" />
</div>
<?php endif; ?>
</footer>
<jdoc:include type="modules" name="debug" style="none" />
</body>
</html>

20
src/custom.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
/* 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
*/
function console_log($output, $with_script_tags = true) {
$js_code = 'console.log(' . json_encode($output, JSON_HEX_TAG) .
');';
if ($with_script_tags) {
$js_code = '<script>' . $js_code . '</script>';
}
echo $js_code;
}
?>
<!--
Custom code included here
-->

452
src/error.php Normal file
View File

@@ -0,0 +1,452 @@
<?php
/* 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
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
/** @var Joomla\CMS\Document\ErrorDocument|Joomla\CMS\Document\HtmlDocument $this */
$app = Factory::getApplication();
$params = $this->params;
$wa = $this->getWebAssetManager();
// Template params
$params_LightColorName = (string) $params->get('colorLightName', 'standard'); // standard|custom
$params_DarkColorName = (string) $params->get('colorDarkName', 'standard'); // standard|custom
$params_googletagmanager = $params->get('googletagmanager', false);
$params_googletagmanagerid = $params->get('googletagmanagerid', '');
$params_googleanalytics = $params->get('googleanalytics', false);
$params_googleanalyticsid = $params->get('googleanalyticsid', '');
$params_custom_head_start = $params->get('custom_head_start', '');
$params_custom_head_end = $params->get('custom_head_end', '');
$params_developmentmode = $params->get('developmentmode', false);
// ------------------ Params ------------------
$fluidContainer = (bool) $params->get('fluidContainer', 0);
$wrapper = $fluidContainer ? 'wrapper-fluid' : 'wrapper-static';
$stickyHeader = (bool) $params->get('stickyHeader', 0);
// Drawer icon params (escaped)
$params_leftIcon = htmlspecialchars($params->get('drawerLeftIcon', 'fa-solid fa-chevron-left'), ENT_QUOTES, 'UTF-8');
$params_rightIcon = htmlspecialchars($params->get('drawerRightIcon', 'fa-solid fa-chevron-right'), ENT_QUOTES, 'UTF-8');
// Template/Media path
$templatePath = 'media/templates/site/mokoonyx';
// ===========================
// Web Asset Manager (WAM) — matches your joomla.asset.json
// ===========================
// Core template CSS
$wa->useStyle('template.base'); // css/template.css
// Load theme palette stylesheets based on configuration
$wa->useStyle('template.light.standard'); // css/theme/light.standard.css
$wa->useStyle('template.dark.standard'); // css/theme/dark.standard.css
// Load custom palettes only if selected in template configuration AND files exist
if ($params_LightColorName === 'custom' && file_exists(JPATH_ROOT . '/media/templates/site/mokoonyx/css/theme/light.custom.css'))
{
$wa->useStyle('template.light.custom');
}
if ($params_DarkColorName === 'custom' && file_exists(JPATH_ROOT . '/media/templates/site/mokoonyx/css/theme/dark.custom.css'))
{
$wa->useStyle('template.dark.custom');
}
// Scripts
$wa->useScript('template.js');
// Load Osaka font for site title
$wa->useStyle('template.font.osaka');
// Smart Bootstrap component loading - only load what's needed
if ($this->countModules('drawer-left', true) || $this->countModules('drawer-right', true)) {
// Load Bootstrap Offcanvas component for drawers
HTMLHelper::_('bootstrap.offcanvas');
}
// Meta
$this->setMetaData('viewport', 'width=device-width, initial-scale=1');
if ($this->params->get('faKitCode')) {
$faKit = "https://kit.fontawesome.com/" . $this->params->get('faKitCode') . ".js";
HTMLHelper::_('script', $faKit, ['crossorigin' => 'anonymous']);
} else {
try {
if ($params_developmentmode){
$wa->useStyle('vendor.fa7free.all');
$wa->useStyle('vendor.fa7free.brands');
$wa->useStyle('vendor.fa7free.fontawesome');
$wa->useStyle('vendor.fa7free.regular');
$wa->useStyle('vendor.fa7free.solid');
} else {
$wa->useStyle('vendor.fa7free.all.min');
$wa->useStyle('vendor.fa7free.brands.min');
$wa->useStyle('vendor.fa7free.fontawesome.min');
$wa->useStyle('vendor.fa7free.regular.min');
$wa->useStyle('vendor.fa7free.solid.min');
}
} catch (\Throwable $e) {
if ($params_developmentmode){
$wa->registerAndUseStyle('vendor.fa7free.all.dynamic', $templatePath . '/vendor/fa7free/css/all.css');
$wa->registerAndUseStyle('vendor.fa7free.brands.dynamic', $templatePath . '/vendor/fa7free/css/brands.css');
$wa->registerAndUseStyle('vendor.fa7free.fontawesome.dynamic', $templatePath . '/vendor/fa7free/css/fontawesome.css');
$wa->registerAndUseStyle('vendor.fa7free.regular.dynamic', $templatePath . '/vendor/fa7free/css/regular.css');
$wa->registerAndUseStyle('vendor.fa7free.solid.dynamic', $templatePath . '/vendor/fa7free/css/solid.css');
} else {
$wa->registerAndUseStyle('vendor.fa7free.all.min.dynamic', $templatePath . '/vendor/fa7free/css/all.min.css');
$wa->registerAndUseStyle('vendor.fa7free.brands.min.dynamic', $templatePath . '/vendor/fa7free/css/brands.min.css');
$wa->registerAndUseStyle('vendor.fa7free.fontawesome.min.dynamic', $templatePath . '/vendor/fa7free/css/fontawesome.min.css');
$wa->registerAndUseStyle('vendor.fa7free.regular.min.dynamic', $templatePath . '/vendor/fa7free/css/regular.min.css');
$wa->registerAndUseStyle('vendor.fa7free.solid.min.dynamic', $templatePath . '/vendor/fa7free/css/solid.min.css');
}
}
}
// ------------------ Context (logo, bootstrap needs) ------------------
$sitename = htmlspecialchars($app->get('sitename'), ENT_QUOTES, 'UTF-8');
// -------------------------------------
// Brand: logo from params OR siteTitle
// -------------------------------------
$brandHtml = '';
$logoFile = (string) $this->params->get('logoFile');
if ($logoFile !== '') {
$brandHtml = HTMLHelper::_(
'image',
Uri::root(false) . htmlspecialchars($logoFile, ENT_QUOTES, 'UTF-8'),
$sitename,
['class' => 'logo d-inline-block', 'loading' => 'eager', 'decoding' => 'async'],
false,
0
);
} else {
// If no logo file, show the title (defaults to "MokoOnyx" if not set)
$siteTitle = $this->params->get('siteTitle', 'MokoOnyx');
$brandHtml = '<span class="site-title" title="' . $sitename . '">'
. htmlspecialchars($siteTitle, ENT_COMPAT, 'UTF-8')
. '</span>';
}
// ------------------ Error details ------------------
$errorObj = isset($this->error) && is_object($this->error) ? $this->error : null;
$errorCode = $errorObj ? (int) $errorObj->getCode() : 500;
$errorMsg = $errorObj ? $errorObj->getMessage() : Text::_('JERROR_AN_ERROR_HAS_OCCURRED');
$debugOn = defined('JDEBUG') && JDEBUG;
// Load user assets last (after all other styles and scripts)
$wa->useStyle('template.user'); // css/user.css
$wa->useScript('user.js'); // js/user.js
?>
<!DOCTYPE html>
<html lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>">
<head>
<?php if ($params_custom_head_start !== '') : ?><?php echo $params_custom_head_start; ?><?php endif; ?>
<jdoc:include type="head" />
<script>
// Early theme application to avoid FOUC
(function () {
try {
var stored = localStorage.getItem('theme');
var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
var theme = stored ? stored : (prefersDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-bs-theme', theme);
} catch (e) {}
})();
</script>
<script>
// Facebook in-app browser warning banner
window.addEventListener('DOMContentLoaded', function () {
var ua = navigator.userAgent || navigator.vendor || window.opera;
var isFacebookBrowser = ua.indexOf('FBAN') > -1 || ua.indexOf('FBAV') > -1;
if (isFacebookBrowser) {
var warning = document.createElement('div');
warning.textContent = '⚠️ KNOWN ISSUE: Images do not load in Facebook Web browser. Please open in external browser for full experience.';
warning.style.position = 'fixed';
warning.style.top = '0';
warning.style.left = '0';
warning.style.right = '0';
warning.style.zIndex = '10000';
warning.style.backgroundColor = '#007bff';
warning.style.color = '#fff';
warning.style.padding = '15px';
warning.style.textAlign = 'center';
warning.style.fontWeight = 'bold';
warning.style.fontSize = '16px';
warning.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
document.body.appendChild(warning);
}
});
</script>
<?php if ($params_custom_head_end !== '') : ?><?php echo $params_custom_head_end; ?><?php endif; ?>
</head>
<body data-bs-spy="scroll" data-bs-target="#toc" class="site error-page<?php
echo ($this->direction == 'rtl' ? ' rtl' : '');
?>">
<?php if (!empty($params_googletagmanager) && !empty($params_googletagmanagerid)) : ?>
<!-- Google Tag Manager -->
<script>
(function(w,d,s,l,i){
w[l]=w[l]||[];
w[l].push({'gtm.start': new Date().getTime(), event:'gtm.js'});
var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),
dl=l!='dataLayer'?'&l='+l:'';
j.async=true;
j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;
f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer',<?php echo json_encode($params_googletagmanagerid, JSON_HEX_TAG | JSON_HEX_AMP); ?>);
</script>
<!-- End Google Tag Manager -->
<!-- Google Tag Manager (noscript) -->
<noscript>
<iframe src="https://www.googletagmanager.com/ns.html?id=<?php echo htmlspecialchars($params_googletagmanagerid, ENT_QUOTES, 'UTF-8'); ?>"
height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript>
<!-- End Google Tag Manager (noscript) -->
<?php endif; ?>
<?php if (!empty($params_googleanalytics) && !empty($params_googleanalyticsid)) : ?>
<!-- Google Analytics (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=<?php echo htmlspecialchars($params_googleanalyticsid, ENT_QUOTES, 'UTF-8'); ?>"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('consent', 'default', {
'ad_storage': 'denied',
'analytics_storage': 'granted',
'ad_user_data': 'denied',
'ad_personalization': 'denied'
});
(function(id){
if (/^G-/.test(id)) {
gtag('config', id, { 'anonymize_ip': true });
} else if (/^UA-/.test(id)) {
gtag('config', id, { 'anonymize_ip': true });
console.warn('Using a UA- ID. Universal Analytics is sunset; consider migrating to GA4.');
} else {
console.warn('Unrecognized Google Analytics ID format:', id);
}
})(<?php echo json_encode($params_googleanalyticsid, JSON_HEX_TAG | JSON_HEX_AMP); ?>);
</script>
<!-- End Google Analytics -->
<?php endif; ?>
<!-- ========== HEADER FROM INDEX ========== -->
<header class="header container-header full-width<?php echo $stickyHeader ? ' position-sticky sticky-top' : ''; ?>" role="banner">
<?php if ($this->countModules('topbar')) : ?>
<div class="container-topbar">
<jdoc:include type="modules" name="topbar" style="none" />
</div>
<?php endif; ?>
<div class="header-top">
<?php if ($this->countModules('below-topbar')) : ?>
<div class="grid-child container-below-topbar">
<jdoc:include type="modules" name="below-topbar" style="none" />
</div>
<?php endif; ?>
<?php if ($this->params->get('brand', 1)) : ?>
<div class="grid-child">
<div class="navbar-brand">
<a class="brand-logo" href="<?php echo $this->baseurl; ?>/">
<?php echo $brandHtml; ?>
</a>
<?php if ($this->params->get('siteDescription')) : ?>
<div class="site-description">
<?php echo htmlspecialchars($this->params->get('siteDescription'), ENT_QUOTES, 'UTF-8'); ?>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php if ($this->countModules('below-logo')) : ?>
<div class="grid container-below-logo">
<jdoc:include type="modules" name="below-logo" style="none" />
</div>
<?php endif; ?>
</div>
<!-- Drawer Toggle Buttons -->
<?php if ($this->countModules('drawer-left')) : ?>
<button class="drawer-toggle-left btn btn-outline-secondary me-2"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#drawer-left"
aria-controls="drawer-left">
<span class="<?php echo $params_leftIcon; ?>"></span>
</button>
<?php endif; ?>
<?php if ($this->countModules('drawer-right')) : ?>
<button class="drawer-toggle-right btn btn-outline-secondary"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#drawer-right"
aria-controls="drawer-right">
<span class="<?php echo $params_rightIcon; ?>"></span>
</button>
<?php endif; ?>
<?php if ($this->countModules('menu', true) || $this->countModules('search', true)) : ?>
<div class="grid-child container-nav">
<?php if ($this->countModules('menu', true)) : ?>
<nav role="navigation" aria-label="Primary">
<jdoc:include type="modules" name="menu" style="none" />
</nav>
<?php endif; ?>
<?php if ($this->countModules('search', true)) : ?>
<div class="container-search">
<jdoc:include type="modules" name="search" style="none" />
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</header>
<!-- ========== END HEADER ========== -->
<main class="container my-4">
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h1 class="h3">
<span class="text-muted"><?php echo Text::_('JERROR_LAYOUT_ERROR_HAS_OCCURRED'); ?>:</span>
<strong><?php echo (int) $errorCode; ?></strong>
</h1>
<p class="lead mb-1">
<?php echo htmlspecialchars($errorMsg, ENT_QUOTES, 'UTF-8'); ?>
</p>
<p class="text-muted mb-0">
<?php echo Text::_('JERROR_LAYOUT_PLEASE_TRY_ONE_OF_THE_FOLLOWING_PAGES'); ?>
</p>
</div>
</div>
<div class="d-flex gap-2 flex-wrap">
<a class="btn btn-primary" href="<?php echo htmlspecialchars(Uri::base(), ENT_QUOTES, 'UTF-8'); ?>">
<i class="fa-solid fa-home me-1" aria-hidden="true"></i>
<?php echo Text::_('JERROR_LAYOUT_HOME_PAGE'); ?>
</a>
<button class="btn btn-outline-secondary" type="button" onclick="history.back();">
<i class="fa-solid fa-arrow-left me-1" aria-hidden="true"></i>
<?php echo Text::_('JPREV'); ?>
</button>
</div>
<?php if ($debugOn && $errorObj) : ?>
<section class="mt-4" role="region" aria-label="Debug Details">
<div class="alert alert-warning"><strong>Debug mode is ON</strong> — detailed error information is shown below.</div>
<div class="card mb-3">
<div class="card-header fw-bold">Exception</div>
<div class="card-body small">
<dl class="row mb-0">
<dt class="col-sm-3">Class</dt>
<dd class="col-sm-9"><?php echo htmlspecialchars(get_class($errorObj), ENT_QUOTES, 'UTF-8'); ?></dd>
<dt class="col-sm-3">Code</dt>
<dd class="col-sm-9"><?php echo (int) $errorObj->getCode(); ?></dd>
<dt class="col-sm-3">Message</dt>
<dd class="col-sm-9 text-break"><?php echo htmlspecialchars($errorObj->getMessage(), ENT_QUOTES, 'UTF-8'); ?></dd>
<dt class="col-sm-3">File</dt>
<dd class="col-sm-9 text-break"><?php echo htmlspecialchars($errorObj->getFile(), ENT_QUOTES, 'UTF-8'); ?> : <?php echo (int) $errorObj->getLine(); ?></dd>
</dl>
</div>
</div>
<?php $trace = method_exists($errorObj, 'getTrace') ? $errorObj->getTrace() : []; ?>
<div class="card mb-3">
<div class="card-header fw-bold">Stack Trace (<?php echo count($trace); ?> frames)</div>
<div class="card-body small">
<?php if ($trace) : ?>
<ol class="mb-0 ps-3">
<?php foreach ($trace as $i => $frame) :
$file = $frame['file'] ?? '[internal]';
$line = isset($frame['line']) ? (int) $frame['line'] : 0;
$func = $frame['function'] ?? '';
$class= $frame['class'] ?? '';
$type = $frame['type'] ?? '';
?>
<li class="mb-2">
<div class="text-break"><code>#<?php echo $i; ?></code> <?php echo htmlspecialchars($class . $type . $func, ENT_QUOTES, 'UTF-8'); ?>()</div>
<div class="text-muted"><?php echo htmlspecialchars($file, ENT_QUOTES, 'UTF-8'); ?><?php echo $line ? ':' . $line : ''; ?></div>
</li>
<?php endforeach; ?>
</ol>
<?php else : ?>
<em>No stack trace available.</em>
<?php endif; ?>
</div>
</div>
</section>
<?php endif; ?>
</main>
<footer class="container-footer footer full-width">
<?php if ($this->countModules('footer-menu', true)) : ?>
<div class="grid-child footer-menu">
<jdoc:include type="modules" name="footer-menu" />
</div>
<?php endif; ?>
<?php if ($this->countModules('footer', true)) : ?>
<div class="grid-child">
<jdoc:include type="modules" name="footer" style="none" />
</div>
<?php endif; ?>
</footer>
<?php if ($this->params->get('backTop') == 1) : ?>
<a href="#top" id="back-top" class="back-to-top-link" aria-label="<?php echo Text::_('TPL_MOKOONYX_BACKTOTOP'); ?>">
<span class="fa-solid fa-arrow-up" aria-hidden="true"></span>
</a>
<?php endif; ?>
<?php if ($this->countModules('drawer-left', true)) : ?>
<!-- Left Offcanvas Drawer -->
<aside class="offcanvas offcanvas-start" tabindex="-1" id="drawer-left">
<div class="offcanvas-header">
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="<?php echo Text::_('JLIB_HTML_BEHAVIOR_CLOSE'); ?>"></button>
</div>
<div class="offcanvas-body">
<jdoc:include type="modules" name="drawer-left" style="none" />
</div>
</aside>
<?php endif; ?>
<?php if ($this->countModules('drawer-right', true)) : ?>
<!-- Right Offcanvas Drawer -->
<aside class="offcanvas offcanvas-end" tabindex="-1" id="drawer-right">
<div class="offcanvas-header">
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="<?php echo Text::_('JLIB_HTML_BEHAVIOR_CLOSE'); ?>"></button>
</div>
<div class="offcanvas-body">
<jdoc:include type="modules" name="drawer-right" style="none" />
</div>
</aside>
<?php endif; ?>
<jdoc:include type="modules" name="debug" style="none" />
</body>
</html>

518
src/helper/favicon.php Normal file
View File

@@ -0,0 +1,518 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Favicon generator — creates ICO, Apple Touch Icon, and Android icons
* from a single source image uploaded via the template config.
*
* Supports three backends in priority order:
* 1. GD (fastest, most common)
* 2. Imagick (common on shared hosting)
* 3. Pure PHP (zero-dependency fallback using raw PNG manipulation)
*/
defined('_JEXEC') or die;
use Joomla\CMS\Log\Log;
class MokoFaviconHelper
{
private const SIZES = [
'apple-touch-icon.png' => [180, 180],
'favicon-32x32.png' => [32, 32],
'favicon-16x16.png' => [16, 16],
'android-chrome-192x192.png' => [192, 192],
'android-chrome-512x512.png' => [512, 512],
];
/**
* Generate all favicon files from a source image.
*/
public static function generate(string $sourcePath, string $outputDir): bool
{
if (!is_file($sourcePath)) {
self::log('Favicon: source file not found: ' . $sourcePath, 'warning');
return false;
}
if (!is_dir($outputDir)) {
mkdir($outputDir, 0755, true);
}
$sourceTime = filemtime($sourcePath);
$stampFile = $outputDir . '/.favicon_generated';
if (is_file($stampFile) && filemtime($stampFile) >= $sourceTime) {
return true;
}
// Strip #joomlaImage fragment if present
$sourcePath = strtok($sourcePath, '#');
// Select backend
if (extension_loaded('gd')) {
$result = self::generateWithGd($sourcePath, $outputDir);
} elseif (extension_loaded('imagick')) {
$result = self::generateWithImagick($sourcePath, $outputDir);
} else {
$result = self::generatePurePHP($sourcePath, $outputDir);
}
if ($result) {
self::generateManifest($outputDir);
file_put_contents($stampFile, date('c'));
}
return $result;
}
// ── GD Backend ──────────────────────────────────────────────────
private static function generateWithGd(string $sourcePath, string $outputDir): bool
{
$imageInfo = @getimagesize($sourcePath);
if ($imageInfo === false) {
self::log('Favicon: cannot read image: ' . $sourcePath, 'warning');
return false;
}
$source = match ($imageInfo[2]) {
IMAGETYPE_PNG => @imagecreatefrompng($sourcePath),
IMAGETYPE_JPEG => @imagecreatefromjpeg($sourcePath),
IMAGETYPE_GIF => @imagecreatefromgif($sourcePath),
IMAGETYPE_WEBP => function_exists('imagecreatefromwebp') ? @imagecreatefromwebp($sourcePath) : false,
default => false,
};
if (!$source) {
self::log('Favicon: unsupported image type', 'warning');
return false;
}
imagealphablending($source, false);
imagesavealpha($source, true);
$srcW = imagesx($source);
$srcH = imagesy($source);
foreach (self::SIZES as $filename => [$w, $h]) {
$resized = imagecreatetruecolor($w, $h);
imagealphablending($resized, false);
imagesavealpha($resized, true);
imagefill($resized, 0, 0, imagecolorallocatealpha($resized, 0, 0, 0, 127));
imagecopyresampled($resized, $source, 0, 0, 0, 0, $w, $h, $srcW, $srcH);
imagepng($resized, $outputDir . '/' . $filename, 9);
imagedestroy($resized);
}
// ICO from GD
$icoEntries = [];
foreach ([16, 32] as $size) {
$resized = imagecreatetruecolor($size, $size);
imagealphablending($resized, false);
imagesavealpha($resized, true);
imagefill($resized, 0, 0, imagecolorallocatealpha($resized, 0, 0, 0, 127));
imagecopyresampled($resized, $source, 0, 0, 0, 0, $size, $size, $srcW, $srcH);
ob_start();
imagepng($resized, null, 9);
$icoEntries[] = ['size' => $size, 'data' => ob_get_clean()];
imagedestroy($resized);
}
self::writeIco($icoEntries, $outputDir . '/favicon.ico');
imagedestroy($source);
self::log('Favicon: generated with GD');
return true;
}
// ── Imagick Backend ─────────────────────────────────────────────
private static function generateWithImagick(string $sourcePath, string $outputDir): bool
{
try {
foreach (self::SIZES as $filename => [$w, $h]) {
$img = new \Imagick($sourcePath);
$img->setImageFormat('png');
$img->setImageCompressionQuality(95);
$img->thumbnailImage($w, $h, true);
// Center on transparent canvas if not square
$canvas = new \Imagick();
$canvas->newImage($w, $h, new \ImagickPixel('transparent'), 'png');
$offsetX = (int)(($w - $img->getImageWidth()) / 2);
$offsetY = (int)(($h - $img->getImageHeight()) / 2);
$canvas->compositeImage($img, \Imagick::COMPOSITE_OVER, $offsetX, $offsetY);
$canvas->writeImage($outputDir . '/' . $filename);
$img->destroy();
$canvas->destroy();
}
// ICO from Imagick
$icoEntries = [];
foreach ([16, 32] as $size) {
$img = new \Imagick($sourcePath);
$img->setImageFormat('png');
$img->thumbnailImage($size, $size, true);
$icoEntries[] = ['size' => $size, 'data' => (string) $img];
$img->destroy();
}
self::writeIco($icoEntries, $outputDir . '/favicon.ico');
self::log('Favicon: generated with Imagick');
return true;
} catch (\Exception $e) {
self::log('Favicon: Imagick failed: ' . $e->getMessage(), 'warning');
return false;
}
}
// ── Pure PHP Backend (zero dependencies) ────────────────────────
private static function generatePurePHP(string $sourcePath, string $outputDir): bool
{
$pngData = @file_get_contents($sourcePath);
if ($pngData === false) {
self::log('Favicon: cannot read source file', 'warning');
return false;
}
// Detect format — we can only resize PNG in pure PHP
// For JPEG/other formats, just copy the source as-is for each size
$isPng = (substr($pngData, 0, 8) === "\x89PNG\r\n\x1a\n");
if (!$isPng) {
// Non-PNG: copy source file for all sizes (no resize capability without extensions)
foreach (self::SIZES as $filename => [$w, $h]) {
copy($sourcePath, $outputDir . '/' . $filename);
}
// ICO: embed the raw source for 16 and 32 entries
self::writeIco([
['size' => 16, 'data' => $pngData],
['size' => 32, 'data' => $pngData],
], $outputDir . '/favicon.ico');
self::log('Favicon: non-PNG source copied without resize (no GD/Imagick)');
return true;
}
// Parse PNG dimensions from IHDR
$ihdr = self::parsePngIhdr($pngData);
if (!$ihdr) {
self::log('Favicon: cannot parse PNG header', 'warning');
return false;
}
$srcW = $ihdr['width'];
$srcH = $ihdr['height'];
// Decode PNG to raw RGBA pixel array
$pixels = self::decodePngToRgba($pngData, $srcW, $srcH, $ihdr);
if ($pixels === null) {
// Fallback: copy source for all sizes
foreach (self::SIZES as $filename => [$w, $h]) {
copy($sourcePath, $outputDir . '/' . $filename);
}
self::writeIco([
['size' => 16, 'data' => $pngData],
['size' => 32, 'data' => $pngData],
], $outputDir . '/favicon.ico');
self::log('Favicon: PNG decode failed, copied source without resize');
return true;
}
// Generate resized PNGs
foreach (self::SIZES as $filename => [$w, $h]) {
$resized = self::resizePixels($pixels, $srcW, $srcH, $w, $h);
$png = self::encodePng($resized, $w, $h);
file_put_contents($outputDir . '/' . $filename, $png);
}
// ICO
$icoEntries = [];
foreach ([16, 32] as $size) {
$resized = self::resizePixels($pixels, $srcW, $srcH, $size, $size);
$icoEntries[] = ['size' => $size, 'data' => self::encodePng($resized, $size, $size)];
}
self::writeIco($icoEntries, $outputDir . '/favicon.ico');
self::log('Favicon: generated with pure PHP');
return true;
}
/**
* Parse PNG IHDR chunk.
*/
private static function parsePngIhdr(string $data): ?array
{
if (strlen($data) < 33) return null;
// Skip 8-byte signature, 4-byte chunk length, 4-byte "IHDR"
$width = unpack('N', substr($data, 16, 4))[1];
$height = unpack('N', substr($data, 20, 4))[1];
$bitDepth = ord($data[24]);
$colorType = ord($data[25]);
return ['width' => $width, 'height' => $height, 'bitDepth' => $bitDepth, 'colorType' => $colorType];
}
/**
* Decode PNG to flat RGBA array using zlib decompression.
*
* @return array|null Flat array of [r,g,b,a, r,g,b,a, ...] or null on failure.
*/
private static function decodePngToRgba(string $data, int $w, int $h, array $ihdr): ?array
{
// Only support 8-bit RGBA (color type 6) and RGB (color type 2) for simplicity
$colorType = $ihdr['colorType'];
$bitDepth = $ihdr['bitDepth'];
if ($bitDepth !== 8 || ($colorType !== 6 && $colorType !== 2 && $colorType !== 3)) {
return null; // Unsupported format
}
// Collect all IDAT chunks
$idatData = '';
$pos = 8; // Skip PNG signature
$palette = null;
$trns = null;
while ($pos < strlen($data) - 4) {
$chunkLen = unpack('N', substr($data, $pos, 4))[1];
$chunkType = substr($data, $pos + 4, 4);
if ($chunkType === 'IDAT') {
$idatData .= substr($data, $pos + 8, $chunkLen);
} elseif ($chunkType === 'PLTE') {
$palette = substr($data, $pos + 8, $chunkLen);
} elseif ($chunkType === 'tRNS') {
$trns = substr($data, $pos + 8, $chunkLen);
} elseif ($chunkType === 'IEND') {
break;
}
$pos += 12 + $chunkLen; // 4 len + 4 type + data + 4 crc
}
$raw = @gzuncompress($idatData);
if ($raw === false) {
$raw = @gzinflate($idatData);
}
if ($raw === false) {
// Try with zlib header
$raw = @gzinflate(substr($idatData, 2));
}
if ($raw === false) {
return null;
}
$bpp = $colorType === 6 ? 4 : ($colorType === 2 ? 3 : 1); // bytes per pixel
$stride = 1 + $w * $bpp; // +1 for filter byte per row
$pixels = [];
$prevRow = array_fill(0, $w * $bpp, 0);
for ($y = 0; $y < $h; $y++) {
$rowStart = $y * $stride;
if ($rowStart >= strlen($raw)) break;
$filter = ord($raw[$rowStart]);
$row = [];
for ($x = 0; $x < $w * $bpp; $x++) {
$rawByte = ord($raw[$rowStart + 1 + $x]);
$a = ($x >= $bpp) ? $row[$x - $bpp] : 0;
$b = $prevRow[$x];
$c = ($x >= $bpp) ? $prevRow[$x - $bpp] : 0;
$val = match ($filter) {
0 => $rawByte,
1 => ($rawByte + $a) & 0xFF,
2 => ($rawByte + $b) & 0xFF,
3 => ($rawByte + (int)(($a + $b) / 2)) & 0xFF,
4 => ($rawByte + self::paethPredictor($a, $b, $c)) & 0xFF,
default => $rawByte,
};
$row[] = $val;
}
// Convert row to RGBA
for ($x = 0; $x < $w; $x++) {
if ($colorType === 6) { // RGBA
$pixels[] = $row[$x * 4];
$pixels[] = $row[$x * 4 + 1];
$pixels[] = $row[$x * 4 + 2];
$pixels[] = $row[$x * 4 + 3];
} elseif ($colorType === 2) { // RGB
$pixels[] = $row[$x * 3];
$pixels[] = $row[$x * 3 + 1];
$pixels[] = $row[$x * 3 + 2];
$pixels[] = 255;
} elseif ($colorType === 3 && $palette) { // Indexed
$idx = $row[$x];
$pixels[] = ord($palette[$idx * 3]);
$pixels[] = ord($palette[$idx * 3 + 1]);
$pixels[] = ord($palette[$idx * 3 + 2]);
$pixels[] = ($trns && $idx < strlen($trns)) ? ord($trns[$idx]) : 255;
}
}
$prevRow = $row;
}
return $pixels;
}
private static function paethPredictor(int $a, int $b, int $c): int
{
$p = $a + $b - $c;
$pa = abs($p - $a);
$pb = abs($p - $b);
$pc = abs($p - $c);
if ($pa <= $pb && $pa <= $pc) return $a;
if ($pb <= $pc) return $b;
return $c;
}
/**
* Bilinear resize of RGBA pixel array.
*/
private static function resizePixels(array $src, int $srcW, int $srcH, int $dstW, int $dstH): array
{
$dst = [];
$xRatio = $srcW / $dstW;
$yRatio = $srcH / $dstH;
for ($y = 0; $y < $dstH; $y++) {
$srcY = $y * $yRatio;
$y0 = (int) $srcY;
$y1 = min($y0 + 1, $srcH - 1);
$yFrac = $srcY - $y0;
for ($x = 0; $x < $dstW; $x++) {
$srcX = $x * $xRatio;
$x0 = (int) $srcX;
$x1 = min($x0 + 1, $srcW - 1);
$xFrac = $srcX - $x0;
for ($c = 0; $c < 4; $c++) {
$tl = $src[($y0 * $srcW + $x0) * 4 + $c];
$tr = $src[($y0 * $srcW + $x1) * 4 + $c];
$bl = $src[($y1 * $srcW + $x0) * 4 + $c];
$br = $src[($y1 * $srcW + $x1) * 4 + $c];
$top = $tl + ($tr - $tl) * $xFrac;
$bot = $bl + ($br - $bl) * $xFrac;
$dst[] = (int) round($top + ($bot - $top) * $yFrac);
}
}
}
return $dst;
}
/**
* Encode RGBA pixel array to PNG binary.
*/
private static function encodePng(array $pixels, int $w, int $h): string
{
// Build raw image data with filter byte 0 (None) per row
$raw = '';
for ($y = 0; $y < $h; $y++) {
$raw .= "\x00"; // filter: None
for ($x = 0; $x < $w; $x++) {
$i = ($y * $w + $x) * 4;
$raw .= chr($pixels[$i]) . chr($pixels[$i + 1]) . chr($pixels[$i + 2]) . chr($pixels[$i + 3]);
}
}
$compressed = gzcompress($raw);
// Build PNG
$png = "\x89PNG\r\n\x1a\n";
// IHDR
$ihdr = pack('NNCCCC', $w, $h, 8, 6, 0, 0, 0); // 8-bit RGBA
$png .= self::pngChunk('IHDR', $ihdr);
// IDAT
$png .= self::pngChunk('IDAT', $compressed);
// IEND
$png .= self::pngChunk('IEND', '');
return $png;
}
private static function pngChunk(string $type, string $data): string
{
$chunk = $type . $data;
return pack('N', strlen($data)) . $chunk . pack('N', crc32($chunk));
}
// ── Shared Utilities ────────────────────────────────────────────
/**
* Write ICO file from PNG data entries.
*/
private static function writeIco(array $entries, string $outPath): void
{
$count = count($entries);
$ico = pack('vvv', 0, 1, $count);
$offset = 6 + ($count * 16);
$imageData = '';
foreach ($entries as $entry) {
$size = $entry['size'] >= 256 ? 0 : $entry['size'];
$dataLen = strlen($entry['data']);
$ico .= pack('CCCCvvVV', $size, $size, 0, 0, 1, 32, $dataLen, $offset);
$imageData .= $entry['data'];
$offset += $dataLen;
}
file_put_contents($outPath, $ico . $imageData);
}
private static function generateManifest(string $outputDir): void
{
$manifest = [
'icons' => [
['src' => 'android-chrome-192x192.png', 'sizes' => '192x192', 'type' => 'image/png'],
['src' => 'android-chrome-512x512.png', 'sizes' => '512x512', 'type' => 'image/png'],
],
];
file_put_contents(
$outputDir . '/site.webmanifest',
json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);
}
public static function getHeadTags(string $basePath): string
{
$basePath = htmlspecialchars(rtrim($basePath, '/'), ENT_QUOTES, 'UTF-8');
return '<link rel="apple-touch-icon" sizes="180x180" href="' . $basePath . '/apple-touch-icon.png">' . "\n"
. '<link rel="icon" type="image/png" sizes="32x32" href="' . $basePath . '/favicon-32x32.png">' . "\n"
. '<link rel="icon" type="image/png" sizes="16x16" href="' . $basePath . '/favicon-16x16.png">' . "\n"
. '<link rel="manifest" href="' . $basePath . '/site.webmanifest">' . "\n"
. '<link rel="shortcut icon" href="' . $basePath . '/favicon.ico">' . "\n";
}
private static function log(string $message, string $priority = 'info'): void
{
$priorities = [
'info' => Log::INFO,
'warning' => Log::WARNING,
'error' => Log::ERROR,
];
Log::addLogger(
['text_file' => 'mokoonyx.log.php'],
Log::ALL,
['mokoonyx']
);
Log::add($message, $priorities[$priority] ?? Log::INFO, 'mokoonyx');
}
}

1
src/helper/index.html Normal file
View File

@@ -0,0 +1 @@
<!DOCTYPE html><title></title>

165
src/helper/minify.php Normal file
View File

@@ -0,0 +1,165 @@
<?php
/**
* 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
*/
/**
* CSS/JS minifier — generates .min files from source when dev mode is off,
* deletes them when dev mode is on.
*/
defined('_JEXEC') or die;
class MokoMinifyHelper
{
/**
* Files to minify: source path relative to template media root.
* The .min variant is derived automatically (template.css → template.min.css).
*/
private const CSS_FILES = [
'css/template.css',
'css/offline.css',
'css/editor.css',
'css/a11y-high-contrast.css',
'css/theme/light.standard.css',
'css/theme/dark.standard.css',
'css/theme/light.custom.css',
'css/theme/dark.custom.css',
];
private const JS_FILES = [
'js/template.js',
];
/**
* When dev mode is ON: delete all .min files.
* When dev mode is OFF: regenerate .min files if source is newer.
*
* @param string $mediaRoot Absolute path to the template media directory.
* @param bool $devMode Whether development mode is enabled.
*/
public static function sync(string $mediaRoot, bool $devMode): void
{
$mediaRoot = rtrim($mediaRoot, '/\\');
foreach (self::CSS_FILES as $relPath) {
$source = $mediaRoot . '/' . $relPath;
$min = self::minPath($source);
if ($devMode) {
self::deleteIfExists($min);
} else {
self::buildIfStale($source, $min, 'css');
}
}
foreach (self::JS_FILES as $relPath) {
$source = $mediaRoot . '/' . $relPath;
$min = self::minPath($source);
if ($devMode) {
self::deleteIfExists($min);
} else {
self::buildIfStale($source, $min, 'js');
}
}
}
/**
* Derive the .min path from a source path.
* template.css → template.min.css
*/
private static function minPath(string $path): string
{
$info = pathinfo($path);
return $info['dirname'] . '/' . $info['filename'] . '.min.' . $info['extension'];
}
/**
* Delete a file if it exists.
*/
private static function deleteIfExists(string $path): void
{
if (is_file($path)) {
@unlink($path);
}
}
/**
* Build the minified file if the source is newer or the min file is missing.
*/
private static function buildIfStale(string $source, string $min, string $type): void
{
if (!is_file($source)) {
return;
}
// Skip if min file exists and is newer than source
if (is_file($min) && filemtime($min) >= filemtime($source)) {
return;
}
$content = file_get_contents($source);
if ($content === false) {
return;
}
$minified = ($type === 'css')
? self::minifyCss($content)
: self::minifyJs($content);
file_put_contents($min, $minified);
}
/**
* Minify CSS by stripping comments, excess whitespace, and unnecessary characters.
*/
private static function minifyCss(string $css): string
{
// Remove comments (but keep IE hacks like /*\*/)
$css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
// Remove whitespace around { } : ; , > + ~
$css = preg_replace('/\s*([{}:;,>+~])\s*/', '$1', $css);
// Remove remaining newlines and tabs
$css = preg_replace('/\s+/', ' ', $css);
// Remove spaces around selectors
$css = str_replace(['{ ', ' {', '; ', ' ;'], ['{', '{', ';', ';'], $css);
// Remove trailing semicolons before closing braces
$css = str_replace(';}', '}', $css);
// Remove leading/trailing whitespace
return trim($css);
}
/**
* Minify JS by stripping single-line comments, multi-line comments,
* and collapsing whitespace. Preserves string literals.
*/
private static function minifyJs(string $js): string
{
// Remove multi-line comments
$js = preg_replace('!/\*.*?\*/!s', '', $js);
// Remove single-line comments (but not URLs like http://)
$js = preg_replace('!(?<=^|[\s;{}()\[\]])//[^\n]*!m', '', $js);
// Collapse whitespace
$js = preg_replace('/\s+/', ' ', $js);
// Remove spaces around operators and punctuation
$js = preg_replace('/\s*([{}();,=+\-*\/<>!&|?:])\s*/', '$1', $js);
// Restore necessary spaces (after keywords)
$js = preg_replace('/(var|let|const|return|typeof|instanceof|new|delete|throw|case|in|of)([^\s;})><=!&|?:,])/', '$1 $2', $js);
return trim($js);
}
}

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,135 @@
<?php
/**
* 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
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Associations;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
// Load Bootstrap TOC assets
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->useStyle('vendor.bootstrap-toc');
$wa->useScript('vendor.bootstrap-toc.js');
// Get article params
$params = $this->item->params;
$images = json_decode($this->item->images);
$urls = json_decode($this->item->urls);
$canEdit = $params->get('access-edit');
$info = $params->get('info_block_position', 0);
// Check if associations are implemented
$assocParam = (Associations::isEnabled() && $params->get('show_associations'));
?>
<div class="com-content-article item-page<?php echo $this->pageclass_sfx; ?>">
<div class="row">
<!-- Table of Contents - Left Side -->
<div class="col-lg-3 col-md-4 order-md-1 mb-4">
<div class="sticky-top toc-wrapper" style="top: 20px;">
<nav id="toc" data-toggle="toc" class="toc-container">
<h5 class="toc-title"><?php echo Text::_('TPL_MOKOONYX_TOC_TITLE'); ?></h5>
</nav>
</div>
</div>
<!-- Article Content -->
<div class="col-lg-9 col-md-8 order-md-2">
<meta itemprop="inLanguage" content="<?php echo ($this->item->language === '*') ? Factory::getApplication()->get('language') : $this->item->language; ?>" />
<?php if ($this->params->get('show_page_heading')) : ?>
<div class="page-header">
<h1><?php echo $this->escape($this->params->get('page_heading')); ?></h1>
</div>
<?php endif; ?>
<?php if (!$this->print) : ?>
<?php if ($canEdit || $params->get('show_print_icon') || $params->get('show_email_icon')) : ?>
<?php echo LayoutHelper::render('joomla.content.icons', ['params' => $params, 'item' => $this->item, 'print' => false]); ?>
<?php endif; ?>
<?php else : ?>
<?php if ($params->get('show_print_icon')) : ?>
<?php echo LayoutHelper::render('joomla.content.icons', ['params' => $params, 'item' => $this->item, 'print' => true]); ?>
<?php endif; ?>
<?php endif; ?>
<?php echo $this->item->event->afterDisplayTitle; ?>
<?php if ($params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
<?php endif; ?>
<?php echo $this->item->event->beforeDisplayContent; ?>
<?php if (isset($urls) && ((!empty($urls->urls_position) && $urls->urls_position == '0') || ($params->get('urls_position') == '0' && empty($urls->urls_position))) || (empty($urls->urls_position) && (!$params->get('urls_position')))) : ?>
<?php echo $this->loadTemplate('links'); ?>
<?php endif; ?>
<?php if ($params->get('access-view')) : ?>
<?php echo LayoutHelper::render('joomla.content.full_image', $this->item); ?>
<?php if (isset($info) && $info == 0 && $params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
<?php echo LayoutHelper::render('joomla.content.info_block', ['item' => $this->item, 'params' => $params, 'position' => 'above']); ?>
<?php endif; ?>
<?php if ($info == 0 && $params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
<?php endif; ?>
<div class="article-content" itemprop="articleBody" data-toc-scope>
<?php echo $this->item->text; ?>
</div>
<?php if (isset($urls) && ((!empty($urls->urls_position) && $urls->urls_position == '1') || ($params->get('urls_position') == '1'))) : ?>
<?php echo $this->loadTemplate('links'); ?>
<?php endif; ?>
<?php elseif ($params->get('show_noauth') == true && $this->user->get('guest')) : ?>
<?php echo LayoutHelper::render('joomla.content.intro_image', $this->item); ?>
<?php echo HTMLHelper::_('content.prepare', $this->item->introtext); ?>
<?php endif; ?>
<?php echo $this->item->event->afterDisplayContent; ?>
<?php if (isset($info) && ($info == 1 || $info == 2)) : ?>
<?php if ($params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
<?php endif; ?>
<?php echo LayoutHelper::render('joomla.content.info_block', ['item' => $this->item, 'params' => $params, 'position' => 'below']); ?>
<?php endif; ?>
</div>
</div>
</div>
<style>
.toc-container {
background: var(--cassiopeia-color-bg, #fff);
border: 1px solid var(--cassiopeia-color-border, #dee2e6);
border-radius: 0.375rem;
padding: 1rem;
}
.toc-title {
margin-bottom: 0.75rem;
font-size: 1rem;
font-weight: 600;
color: var(--cassiopeia-color-text, #212529);
border-bottom: 1px solid var(--cassiopeia-color-border, #dee2e6);
padding-bottom: 0.5rem;
}
@media (max-width: 767.98px) {
.toc-wrapper {
position: static !important;
margin-bottom: 1.5rem;
}
}
</style>

View File

@@ -0,0 +1,135 @@
<?php
/**
* 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
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Associations;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
// Load Bootstrap TOC assets
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->useStyle('vendor.bootstrap-toc');
$wa->useScript('vendor.bootstrap-toc.js');
// Get article params
$params = $this->item->params;
$images = json_decode($this->item->images);
$urls = json_decode($this->item->urls);
$canEdit = $params->get('access-edit');
$info = $params->get('info_block_position', 0);
// Check if associations are implemented
$assocParam = (Associations::isEnabled() && $params->get('show_associations'));
?>
<div class="com-content-article item-page<?php echo $this->pageclass_sfx; ?>">
<div class="row">
<!-- Article Content -->
<div class="col-lg-9 col-md-8 order-md-1">
<meta itemprop="inLanguage" content="<?php echo ($this->item->language === '*') ? Factory::getApplication()->get('language') : $this->item->language; ?>" />
<?php if ($this->params->get('show_page_heading')) : ?>
<div class="page-header">
<h1><?php echo $this->escape($this->params->get('page_heading')); ?></h1>
</div>
<?php endif; ?>
<?php if (!$this->print) : ?>
<?php if ($canEdit || $params->get('show_print_icon') || $params->get('show_email_icon')) : ?>
<?php echo LayoutHelper::render('joomla.content.icons', ['params' => $params, 'item' => $this->item, 'print' => false]); ?>
<?php endif; ?>
<?php else : ?>
<?php if ($params->get('show_print_icon')) : ?>
<?php echo LayoutHelper::render('joomla.content.icons', ['params' => $params, 'item' => $this->item, 'print' => true]); ?>
<?php endif; ?>
<?php endif; ?>
<?php echo $this->item->event->afterDisplayTitle; ?>
<?php if ($params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
<?php endif; ?>
<?php echo $this->item->event->beforeDisplayContent; ?>
<?php if (isset($urls) && ((!empty($urls->urls_position) && $urls->urls_position == '0') || ($params->get('urls_position') == '0' && empty($urls->urls_position))) || (empty($urls->urls_position) && (!$params->get('urls_position')))) : ?>
<?php echo $this->loadTemplate('links'); ?>
<?php endif; ?>
<?php if ($params->get('access-view')) : ?>
<?php echo LayoutHelper::render('joomla.content.full_image', $this->item); ?>
<?php if (isset($info) && $info == 0 && $params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
<?php echo LayoutHelper::render('joomla.content.info_block', ['item' => $this->item, 'params' => $params, 'position' => 'above']); ?>
<?php endif; ?>
<?php if ($info == 0 && $params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
<?php endif; ?>
<div class="article-content" itemprop="articleBody" data-toc-scope>
<?php echo $this->item->text; ?>
</div>
<?php if (isset($urls) && ((!empty($urls->urls_position) && $urls->urls_position == '1') || ($params->get('urls_position') == '1'))) : ?>
<?php echo $this->loadTemplate('links'); ?>
<?php endif; ?>
<?php elseif ($params->get('show_noauth') == true && $this->user->get('guest')) : ?>
<?php echo LayoutHelper::render('joomla.content.intro_image', $this->item); ?>
<?php echo HTMLHelper::_('content.prepare', $this->item->introtext); ?>
<?php endif; ?>
<?php echo $this->item->event->afterDisplayContent; ?>
<?php if (isset($info) && ($info == 1 || $info == 2)) : ?>
<?php if ($params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
<?php endif; ?>
<?php echo LayoutHelper::render('joomla.content.info_block', ['item' => $this->item, 'params' => $params, 'position' => 'below']); ?>
<?php endif; ?>
</div>
<!-- Table of Contents - Right Side -->
<div class="col-lg-3 col-md-4 order-md-2 mb-4">
<div class="sticky-top toc-wrapper" style="top: 20px;">
<nav id="toc" data-toggle="toc" class="toc-container">
<h5 class="toc-title"><?php echo Text::_('TPL_MOKOONYX_TOC_TITLE'); ?></h5>
</nav>
</div>
</div>
</div>
</div>
<style>
.toc-container {
background: var(--cassiopeia-color-bg, #fff);
border: 1px solid var(--cassiopeia-color-border, #dee2e6);
border-radius: 0.375rem;
padding: 1rem;
}
.toc-title {
margin-bottom: 0.75rem;
font-size: 1rem;
font-weight: 600;
color: var(--cassiopeia-color-text, #212529);
border-bottom: 1px solid var(--cassiopeia-color-border, #dee2e6);
padding-bottom: 0.5rem;
}
@media (max-width: 767.98px) {
.toc-wrapper {
position: static !important;
margin-bottom: 1.5rem;
}
}
</style>

View File

@@ -0,0 +1,36 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Default layout override for mod_articles_archive.
* Adds showtitle support.
*/
defined('_JEXEC') or die;
if (empty($list)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-articles-archive<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-articles-archive__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<ul class="mod-articles-archive__list">
<?php foreach ($list as $item) : ?>
<li class="mod-articles-archive__item">
<a href="<?php echo $item->link; ?>"><?php echo $item->text; ?></a>
</li>
<?php endforeach; ?>
</ul>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,44 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Default layout override for mod_articles_categories.
* Adds showtitle support.
*/
defined('_JEXEC') or die;
if (empty($list)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
$showDescription = $params->get('show_description', 0);
$numitems = $params->get('numitems', 0);
?>
<div class="mod-articles-categories<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-articles-categories__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<ul class="mod-articles-categories__list">
<?php foreach ($list as $item) : ?>
<li class="mod-articles-categories__item">
<a href="<?php echo $item->link; ?>"><?php echo $item->title; ?></a>
<?php if ($numitems) : ?>
<span class="mod-articles-categories__count">(<?php echo $item->numitems; ?>)</span>
<?php endif; ?>
<?php if ($showDescription && !empty($item->description)) : ?>
<p class="mod-articles-categories__description"><?php echo $item->description; ?></p>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,78 @@
<?php
/**
* 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
*/
/**
* Default layout override for mod_articles_category.
* Adds showtitle support and respects module settings.
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
Factory::getApplication()->getLanguage()->load('mod_articles_category', JPATH_SITE);
if (empty($list)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-articles-category<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-articles-category__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<ul class="mod-articles-category__list">
<?php foreach ($list as $item) : ?>
<li class="mod-articles-category__item" itemscope itemtype="https://schema.org/Article">
<?php if ($params->get('link_titles') == 1) : ?>
<a class="mod-articles-category__link" href="<?php echo $item->link; ?>" itemprop="url">
<span itemprop="name"><?php echo $item->title; ?></span>
</a>
<?php else : ?>
<span itemprop="name"><?php echo $item->title; ?></span>
<?php endif; ?>
<?php if ($item->displayHits) : ?>
<span class="mod-articles-category__hits">
(<?php echo $item->displayHits; ?>)
</span>
<?php endif; ?>
<?php if ($params->get('show_author', 0)) : ?>
<span class="mod-articles-category__author">
<?php echo $item->displayAuthorName; ?>
</span>
<?php endif; ?>
<?php if ($item->displayDate) : ?>
<time class="mod-articles-category__date" datetime="<?php echo HTMLHelper::_('date', $item->displayDate, 'c'); ?>" itemprop="datePublished">
<?php echo $item->displayDate; ?>
</time>
<?php endif; ?>
<?php if ($params->get('show_introtext', 0)) : ?>
<div class="mod-articles-category__intro" itemprop="description">
<?php echo $item->displayIntrotext; ?>
</div>
<?php endif; ?>
<?php if ($params->get('show_readmore', 0)) : ?>
<a class="mod-articles-category__readmore" href="<?php echo $item->link; ?>" itemprop="url">
<?php echo Text::_('MOD_ARTICLES_CATEGORY_READ_MORE_TITLE'); ?>
</a>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,40 @@
<?php
/**
* 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
*/
/**
* Default layout override for mod_articles_latest.
* Adds showtitle support and respects module settings.
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
if (empty($list)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-articles-latest<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-articles-latest__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<ul class="mod-articles-latest__list">
<?php foreach ($list as $item) : ?>
<li class="mod-articles-latest__item" itemscope itemtype="https://schema.org/Article">
<a href="<?php echo $item->link; ?>" itemprop="url">
<span itemprop="name"><?php echo $item->title; ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,58 @@
<?php
/**
* 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
*/
/**
* Default layout override for mod_articles_news (newsflash).
* Adds showtitle support with card-based layout.
*/
defined('_JEXEC') or die;
if (empty($list)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-articles-news newsflash<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-articles-news__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<?php foreach ($list as $item) : ?>
<div class="mod-articles-news__item" itemscope itemtype="https://schema.org/Article">
<?php if ($params->get('item_title')) : ?>
<h4 class="mod-articles-news__item-title" itemprop="name">
<?php if ($item->link !== '' && $params->get('link_titles')) : ?>
<a href="<?php echo $item->link; ?>" itemprop="url"><?php echo $item->title; ?></a>
<?php else : ?>
<?php echo $item->title; ?>
<?php endif; ?>
</h4>
<?php endif; ?>
<?php if (!empty($item->afterDisplayTitle)) : ?>
<?php echo $item->afterDisplayTitle; ?>
<?php endif; ?>
<?php if ($params->get('show_introtext', 1)) : ?>
<div class="mod-articles-news__intro" itemprop="description">
<?php echo $item->introtext; ?>
</div>
<?php endif; ?>
<?php if (isset($item->readmore) && $item->readmore) : ?>
<a class="mod-articles-news__readmore" href="<?php echo $item->link; ?>" itemprop="url">
<?php echo $item->linkText; ?>
</a>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,38 @@
<?php
/**
* 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
*/
/**
* Default layout override for mod_articles_popular.
* Adds showtitle support and respects module settings.
*/
defined('_JEXEC') or die;
if (empty($list)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-articles-popular<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-articles-popular__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<ul class="mod-articles-popular__list">
<?php foreach ($list as $item) : ?>
<li class="mod-articles-popular__item" itemscope itemtype="https://schema.org/Article">
<a href="<?php echo $item->link; ?>" itemprop="url">
<span itemprop="name"><?php echo $item->title; ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,52 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Default layout override for mod_banners.
* Adds showtitle support.
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
if (empty($list)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-banners<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-banners__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<?php foreach ($list as $item) : ?>
<div class="mod-banners__item">
<?php $link = $item->params->get('url') ?: ''; ?>
<?php if ($item->type == 1) : ?>
<?php // Image banner ?>
<?php $imageUrl = $item->params->get('imageurl', ''); ?>
<?php $alt = htmlspecialchars($item->name, ENT_COMPAT, 'UTF-8'); ?>
<?php if ($link) : ?>
<a href="<?php echo htmlspecialchars($link, ENT_COMPAT, 'UTF-8'); ?>" target="_blank" rel="noopener noreferrer">
<img src="<?php echo htmlspecialchars($imageUrl, ENT_COMPAT, 'UTF-8'); ?>" alt="<?php echo $alt; ?>" class="mod-banners__image" loading="lazy" />
</a>
<?php else : ?>
<img src="<?php echo htmlspecialchars($imageUrl, ENT_COMPAT, 'UTF-8'); ?>" alt="<?php echo $alt; ?>" class="mod-banners__image" loading="lazy" />
<?php endif; ?>
<?php else : ?>
<?php // Custom HTML banner ?>
<?php echo $item->custombannercode; ?>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,53 @@
<?php
/**
* 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
*/
/**
* Default layout override for mod_breadcrumbs.
* Bootstrap 5 breadcrumb with schema.org BreadcrumbList markup.
* Module settings (showHome, showLast, homeText) are handled by Joomla core
* before $list reaches this template.
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
$showHere = $params->get('showHere', 1);
if (empty($list)) {
return;
}
?>
<nav class="mod-breadcrumbs<?php echo $suffix ? ' ' . $suffix : ''; ?>" aria-label="<?php echo Text::_('MOD_BREADCRUMBS_HERE'); ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-breadcrumbs__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<?php if ($showHere) : ?>
<span class="mod-breadcrumbs__here"><?php echo Text::_('MOD_BREADCRUMBS_HERE'); ?></span>
<?php endif; ?>
<ol class="breadcrumb" itemscope itemtype="https://schema.org/BreadcrumbList">
<?php foreach ($list as $key => $item) : ?>
<?php $isLast = ($key === array_key_last($list)); ?>
<li class="breadcrumb-item<?php echo $isLast ? ' active' : ''; ?>" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"
<?php echo $isLast ? ' aria-current="page"' : ''; ?>>
<?php if (!$isLast && !empty($item->link)) : ?>
<a href="<?php echo $item->link; ?>" itemprop="item">
<span itemprop="name"><?php echo $item->name; ?></span>
</a>
<?php else : ?>
<span itemprop="name"><?php echo $item->name; ?></span>
<?php endif; ?>
<meta itemprop="position" content="<?php echo $key + 1; ?>" />
</li>
<?php endforeach; ?>
</ol>
</nav>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,39 @@
<?php
/**
* 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
*/
/**
* Default layout override for mod_custom.
* Adds showtitle support and respects all module settings.
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Uri\Uri;
$modId = 'mod-custom' . $module->id;
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
if ($params->get('backgroundimage')) {
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $app->getDocument()->getWebAssetManager();
$wa->addInlineStyle(
'#' . $modId . '{background-image: url("' . Uri::root(true) . '/' . HTMLHelper::_('cleanImageURL', $params->get('backgroundimage'))->url . '");}',
['name' => $modId]
);
}
?>
<div class="mod-custom custom<?php echo $suffix ? ' ' . $suffix : ''; ?>" id="<?php echo $modId; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-custom__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<?php echo $module->content; ?>
</div>

View File

@@ -0,0 +1,42 @@
<?php
/**
* 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
*/
/**
* Template override for mod_custom adding banner-overlay wrapper pattern.
* Based on Cassiopeia's banner layout approach.
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Uri\Uri;
$modId = 'mod-custom' . $module->id;
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
if ($params->get('backgroundimage')) {
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $app->getDocument()->getWebAssetManager();
$wa->addInlineStyle(
'#' . $modId . '{background-image: url("' . Uri::root(true) . '/' . HTMLHelper::_('cleanImageURL', $params->get('backgroundimage'))->url . '");}',
['name' => $modId]
);
}
?>
<div class="mod-custom custom banner-overlay custom-hero<?php echo $suffix ? ' ' . $suffix : ''; ?>" id="<?php echo $modId; ?>">
<div class="overlay">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-custom__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<?php echo $module->content; ?>
</div>
</div>

View File

@@ -0,0 +1,88 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Default layout override for mod_feed.
* Adds showtitle support.
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
if (!$feed) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
$rssurl = $params->get('rssurl', '');
$rsstitle = $params->get('rsstitle', 1);
$rssdesc = $params->get('rssrtl', 0) ? ' feed-rtl' : '';
$rssimage = $params->get('rssimage', 1);
$rssitems = $params->get('rssitems', 5);
$rssitemdesc = $params->get('rssitemdesc', 1);
$word_count = $params->get('word_count', 0);
?>
<div class="mod-feed<?php echo $suffix ? ' ' . $suffix : ''; ?><?php echo $rssdesc; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-feed__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<?php if ($feed->title && $rsstitle) : ?>
<h4 class="mod-feed__feed-title">
<?php if (!empty($rssurl)) : ?>
<a href="<?php echo htmlspecialchars($rssurl, ENT_COMPAT, 'UTF-8'); ?>" target="_blank" rel="noopener noreferrer">
<?php echo $feed->title; ?>
</a>
<?php else : ?>
<?php echo $feed->title; ?>
<?php endif; ?>
</h4>
<?php endif; ?>
<?php if ($feed->description && $rssdesc) : ?>
<p class="mod-feed__description"><?php echo $feed->description; ?></p>
<?php endif; ?>
<?php if ($rssimage && $feed->image) : ?>
<img src="<?php echo $feed->image->uri; ?>" alt="<?php echo $feed->image->title ?? ''; ?>" class="mod-feed__image" />
<?php endif; ?>
<?php if (!empty($feed->items)) : ?>
<ul class="mod-feed__list">
<?php for ($i = 0, $max = min(count($feed->items), $rssitems); $i < $max; $i++) :
$item = $feed->items[$i];
?>
<li class="mod-feed__item">
<?php if (!empty($item->uri)) : ?>
<a href="<?php echo htmlspecialchars($item->uri, ENT_COMPAT, 'UTF-8'); ?>" target="_blank" rel="noopener noreferrer">
<?php echo $item->title; ?>
</a>
<?php else : ?>
<?php echo $item->title; ?>
<?php endif; ?>
<?php if ($rssitemdesc && !empty($item->content)) :
$desc = $item->content;
if ($word_count) {
$words = explode(' ', strip_tags($desc));
if (count($words) > $word_count) {
$desc = implode(' ', array_slice($words, 0, $word_count)) . '&hellip;';
}
}
?>
<p class="mod-feed__item-description"><?php echo $desc; ?></p>
<?php endif; ?>
</li>
<?php endfor; ?>
</ul>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,85 @@
<?php
/**
* 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
*/
/**
* Default layout override for mod_finder (Smart Search).
* Bootstrap 5 search form with showtitle support.
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
// Load component language for search labels
$lang = $app->getLanguage();
$lang->load('com_finder', JPATH_SITE);
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
$showLabel = $params->get('show_label', 1);
$labelClass = (!$showLabel ? 'visually-hidden ' : '') . 'finder';
Text::script('MOD_FINDER_SEARCH_VALUE');
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $app->getDocument()->getWebAssetManager();
$wa->getRegistry()->addExtensionRegistryFile('com_finder');
if ($params->get('show_autosuggest', 1)) {
$wa->usePreset('awesomplete');
$app->getDocument()->addScriptOptions('finder-search', ['url' => Route::_('index.php?option=com_finder&task=suggestions.suggest&format=json&tmpl=component', false)]);
Text::script('COM_FINDER_SEARCH_FORM_LIST_LABEL');
Text::script('JLIB_JS_AJAX_ERROR_OTHER');
Text::script('JLIB_JS_AJAX_ERROR_PARSE');
}
$wa->useScript('com_finder.finder');
?>
<div class="mod-finder<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-finder__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<form class="mod-finder__form js-finder-searchform form-search" action="<?php echo Route::_($route); ?>" method="get" role="search">
<label for="mod-finder-searchword<?php echo $module->id; ?>" class="<?php echo $labelClass; ?>">
<?php echo $params->get('alt_label', Text::_('JSEARCH_FILTER_SUBMIT')); ?>
</label>
<div class="input-group">
<input type="text" name="q" id="mod-finder-searchword<?php echo $module->id; ?>"
class="js-finder-search-query form-control"
value="<?php echo htmlspecialchars($app->getInput()->get('q', '', 'string'), ENT_COMPAT, 'UTF-8'); ?>"
placeholder="<?php echo Text::_('MOD_FINDER_SEARCH_VALUE'); ?>">
<?php if ($params->get('show_button', 0)) : ?>
<button class="btn btn-primary" type="submit">
<span class="fa-solid fa-magnifying-glass" aria-hidden="true"></span>
<span class="visually-hidden"><?php echo Text::_('JSEARCH_FILTER_SUBMIT'); ?></span>
</button>
<?php endif; ?>
</div>
<?php $show_advanced = $params->get('show_advanced', 0); ?>
<?php if ($show_advanced == 2) : ?>
<a href="<?php echo Route::_($route); ?>" class="mod-finder__advanced-link mt-2 d-inline-block">
<?php echo Text::_('COM_FINDER_ADVANCED_SEARCH'); ?>
</a>
<?php elseif ($show_advanced == 1) : ?>
<div class="mod-finder__advanced js-finder-advanced mt-2">
<?php echo HTMLHelper::_('filter.select', $query, $params); ?>
</div>
<?php endif; ?>
<?php
$finderHelper = $app->bootModule('mod_finder', 'site')->getHelper('FinderHelper');
echo $finderHelper->getHiddenFields($route);
?>
</form>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<?php
/**
* 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
*/
/**
* Default layout override for mod_footer.
* Adds showtitle support and respects module settings.
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-footer<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-footer__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<div class="mod-footer__line1"><?php echo $lineone; ?></div>
<div class="mod-footer__line2"><?php echo Text::_('MOD_FOOTER_LINE2'); ?></div>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,63 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Default layout override for mod_languages.
* Adds showtitle support.
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
if (empty($list)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-languages<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-languages__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<ul class="mod-languages__list">
<?php foreach ($list as $language) : ?>
<?php $isActive = $language->active ? ' active' : ''; ?>
<li class="mod-languages__item<?php echo $isActive; ?>" dir="<?php echo $language->rtl ? 'rtl' : 'ltr'; ?>">
<?php if ($language->active) : ?>
<span class="mod-languages__link mod-languages__link--active" lang="<?php echo $language->sef; ?>">
<?php else : ?>
<a class="mod-languages__link" href="<?php echo htmlspecialchars($language->link, ENT_COMPAT, 'UTF-8'); ?>" lang="<?php echo $language->sef; ?>">
<?php endif; ?>
<?php if ($params->get('image', 1)) : ?>
<?php if ($language->image) : ?>
<?php echo HTMLHelper::_('image', 'mod_languages/' . $language->image . '.gif', '', null, true); ?>
<?php else : ?>
<span class="mod-languages__badge badge bg-secondary"><?php echo strtoupper($language->sef); ?></span>
<?php endif; ?>
<?php endif; ?>
<?php if ($params->get('show_name', 1)) : ?>
<?php echo $language->title_native; ?>
<?php endif; ?>
<?php if ($language->active) : ?>
</span>
<?php else : ?>
</a>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,126 @@
<?php
/**
* 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
*/
/**
* Default layout override for mod_login.
* Bootstrap 5 login form with showtitle support.
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
Factory::getApplication()->getLanguage()->load('mod_login', JPATH_SITE);
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-login<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-login__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<?php if ($type === 'logout') : ?>
<form action="<?php echo Route::_('index.php', true); ?>" method="post" class="mod-login__form mod-login__form--logout">
<?php if ($params->get('greeting', 1)) : ?>
<div class="mod-login__greeting">
<?php if (!empty($user->name)) : ?>
<span class="mod-login__name"><?php echo Text::sprintf('MOD_LOGIN_HINAME', htmlspecialchars($user->name, ENT_COMPAT, 'UTF-8')); ?></span>
<?php else : ?>
<span class="mod-login__name"><?php echo Text::sprintf('MOD_LOGIN_HINAME', htmlspecialchars($user->username, ENT_COMPAT, 'UTF-8')); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="mod-login__submit">
<button type="submit" name="Submit" class="btn btn-primary w-100"><?php echo Text::_('JLOGOUT'); ?></button>
</div>
<input type="hidden" name="option" value="com_users">
<input type="hidden" name="task" value="user.logout">
<input type="hidden" name="return" value="<?php echo $return; ?>">
<?php echo HTMLHelper::_('form.token'); ?>
</form>
<?php else : ?>
<form action="<?php echo Route::_('index.php', true); ?>" method="post" class="mod-login__form mod-login__form--login">
<?php if ($params->get('pretext')) : ?>
<div class="mod-login__pretext"><?php echo $params->get('pretext'); ?></div>
<?php endif; ?>
<div class="mod-login__field mb-3">
<label for="modlgn-username-<?php echo $module->id; ?>" class="form-label visually-hidden"><?php echo Text::_('JGLOBAL_USERNAME'); ?></label>
<div class="input-group">
<span class="input-group-text"><i class="fa-solid fa-user" aria-hidden="true"></i></span>
<input id="modlgn-username-<?php echo $module->id; ?>" type="text" name="username" class="form-control" autocomplete="username" placeholder="<?php echo Text::_('JGLOBAL_USERNAME'); ?>">
</div>
</div>
<div class="mod-login__field mb-3">
<label for="modlgn-passwd-<?php echo $module->id; ?>" class="form-label visually-hidden"><?php echo Text::_('JGLOBAL_PASSWORD'); ?></label>
<div class="input-group">
<span class="input-group-text"><i class="fa-solid fa-lock" aria-hidden="true"></i></span>
<input id="modlgn-passwd-<?php echo $module->id; ?>" type="password" name="password" class="form-control" autocomplete="current-password" placeholder="<?php echo Text::_('JGLOBAL_PASSWORD'); ?>">
</div>
</div>
<?php if (!empty($twofactormethods) && count($twofactormethods) > 1) : ?>
<div class="mod-login__field mb-3">
<label for="modlgn-secretkey-<?php echo $module->id; ?>" class="form-label visually-hidden"><?php echo Text::_('JGLOBAL_SECRETKEY'); ?></label>
<div class="input-group">
<span class="input-group-text"><i class="fa-solid fa-shield-halved" aria-hidden="true"></i></span>
<input id="modlgn-secretkey-<?php echo $module->id; ?>" type="text" name="secretkey" class="form-control" autocomplete="one-time-code" placeholder="<?php echo Text::_('JGLOBAL_SECRETKEY'); ?>">
</div>
</div>
<?php endif; ?>
<?php if ($params->get('remember', 1)) : ?>
<div class="mod-login__remember form-check mb-3">
<input id="modlgn-remember-<?php echo $module->id; ?>" type="checkbox" name="remember" class="form-check-input" value="yes">
<label for="modlgn-remember-<?php echo $module->id; ?>" class="form-check-label"><?php echo Text::_('JGLOBAL_REMEMBER_ME'); ?></label>
</div>
<?php endif; ?>
<div class="mod-login__submit mb-3">
<button type="submit" name="Submit" class="btn btn-primary w-100"><?php echo Text::_('JLOGIN'); ?></button>
</div>
<?php $usersConfig = \Joomla\CMS\Component\ComponentHelper::getParams('com_users'); ?>
<ul class="mod-login__options list-unstyled small">
<?php if ($usersConfig->get('allowUserRegistration')) : ?>
<li>
<a href="<?php echo Route::_('index.php?option=com_users&view=registration'); ?>">
<?php echo Text::_('MOD_LOGIN_REGISTER'); ?>
</a>
</li>
<?php endif; ?>
<li>
<a href="<?php echo Route::_('index.php?option=com_users&view=remind'); ?>">
<?php echo Text::_('MOD_LOGIN_FORGOT_YOUR_USERNAME'); ?>
</a>
</li>
<li>
<a href="<?php echo Route::_('index.php?option=com_users&view=reset'); ?>">
<?php echo Text::_('MOD_LOGIN_FORGOT_YOUR_PASSWORD'); ?>
</a>
</li>
</ul>
<input type="hidden" name="option" value="com_users">
<input type="hidden" name="task" value="user.login">
<input type="hidden" name="return" value="<?php echo $return; ?>">
<?php echo HTMLHelper::_('form.token'); ?>
<?php if ($params->get('posttext')) : ?>
<div class="mod-login__posttext"><?php echo $params->get('posttext'); ?></div>
<?php endif; ?>
</form>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,95 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Default layout override for mod_menu.
* Simple list menu with showtitle support, suitable for sidebars and footers.
*/
defined('_JEXEC') or die;
use Joomla\CMS\Helper\ModuleHelper;
$id = '';
if ($tagId = $params->get('tag_id', '')) {
$id = ' id="' . $tagId . '"';
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<nav class="mod-menu<?php echo $suffix ? ' ' . $suffix : ''; ?>"<?php echo $id; ?> aria-label="<?php echo htmlspecialchars($module->title, ENT_COMPAT, 'UTF-8'); ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-menu__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<ul class="mod-menu__list nav flex-column">
<?php foreach ($list as $i => &$item) :
$itemParams = $item->getParams();
$class = 'nav-item mod-menu__item item-' . $item->id;
if ($item->id == $default_id) {
$class .= ' default';
}
if ($item->id == $active_id || ($item->type === 'alias' && $itemParams->get('aliasoptions') == $active_id)) {
$class .= ' current';
}
if (in_array($item->id, $path)) {
$class .= ' active';
} elseif ($item->type === 'alias') {
$aliasToId = $itemParams->get('aliasoptions');
if (count($path) > 0 && $aliasToId == $path[count($path) - 1]) {
$class .= ' active';
} elseif (in_array($aliasToId, $path)) {
$class .= ' alias-parent-active';
}
}
if ($item->type === 'separator') {
$class .= ' divider';
}
if ($item->deeper) {
$class .= ' deeper';
}
if ($item->parent) {
$class .= ' parent';
}
echo '<li class="' . $class . '">';
switch ($item->type) :
case 'separator':
case 'component':
case 'heading':
case 'url':
require ModuleHelper::getLayoutPath('mod_menu', 'default_' . $item->type);
break;
default:
require ModuleHelper::getLayoutPath('mod_menu', 'default_url');
break;
endswitch;
if ($item->deeper) {
echo '<ul class="mod-menu__sub nav flex-column ms-3">';
} elseif ($item->shallower) {
echo '</li>';
echo str_repeat('</ul></li>', $item->level_diff);
} else {
echo '</li>';
}
endforeach;
?></ul>
</nav>

View File

@@ -0,0 +1,106 @@
<?php
/**
* 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
*/
/**
* Main Menu - Mobile responsive collapsible dropdown menu override
* Bootstrap 5 responsive navbar with hamburger menu
*/
defined('_JEXEC') or die;
use Joomla\CMS\Helper\ModuleHelper;
$id = '';
if ($tagId = $params->get('tag_id', '')) {
$id = ' id="' . $tagId . '"';
}
// Get module class suffix
$moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
// The menu class is deprecated. Use mod-menu instead
?>
<nav class="mod-menu mod-menu-main navbar navbar-expand-lg<?php echo $moduleclass_sfx; ?>"<?php echo $id; ?>>
<div class="container-fluid">
<!-- Hamburger toggle button for mobile -->
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#horizontalMenuCollapse-<?php echo $module->id; ?>" aria-controls="horizontalMenuCollapse-<?php echo $module->id; ?>" aria-expanded="false" aria-label="Toggle Menu">
<span class="fa-solid fa-bars" aria-hidden="true"></span>
</button>
<!-- Collapsible menu content -->
<div class="collapse navbar-collapse" id="horizontalMenuCollapse-<?php echo $module->id; ?>">
<ul class="navbar-nav mod-menu-main__list">
<?php foreach ($list as $i => &$item) :
$itemParams = $item->getParams();
$class = 'nav-item mod-menu-main__item item-' . $item->id;
if ($item->id == $default_id) {
$class .= ' default';
}
if ($item->id == $active_id || ($item->type === 'alias' && $itemParams->get('aliasoptions') == $active_id)) {
$class .= ' current';
}
if (in_array($item->id, $path)) {
$class .= ' active';
} elseif ($item->type === 'alias') {
$aliasToId = $itemParams->get('aliasoptions');
if (count($path) > 0 && $aliasToId == $path[count($path) - 1]) {
$class .= ' active';
} elseif (in_array($aliasToId, $path)) {
$class .= ' alias-parent-active';
}
}
if ($item->type === 'separator') {
$class .= ' divider';
}
if ($item->deeper) {
$class .= ' deeper dropdown';
}
if ($item->parent) {
$class .= ' parent';
}
echo '<li class="' . $class . '">';
switch ($item->type) :
case 'separator':
case 'component':
case 'heading':
case 'url':
require ModuleHelper::getLayoutPath('mod_menu', 'horizontal_' . $item->type);
break;
default:
require ModuleHelper::getLayoutPath('mod_menu', 'horizontal_url');
break;
endswitch;
// The next item is deeper.
if ($item->deeper) {
echo '<ul class="dropdown-menu mod-menu-main__dropdown">';
} elseif ($item->shallower) {
// The next item is shallower.
echo '</li>';
echo str_repeat('</ul></li>', $item->level_diff);
} else {
// The next item is on the same level.
echo '</li>';
}
endforeach;
?></ul>
</div>
</div>
</nav>

View File

@@ -0,0 +1,66 @@
<?php
/**
* 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
*/
/**
* Main Menu - Component item layout
*/
defined('_JEXEC') or die;
use Joomla\CMS\Filter\OutputFilter;
use Joomla\CMS\HTML\HTMLHelper;
$attributes = [];
if ($item->anchor_title) {
$attributes['title'] = $item->anchor_title;
}
if ($item->anchor_css) {
$attributes['class'] = $item->anchor_css;
}
if ($item->anchor_rel) {
$attributes['rel'] = $item->anchor_rel;
}
$linktype = $item->title;
if ($item->menu_icon) {
// The link is an icon
if ($itemParams->get('menu_text', 1)) {
// If the link text is to be displayed, the icon is added with aria-hidden
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span>' . $item->title;
} else {
// If the icon itself is the link, it needs a visually hidden text
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span><span class="visually-hidden">' . $item->title . '</span>';
}
}
if ($item->browserNav == 1) {
$attributes['target'] = '_blank';
$attributes['rel'] = 'noopener noreferrer';
} elseif ($item->browserNav == 2) {
$options = 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,' . $params->get('window_open');
$attributes['onclick'] = "window.open(this.href, 'targetWindow', '" . $options . "'); return false;";
}
// Add dropdown toggle for items with children
$linkClass = 'nav-link mod-menu-main__link';
if ($item->deeper) {
$linkClass .= ' dropdown-toggle';
$attributes['data-bs-toggle'] = 'dropdown';
$attributes['role'] = 'button';
$attributes['aria-expanded'] = 'false';
}
$attributes['class'] = $linkClass;
echo HTMLHelper::_('link', OutputFilter::ampReplace(htmlspecialchars($item->flink, ENT_COMPAT, 'UTF-8', false)), $linktype, $attributes);

View File

@@ -0,0 +1,39 @@
<?php
/**
* 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
*/
/**
* Main Menu - Heading item layout
*/
defined('_JEXEC') or die;
$title = $item->anchor_title ? ' title="' . $item->anchor_title . '"' : '';
$anchor_css = $item->anchor_css ?: '';
$linktype = $item->title;
if ($item->menu_icon) {
// The link is an icon
if ($itemParams->get('menu_text', 1)) {
// If the link text is to be displayed, the icon is added with aria-hidden
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span>' . $item->title;
} else {
// If the icon itself is the link, it needs a visually hidden text
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span><span class="visually-hidden">' . $item->title . '</span>';
}
}
// Add dropdown toggle for items with children
$headingClass = 'nav-link mod-menu-main__heading';
if ($item->deeper) {
$headingClass .= ' dropdown-toggle';
}
?>
<span class="<?php echo $headingClass . ' ' . $anchor_css; ?>"<?php echo $title; ?>><?php echo $linktype; ?></span>

View File

@@ -0,0 +1,33 @@
<?php
/**
* 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
*/
/**
* Main Menu - Separator item layout
*/
defined('_JEXEC') or die;
$title = $item->anchor_title ? ' title="' . $item->anchor_title . '"' : '';
$anchor_css = $item->anchor_css ?: '';
$linktype = $item->title;
if ($item->menu_icon) {
// The link is an icon
if ($itemParams->get('menu_text', 1)) {
// If the link text is to be displayed, the icon is added with aria-hidden
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span>' . $item->title;
} else {
// If the icon itself is the link, it needs a visually hidden text
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span><span class="visually-hidden">' . $item->title . '</span>';
}
}
?>
<span class="dropdown-divider mod-menu-main__separator <?php echo $anchor_css; ?>"<?php echo $title; ?>><?php echo $linktype; ?></span>

View File

@@ -0,0 +1,71 @@
<?php
/**
* 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
*/
/**
* Main Menu - URL item layout
*/
defined('_JEXEC') or die;
use Joomla\CMS\Filter\OutputFilter;
use Joomla\CMS\HTML\HTMLHelper;
$attributes = [];
if ($item->anchor_title) {
$attributes['title'] = $item->anchor_title;
}
if ($item->anchor_css) {
$attributes['class'] = $item->anchor_css;
}
if ($item->anchor_rel) {
$attributes['rel'] = $item->anchor_rel;
}
$linktype = $item->title;
if ($item->menu_icon) {
// The link is an icon
if ($itemParams->get('menu_text', 1)) {
// If the link text is to be displayed, the icon is added with aria-hidden
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span>' . $item->title;
} else {
// If the icon itself is the link, it needs a visually hidden text
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span><span class="visually-hidden">' . $item->title . '</span>';
}
}
if ($item->browserNav == 1) {
$attributes['target'] = '_blank';
$attributes['rel'] = 'noopener noreferrer';
} elseif ($item->browserNav == 2) {
$options = 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,' . $params->get('window_open');
$attributes['onclick'] = "window.open(this.href, 'targetWindow', '" . $options . "'); return false;";
}
// Add dropdown toggle for items with children
$linkClass = 'nav-link mod-menu-main__link';
if ($item->deeper) {
$linkClass .= ' dropdown-toggle';
$attributes['data-bs-toggle'] = 'dropdown';
$attributes['role'] = 'button';
$attributes['aria-expanded'] = 'false';
}
// Merge existing class with our class
if (isset($attributes['class'])) {
$attributes['class'] .= ' ' . $linkClass;
} else {
$attributes['class'] = $linkClass;
}
echo HTMLHelper::_('link', OutputFilter::ampReplace(htmlspecialchars($item->flink, ENT_COMPAT, 'UTF-8', false)), $linktype, $attributes);

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,106 @@
<?php
/**
* 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
*/
/**
* Main Menu - Mobile responsive collapsible dropdown menu override
* Bootstrap 5 responsive navbar with hamburger menu
*/
defined('_JEXEC') or die;
use Joomla\CMS\Helper\ModuleHelper;
$id = '';
if ($tagId = $params->get('tag_id', '')) {
$id = ' id="' . $tagId . '"';
}
// Get module class suffix
$moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
// The menu class is deprecated. Use mod-menu instead
?>
<nav class="mod-menu mod-menu-main navbar navbar-expand-lg<?php echo $moduleclass_sfx; ?>"<?php echo $id; ?>>
<div class="container-fluid">
<!-- Hamburger toggle button for mobile -->
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainMenuCollapse-<?php echo $module->id; ?>" aria-controls="mainMenuCollapse-<?php echo $module->id; ?>" aria-expanded="false" aria-label="Toggle Main Menu">
<span class="fa-solid fa-bars" aria-hidden="true"></span>
</button>
<!-- Collapsible menu content -->
<div class="collapse navbar-collapse" id="mainMenuCollapse-<?php echo $module->id; ?>">
<ul class="navbar-nav mod-menu-main__list">
<?php foreach ($list as $i => &$item) :
$itemParams = $item->getParams();
$class = 'nav-item mod-menu-main__item item-' . $item->id;
if ($item->id == $default_id) {
$class .= ' default';
}
if ($item->id == $active_id || ($item->type === 'alias' && $itemParams->get('aliasoptions') == $active_id)) {
$class .= ' current';
}
if (in_array($item->id, $path)) {
$class .= ' active';
} elseif ($item->type === 'alias') {
$aliasToId = $itemParams->get('aliasoptions');
if (count($path) > 0 && $aliasToId == $path[count($path) - 1]) {
$class .= ' active';
} elseif (in_array($aliasToId, $path)) {
$class .= ' alias-parent-active';
}
}
if ($item->type === 'separator') {
$class .= ' divider';
}
if ($item->deeper) {
$class .= ' deeper dropdown';
}
if ($item->parent) {
$class .= ' parent';
}
echo '<li class="' . $class . '">';
switch ($item->type) :
case 'separator':
case 'component':
case 'heading':
case 'url':
require ModuleHelper::getLayoutPath('mod_menu', 'mainmenu_' . $item->type);
break;
default:
require ModuleHelper::getLayoutPath('mod_menu', 'mainmenu_url');
break;
endswitch;
// The next item is deeper.
if ($item->deeper) {
echo '<ul class="dropdown-menu mod-menu-main__dropdown">';
} elseif ($item->shallower) {
// The next item is shallower.
echo '</li>';
echo str_repeat('</ul></li>', $item->level_diff);
} else {
// The next item is on the same level.
echo '</li>';
}
endforeach;
?></ul>
</div>
</div>
</nav>

View File

@@ -0,0 +1,66 @@
<?php
/**
* 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
*/
/**
* Main Menu - Component item layout
*/
defined('_JEXEC') or die;
use Joomla\CMS\Filter\OutputFilter;
use Joomla\CMS\HTML\HTMLHelper;
$attributes = [];
if ($item->anchor_title) {
$attributes['title'] = $item->anchor_title;
}
if ($item->anchor_css) {
$attributes['class'] = $item->anchor_css;
}
if ($item->anchor_rel) {
$attributes['rel'] = $item->anchor_rel;
}
$linktype = $item->title;
if ($item->menu_icon) {
// The link is an icon
if ($itemParams->get('menu_text', 1)) {
// If the link text is to be displayed, the icon is added with aria-hidden
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span>' . $item->title;
} else {
// If the icon itself is the link, it needs a visually hidden text
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span><span class="visually-hidden">' . $item->title . '</span>';
}
}
if ($item->browserNav == 1) {
$attributes['target'] = '_blank';
$attributes['rel'] = 'noopener noreferrer';
} elseif ($item->browserNav == 2) {
$options = 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,' . $params->get('window_open');
$attributes['onclick'] = "window.open(this.href, 'targetWindow', '" . $options . "'); return false;";
}
// Add dropdown toggle for items with children
$linkClass = 'nav-link mod-menu-main__link';
if ($item->deeper) {
$linkClass .= ' dropdown-toggle';
$attributes['data-bs-toggle'] = 'dropdown';
$attributes['role'] = 'button';
$attributes['aria-expanded'] = 'false';
}
$attributes['class'] = $linkClass;
echo HTMLHelper::_('link', OutputFilter::ampReplace(htmlspecialchars($item->flink, ENT_COMPAT, 'UTF-8', false)), $linktype, $attributes);

View File

@@ -0,0 +1,39 @@
<?php
/**
* 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
*/
/**
* Main Menu - Heading item layout
*/
defined('_JEXEC') or die;
$title = $item->anchor_title ? ' title="' . $item->anchor_title . '"' : '';
$anchor_css = $item->anchor_css ?: '';
$linktype = $item->title;
if ($item->menu_icon) {
// The link is an icon
if ($itemParams->get('menu_text', 1)) {
// If the link text is to be displayed, the icon is added with aria-hidden
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span>' . $item->title;
} else {
// If the icon itself is the link, it needs a visually hidden text
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span><span class="visually-hidden">' . $item->title . '</span>';
}
}
// Add dropdown toggle for items with children
$headingClass = 'nav-link mod-menu-main__heading';
if ($item->deeper) {
$headingClass .= ' dropdown-toggle';
}
?>
<span class="<?php echo $headingClass . ' ' . $anchor_css; ?>"<?php echo $title; ?>><?php echo $linktype; ?></span>

View File

@@ -0,0 +1,33 @@
<?php
/**
* 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
*/
/**
* Main Menu - Separator item layout
*/
defined('_JEXEC') or die;
$title = $item->anchor_title ? ' title="' . $item->anchor_title . '"' : '';
$anchor_css = $item->anchor_css ?: '';
$linktype = $item->title;
if ($item->menu_icon) {
// The link is an icon
if ($itemParams->get('menu_text', 1)) {
// If the link text is to be displayed, the icon is added with aria-hidden
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span>' . $item->title;
} else {
// If the icon itself is the link, it needs a visually hidden text
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span><span class="visually-hidden">' . $item->title . '</span>';
}
}
?>
<span class="dropdown-divider mod-menu-main__separator <?php echo $anchor_css; ?>"<?php echo $title; ?>><?php echo $linktype; ?></span>

View File

@@ -0,0 +1,71 @@
<?php
/**
* 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
*/
/**
* Main Menu - URL item layout
*/
defined('_JEXEC') or die;
use Joomla\CMS\Filter\OutputFilter;
use Joomla\CMS\HTML\HTMLHelper;
$attributes = [];
if ($item->anchor_title) {
$attributes['title'] = $item->anchor_title;
}
if ($item->anchor_css) {
$attributes['class'] = $item->anchor_css;
}
if ($item->anchor_rel) {
$attributes['rel'] = $item->anchor_rel;
}
$linktype = $item->title;
if ($item->menu_icon) {
// The link is an icon
if ($itemParams->get('menu_text', 1)) {
// If the link text is to be displayed, the icon is added with aria-hidden
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span>' . $item->title;
} else {
// If the icon itself is the link, it needs a visually hidden text
$linktype = '<span class="p-2 ' . $item->menu_icon . '" aria-hidden="true"></span><span class="visually-hidden">' . $item->title . '</span>';
}
}
if ($item->browserNav == 1) {
$attributes['target'] = '_blank';
$attributes['rel'] = 'noopener noreferrer';
} elseif ($item->browserNav == 2) {
$options = 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,' . $params->get('window_open');
$attributes['onclick'] = "window.open(this.href, 'targetWindow', '" . $options . "'); return false;";
}
// Add dropdown toggle for items with children
$linkClass = 'nav-link mod-menu-main__link';
if ($item->deeper) {
$linkClass .= ' dropdown-toggle';
$attributes['data-bs-toggle'] = 'dropdown';
$attributes['role'] = 'button';
$attributes['aria-expanded'] = 'false';
}
// Merge existing class with our class
if (isset($attributes['class'])) {
$attributes['class'] .= ' ' . $linkClass;
} else {
$attributes['class'] = $linkClass;
}
echo HTMLHelper::_('link', OutputFilter::ampReplace(htmlspecialchars($item->flink, ENT_COMPAT, 'UTF-8', false)), $linktype, $attributes);

View File

@@ -0,0 +1,41 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Default layout override for mod_random_image.
* Adds showtitle support.
*/
defined('_JEXEC') or die;
if (empty($image)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-random-image<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-random-image__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<?php if ($link) : ?>
<a href="<?php echo htmlspecialchars($link, ENT_COMPAT, 'UTF-8'); ?>">
<?php endif; ?>
<img src="<?php echo htmlspecialchars($image->folder . '/' . $image->name, ENT_COMPAT, 'UTF-8'); ?>"
alt="<?php echo htmlspecialchars($image->name, ENT_COMPAT, 'UTF-8'); ?>"
<?php if ($image->width) : ?>width="<?php echo $image->width; ?>"<?php endif; ?>
<?php if ($image->height) : ?>height="<?php echo $image->height; ?>"<?php endif; ?>
class="mod-random-image__img"
loading="lazy" />
<?php if ($link) : ?>
</a>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,44 @@
<?php
/**
* 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
*/
/**
* Default layout override for mod_related_items.
* Adds showtitle support.
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
if (empty($list)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
$showDate = $params->get('showDate', 0);
?>
<div class="mod-related-items<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-related-items__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<ul class="mod-related-items__list">
<?php foreach ($list as $item) : ?>
<li class="mod-related-items__item">
<a href="<?php echo $item->route; ?>"><?php echo $item->title; ?></a>
<?php if ($showDate) : ?>
<time class="mod-related-items__date" datetime="<?php echo HTMLHelper::_('date', $item->created, 'c'); ?>">
<?php echo HTMLHelper::_('date', $item->created, 'DATE_FORMAT_LC3'); ?>
</time>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,39 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Default layout override for mod_stats.
* Adds showtitle support.
*/
defined('_JEXEC') or die;
if (empty($list)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-stats<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-stats__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<table class="mod_stats__table">
<tbody>
<?php foreach ($list as $item) : ?>
<tr>
<th class="mod_stats__label" scope="row"><?php echo $item->title; ?></th>
<td class="mod_stats__data"><?php echo $item->data; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,31 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Default layout override for mod_syndicate.
* Adds showtitle support.
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-syndicate<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-syndicate__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<a href="<?php echo $link; ?>" class="mod-syndicate__link">
<span class="fa-solid fa-rss" aria-hidden="true"></span>
<?php echo htmlspecialchars($text, ENT_COMPAT, 'UTF-8'); ?>
</a>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,41 @@
<?php
/**
* 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
*/
/**
* Default layout override for mod_tags_popular.
* Adds showtitle support with Bootstrap badge-style tags.
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
if (empty($list)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-tags-popular<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-tags-popular__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<div class="mod-tags-popular__list d-flex flex-wrap gap-2">
<?php foreach ($list as $item) : ?>
<a class="badge bg-secondary text-decoration-none mod-tags-popular__tag" href="<?php echo $item->link; ?>">
<?php echo htmlspecialchars($item->title, ENT_COMPAT, 'UTF-8'); ?>
<?php if ($params->get('show_tag_count', 0)) : ?>
<span class="mod-tags-popular__count">(<?php echo $item->count; ?>)</span>
<?php endif; ?>
</a>
<?php endforeach; ?>
</div>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,38 @@
<?php
/**
* 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
*/
/**
* Default layout override for mod_tags_similar.
* Adds showtitle support.
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
if (empty($list)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-tags-similar<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-tags-similar__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<ul class="mod-tags-similar__list">
<?php foreach ($list as $item) : ?>
<li class="mod-tags-similar__item">
<a href="<?php echo $item->link; ?>"><?php echo htmlspecialchars($item->core_title, ENT_COMPAT, 'UTF-8'); ?></a>
</li>
<?php endforeach; ?>
</ul>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,41 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Default layout override for mod_users_latest.
* Adds showtitle support.
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
if (empty($names)) {
return;
}
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
?>
<div class="mod-users-latest<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-users-latest__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<ul class="mod-users-latest__list">
<?php foreach ($names as $name) : ?>
<li class="mod-users-latest__item">
<?php echo htmlspecialchars($name->name, ENT_COMPAT, 'UTF-8'); ?>
<time class="mod-users-latest__date" datetime="<?php echo HTMLHelper::_('date', $name->registerDate, 'c'); ?>">
<?php echo HTMLHelper::_('date', $name->registerDate, 'DATE_FORMAT_LC3'); ?>
</time>
</li>
<?php endforeach; ?>
</ul>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,43 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Default layout override for mod_whosonline.
* Adds showtitle support.
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
$showmode = $params->get('showmode', 0);
?>
<div class="mod-whosonline<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-whosonline__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<?php if ($showmode == 0 || $showmode == 2) : ?>
<p class="mod-whosonline__count">
<?php echo Text::plural('MOD_WHOSONLINE_GUESTS', $count['guest']); ?><br />
<?php echo Text::plural('MOD_WHOSONLINE_MEMBERS', $count['user']); ?>
</p>
<?php endif; ?>
<?php if (($showmode == 1 || $showmode == 2) && !empty($names)) : ?>
<ul class="mod-whosonline__list">
<?php foreach ($names as $name) : ?>
<li class="mod-whosonline__item"><?php echo $name->username; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,39 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* Default layout override for mod_wrapper.
* Adds showtitle support.
*/
defined('_JEXEC') or die;
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
$url = htmlspecialchars($params->get('url', ''), ENT_COMPAT, 'UTF-8');
$width = htmlspecialchars($params->get('width', '100%'), ENT_COMPAT, 'UTF-8');
$height = htmlspecialchars($params->get('height', '500'), ENT_COMPAT, 'UTF-8');
$scrolling = $params->get('scrolling', 'auto');
$frameborder = $params->get('frameborder', 0) ? '1' : '0';
?>
<div class="mod-wrapper<?php echo $suffix ? ' ' . $suffix : ''; ?>">
<?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-wrapper__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?>
<iframe src="<?php echo $url; ?>"
width="<?php echo $width; ?>"
height="<?php echo $height; ?>"
scrolling="<?php echo $scrolling; ?>"
frameborder="<?php echo $frameborder; ?>"
title="<?php echo htmlspecialchars($module->title, ENT_COMPAT, 'UTF-8'); ?>"
class="mod-wrapper__iframe"
loading="lazy">
</iframe>
</div>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

76
src/index.html Normal file
View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

590
src/index.php Normal file
View File

@@ -0,0 +1,590 @@
<?php
/* 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
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\Component\ComponentHelper;
/** @var Joomla\CMS\Document\HtmlDocument $this */
$app = Factory::getApplication();
$input = $app->getInput();
$document = $app->getDocument();
$wa = $document->getWebAssetManager();
// Template params
$params_LightColorName = (string) $this->params->get('colorLightName', 'standard'); // standard|custom
$params_DarkColorName = (string) $this->params->get('colorDarkName', 'standard'); // standard|custom
$params_googletagmanager = $this->params->get('googletagmanager', false);
$params_googletagmanagerid = $this->params->get('googletagmanagerid', null);
$params_googleanalytics = $this->params->get('googleanalytics', false);
$params_googleanalyticsid = $this->params->get('googleanalyticsid', null);
$params_googlesitekey = $this->params->get('googlesitekey', null);
$params_custom_head_start = $this->params->get('custom_head_start', null);
$params_custom_head_end = $this->params->get('custom_head_end', null);
$params_developmentmode = $this->params->get('developmentmode', false) || $app->get('debug', false);
$params_favicon_source = (string) $this->params->get('favicon_source', '');
// Theme params
$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 = 'br';
// Accessibility params
$params_a11y_toolbar = $this->params->get('a11y_toolbar_enabled', 1);
$params_a11y_resize = $this->params->get('a11y_text_resize', 1);
$params_a11y_invert = $this->params->get('a11y_color_inversion', 1);
$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 = 'br';
// Detecting Active Variables
$option = $input->getCmd('option', '');
$view = $input->getCmd('view', '');
$layout = $input->getCmd('layout', '');
$task = $input->getCmd('task', '');
$itemid = $input->getCmd('Itemid', '');
$sitenameR = $app->get('sitename'); // raw for title composition
$sitename = htmlspecialchars($sitenameR, ENT_QUOTES, 'UTF-8');
$menu = $app->getMenu()->getActive();
$pageclass = $menu !== null ? $menu->getParams()->get('pageclass_sfx', '') : '';
// Template/Media path
$templatePath = 'media/templates/site/mokoonyx';
// Favicon generation
$faviconHeadTags = '';
if ($params_favicon_source) {
require_once __DIR__ . '/helper/favicon.php';
// Joomla's media field returns paths like:
// 'images/logo.png' (images folder)
// 'media/templates/site/mokoonyx/images/logo.png' (template media)
// 'logo.png' (bare filename)
// Strip Joomla's #joomlaImage:// fragment from media field value
$faviconSourceRel = strtok(ltrim($params_favicon_source, '/'), '#');
$faviconSourceAbs = JPATH_ROOT . '/' . $faviconSourceRel;
// Try common prefixes if not found
if (!is_file($faviconSourceAbs)) {
$candidates = [
JPATH_ROOT . '/images/' . $faviconSourceRel,
JPATH_ROOT . '/media/templates/site/' . $this->template . '/' . $faviconSourceRel,
JPATH_ROOT . '/media/templates/site/' . $this->template . '/images/' . basename($faviconSourceRel),
];
foreach ($candidates as $candidate) {
if (is_file($candidate)) {
$faviconSourceAbs = $candidate;
break;
}
}
}
$faviconOutputDir = JPATH_ROOT . '/images/favicons';
$faviconUrlBase = Uri::root(true) . '/images/favicons';
if (MokoFaviconHelper::generate($faviconSourceAbs, $faviconOutputDir)) {
$faviconHeadTags = MokoFaviconHelper::getHeadTags($faviconUrlBase);
}
}
// Minification: dev mode ON → delete .min files; OFF → regenerate if stale
require_once __DIR__ . '/helper/minify.php';
MokoMinifyHelper::sync(JPATH_ROOT . '/' . $templatePath, (bool) $params_developmentmode);
// Core template CSS + JS — use minified when not in development mode
if ($params_developmentmode) {
$wa->useStyle('template.base'); // css/template.css
$wa->useScript('template.js'); // js/template.js
} else {
$wa->useStyle('template.base.min'); // css/template.min.css
$wa->useScript('template.js.min'); // js/template.min.js
}
// Load Osaka font for site title
$wa->useStyle('template.font.osaka');
// Load GTM script if GTM is enabled
if (!empty($params_googletagmanager) && !empty($params_googletagmanagerid)) {
$wa->useScript('gtm.js');
}
/**
* VirtueMart detection:
* - Component must exist and be enabled
*/
$isVirtueMartActive = ComponentHelper::isEnabled('com_virtuemart', true);
if ($isVirtueMartActive) {
/**
* Load a VirtueMart-specific stylesheet defined in your template manifest.
* This assumes you defined an asset named "template.virtuemart".
*/
$wa->useStyle('vendor.vm');
}
// Font scheme (external or local) + CSS custom properties
$params_FontScheme = $this->params->get('useFontScheme', false);
$fontStyles = '';
if ($params_FontScheme) {
if (stripos($params_FontScheme, 'https://') === 0) {
$this->getPreloadManager()->preload($params_FontScheme, ['as' => 'style', 'crossorigin' => 'anonymous']);
$wa->registerAndUseStyle('fontscheme.current', $params_FontScheme, [], [
'media' => 'print',
'rel' => 'lazy-stylesheet',
'onload' => 'this.media=\'all\'',
'crossorigin' => 'anonymous'
]);
if (preg_match_all('/family=([^?:]*):/i', $params_FontScheme, $matches) > 0) {
$fontStyles = '--font-family-body: "' . str_replace('+', ' ', $matches[1][0]) . '", sans-serif;' . "\n";
$fontStyles .= '--font-family-headings: "' . str_replace('+', ' ', isset($matches[1][1]) ? $matches[1][1] : $matches[1][0]) . '", sans-serif;' . "\n";
$fontStyles .= '--font-weight-normal: 400;' . "\n";
$fontStyles .= '--font-weight-headings: 700;';
}
} else {
$wa->registerAndUseStyle('fontscheme.current', $params_FontScheme, ['version' => 'auto'], [
'media' => 'print',
'rel' => 'lazy-stylesheet',
'onload' => 'this.media=\'all\''
]);
$this->getPreloadManager()->preload(
$wa->getAsset('style', 'fontscheme.current')->getUri() . '?' . $this->getMediaVersion(),
['as' => 'style']
);
}
}
// -------------------------------------
// Brand: logo from params OR siteTitle
// -------------------------------------
$brandHtml = '';
$logoFile = (string) $this->params->get('logoFile');
if ($logoFile !== '') {
$brandHtml = HTMLHelper::_(
'image',
Uri::root(false) . htmlspecialchars($logoFile, ENT_QUOTES, 'UTF-8'),
$sitename,
['class' => 'logo d-inline-block', 'loading' => 'eager', 'decoding' => 'async'],
false,
0
);
} else {
// If no logo file, show the title (defaults to "MokoOnyx" if not set)
$siteTitle = $this->params->get('siteTitle', 'MokoOnyx');
$brandHtml = '<span class="site-title" title="' . $sitename . '">'
. htmlspecialchars($siteTitle, ENT_COMPAT, 'UTF-8')
. '</span>';
}
// Layout flags
$hasClass = '';
if ($this->countModules('sidebar-left', true)) { $hasClass .= ' has-sidebar-left'; }
if ($this->countModules('sidebar-right', true)) { $hasClass .= ' has-sidebar-right'; }
if ($this->countModules('drawer-left', true)) { $hasClass .= ' has-drawer-left'; }
if ($this->countModules('drawer-right', true)) { $hasClass .= ' has-drawer-right'; }
// Smart Bootstrap component loading - only load what's needed
HTMLHelper::_('bootstrap.collapse');
if ($this->countModules('drawer-left', true) || $this->countModules('drawer-right', true)) {
HTMLHelper::_('bootstrap.offcanvas');
}
// Container
$wrapper = $this->params->get('fluidContainer') ? 'wrapper-fluid' : 'wrapper-static';
$stickyHeader = $this->params->get('stickyHeader') ? 'position-sticky sticky-top' : '';
// Meta
$this->setMetaData('viewport', 'width=device-width, initial-scale=1');
if (!empty($params_googlesitekey)) {
$this->setMetaData('google-site-verification', htmlspecialchars($params_googlesitekey, ENT_QUOTES, 'UTF-8'));
}
// Font Awesome 7 — Kit (CDN) or local files
$faKitCode = trim((string) $this->params->get('fA6KitCode', ''));
if ($faKitCode !== '') {
HTMLHelper::_('script', 'https://kit.fontawesome.com/' . $faKitCode . '.js', ['crossorigin' => 'anonymous']);
} else {
// Load local FA7 Free — all.css via WebAsset
// Resolve the actual filesystem path: media dir (Joomla install) or template dir (SFTP deploy)
$faCssFile = $params_developmentmode ? 'vendor/fa7free/css/all.css' : 'vendor/fa7free/css/all.min.css';
$faCandidates = [
$templatePath . '/' . $faCssFile, // media/templates/site/mokoonyx/...
'templates/site/' . $this->template . '/media/' . $faCssFile, // templates/site/mokoonyx/media/...
];
// Also check via __DIR__ for edge cases
$faFromDir = __DIR__ . '/media/' . $faCssFile;
if (is_file($faFromDir)) {
$faCandidates[] = ltrim(str_replace('\\', '/', str_replace(JPATH_ROOT, '', $faFromDir)), '/');
}
$faRegistered = false;
foreach ($faCandidates as $faPath) {
if (is_file(JPATH_ROOT . '/' . $faPath)) {
$wa->registerStyle('vendor.fa7free.all', $faPath);
$faRegistered = true;
break;
}
}
// Use the asset — either our dynamic registration or the one from joomla.asset.json
try {
$wa->useStyle('vendor.fa7free.all');
} catch (\Throwable $e) {
// All paths exhausted — FA icons will rely on the IcoMoon compat layer
}
}
$params_leftIcon = htmlspecialchars($this->params->get('drawerLeftIcon', 'fa-solid fa-chevron-left'), ENT_COMPAT, 'UTF-8');
$params_rightIcon = htmlspecialchars($this->params->get('drawerRightIcon', 'fa-solid fa-chevron-right'), ENT_COMPAT, 'UTF-8');
// Load theme palette stylesheets — minified when not in development mode
$suffix = $params_developmentmode ? '' : '.min';
$wa->useStyle('template.light.standard' . $suffix);
$wa->useStyle('template.dark.standard' . $suffix);
// Load custom palettes only if selected in template configuration AND files exist
if ($params_LightColorName === 'custom' && file_exists(JPATH_ROOT . '/media/templates/site/mokoonyx/css/theme/light.custom.css'))
{
$wa->useStyle('template.light.custom' . $suffix);
}
if ($params_DarkColorName === 'custom' && file_exists(JPATH_ROOT . '/media/templates/site/mokoonyx/css/theme/dark.custom.css'))
{
$wa->useStyle('template.dark.custom' . $suffix);
}
// Load user assets last (after all other styles and scripts)
$wa->useStyle('template.user'); // css/user.css
$wa->useScript('user.js'); // js/user.js
?>
<!DOCTYPE html>
<html lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>">
<head>
<?php if (trim($params_custom_head_start)) : ?><?php echo $params_custom_head_start; ?><?php endif; ?>
<jdoc:include type="head" />
<?php if ($faviconHeadTags) : ?>
<?php echo $faviconHeadTags; ?>
<?php endif; ?>
<?php if ($params_theme_enabled) : ?>
<script>
// Early theme application to avoid FOUC
(function () {
try {
var stored = localStorage.getItem('theme');
var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
var theme = stored ? stored : (prefersDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-bs-theme', theme);
document.documentElement.setAttribute('data-aria-theme', theme);
} catch (e) {}
})();
</script>
<?php endif; ?>
<script>
// Facebook in-app browser warning banner
window.addEventListener('DOMContentLoaded', function () {
var ua = navigator.userAgent || navigator.vendor || window.opera;
var isFacebookBrowser = ua.indexOf('FBAN') > -1 || ua.indexOf('FBAV') > -1;
if (isFacebookBrowser) {
var warning = document.createElement('div');
warning.textContent = '⚠️ KNOWN ISSUE: Images do not load in Facebook Web browser. Please open in external browser for full experience.';
warning.style.position = 'fixed';
warning.style.top = '0';
warning.style.left = '0';
warning.style.right = '0';
warning.style.zIndex = '10000';
warning.style.backgroundColor = '#007bff';
warning.style.color = '#fff';
warning.style.padding = '15px';
warning.style.textAlign = 'center';
warning.style.fontWeight = 'bold';
warning.style.fontSize = '16px';
warning.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
document.body.appendChild(warning);
}
});
</script>
<?php if (trim($params_custom_head_end)) : ?><?php echo $params_custom_head_end; ?><?php endif; ?>
</head>
<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'); ?>"
data-a11y-toolbar="<?php echo $params_a11y_toolbar ? '1' : '0'; ?>"
data-a11y-resize="<?php echo $params_a11y_resize ? '1' : '0'; ?>"
data-a11y-invert="<?php echo $params_a11y_invert ? '1' : '0'; ?>"
data-a11y-contrast="<?php echo $params_a11y_contrast ? '1' : '0'; ?>"
data-a11y-links="<?php echo $params_a11y_links ? '1' : '0'; ?>"
data-a11y-font="<?php echo $params_a11y_font ? '1' : '0'; ?>"
data-a11y-animations="<?php echo $params_a11y_animations ? '1' : '0'; ?>"
data-a11y-pos="<?php echo htmlspecialchars($params_a11y_pos, ENT_QUOTES, 'UTF-8'); ?>"
class="site <?php
echo $option . ' ' . $wrapper
. ' view-' . $view
. ($layout ? ' layout-' . $layout : ' no-layout')
. ($task ? ' task-' . $task : ' no-task')
. ($itemid ? ' itemid-' . $itemid : '')
. ($pageclass ? ' ' . $pageclass : '')
. $hasClass
. ($this->direction == 'rtl' ? ' rtl' : '');
?>">
<?php if (!empty($params_googletagmanager) && !empty($params_googletagmanagerid)) :
$gtmID = htmlspecialchars($params_googletagmanagerid, ENT_QUOTES, 'UTF-8'); ?>
<!-- Google Tag Manager -->
<script>
(function(w,d,s,l,i){
w[l]=w[l]||[];
w[l].push({'gtm.start': new Date().getTime(), event:'gtm.js'});
var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),
dl=l!='dataLayer'?'&l='+l:'';
j.async=true;
j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;
f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','<?php echo $gtmID; ?>');
</script>
<!-- End Google Tag Manager -->
<!-- Google Tag Manager (noscript) -->
<noscript>
<iframe src="https://www.googletagmanager.com/ns.html?id=<?php echo $gtmID; ?>"
height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript>
<!-- End Google Tag Manager (noscript) -->
<?php endif; ?>
<?php if (!empty($params_googleanalytics) && !empty($params_googleanalyticsid)) :
$gaId = htmlspecialchars($params_googleanalyticsid, ENT_QUOTES, 'UTF-8'); ?>
<!-- Google Analytics (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=<?php echo $gaId; ?>"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('consent', 'default', {
'ad_storage': 'denied',
'analytics_storage': 'granted',
'ad_user_data': 'denied',
'ad_personalization': 'denied'
});
(function(id){
if (/^G-/.test(id)) {
gtag('config', id, { 'anonymize_ip': true });
} else if (/^UA-/.test(id)) {
gtag('config', id, { 'anonymize_ip': true });
console.warn('Using a UA- ID. Universal Analytics is sunset; consider migrating to GA4.');
} else {
console.warn('Unrecognized Google Analytics ID format:', id);
}
})('<?php echo $gaId; ?>');
</script>
<!-- End Google Analytics -->
<?php endif; ?>
<header id="top" class="header container-header full-width<?php echo $stickyHeader ? ' ' . $stickyHeader : ''; ?>" role="banner">
<?php if ($this->countModules('topbar')) : ?>
<div class="container-topbar">
<jdoc:include type="modules" name="topbar" style="none" />
</div>
<?php endif; ?>
<div class="header-top">
<?php if ($this->countModules('below-topbar')) : ?>
<div class="grid container-below-topbar">
<jdoc:include type="modules" name="below-topbar" style="none" />
</div>
<?php endif; ?>
<?php if ($this->params->get('brand', 1)) : ?>
<div class="grid-child header-brand-wrap">
<div class="navbar-brand">
<a class="brand-logo" href="<?php echo $this->baseurl; ?>/">
<?php echo $brandHtml; ?>
</a>
<?php if ($this->params->get('siteDescription')) : ?>
<div class="site-description">
<?php echo htmlspecialchars($this->params->get('siteDescription'), ENT_QUOTES, 'UTF-8'); ?>
</div>
<?php endif; ?>
</div>
<?php if ($this->countModules('brand-aside', true)) : ?>
<div class="container-brand-aside">
<jdoc:include type="modules" name="brand-aside" style="card" />
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if ($this->countModules('below-logo')) : ?>
<div class="grid container-below-logo">
<jdoc:include type="modules" name="below-logo" style="none" />
</div>
<?php endif; ?>
</div>
<!-- Drawer Toggle Buttons -->
<?php if ($this->countModules('drawer-left')) : ?>
<button class="drawer-toggle-left btn btn-outline-secondary me-2"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#drawer-left"
aria-controls="drawer-left">
<span class="<?php echo $params_leftIcon; ?>"></span>
</button>
<?php endif; ?>
<?php if ($this->countModules('drawer-right')) : ?>
<button class="drawer-toggle-right btn btn-outline-secondary"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#drawer-right"
aria-controls="drawer-right">
<span class="<?php echo $params_rightIcon; ?>"></span>
</button>
<?php endif; ?>
<?php if ($this->countModules('menu', true) || $this->countModules('search', true)) : ?>
<div class="grid-child container-nav">
<?php if ($this->countModules('menu', true)) : ?>
<nav role="navigation" aria-label="Primary">
<jdoc:include type="modules" name="menu" style="none" />
</nav>
<?php endif; ?>
<?php if ($this->countModules('search', true)) : ?>
<button class="search-toggler d-lg-none" type="button" data-bs-toggle="collapse" data-bs-target="#headerSearchCollapse" aria-controls="headerSearchCollapse" aria-expanded="false" aria-label="<?php echo Text::_('JSEARCH_FILTER_SUBMIT'); ?>">
<span class="fa-solid fa-magnifying-glass" aria-hidden="true"></span>
</button>
<div class="container-search" id="headerSearchCollapse">
<jdoc:include type="modules" name="search" style="none" />
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</header>
<div class="site-grid">
<?php if ($this->countModules('banner', true)) : ?>
<div class="container-banner full-width">
<jdoc:include type="modules" name="banner" style="none" />
</div>
<?php endif; ?>
<?php if ($this->countModules('top-a', true)) : ?>
<div class="grid-child container-top-a">
<jdoc:include type="modules" name="top-a" style="card" />
</div>
<?php endif; ?>
<?php if ($this->countModules('top-b', true)) : ?>
<div class="grid-child container-top-b">
<jdoc:include type="modules" name="top-b" style="card" />
</div>
<?php endif; ?>
<?php if ($this->countModules('sidebar-left', true)) : ?>
<div class="grid-child container-sidebar-left">
<jdoc:include type="modules" name="sidebar-left" style="card" />
</div>
<?php endif; ?>
<div class="grid-child container-component">
<jdoc:include type="modules" name="breadcrumbs" style="none" />
<?php if ($this->countModules('main-top', true)) : ?>
<div class="container-main-top">
<jdoc:include type="modules" name="main-top" style="card" />
</div>
<?php endif; ?>
<jdoc:include type="message" />
<main id="maincontent" role="main">
<jdoc:include type="component" />
</main>
<?php if ($this->countModules('main-bottom', true)) : ?>
<div class="container-main-bottom">
<jdoc:include type="modules" name="main-bottom" style="card" />
</div>
<?php endif; ?>
</div>
<?php if ($this->countModules('sidebar-right', true)) : ?>
<div class="grid-child container-sidebar-right">
<jdoc:include type="modules" name="sidebar-right" style="card" />
</div>
<?php endif; ?>
<?php if ($this->countModules('bottom-a', true)) : ?>
<div class="grid-child container-bottom-a">
<jdoc:include type="modules" name="bottom-a" style="card" />
</div>
<?php endif; ?>
<?php if ($this->countModules('bottom-b', true)) : ?>
<div class="grid-child container-bottom-b">
<jdoc:include type="modules" name="bottom-b" style="card" />
</div>
<?php endif; ?>
</div>
<footer class="container-footer footer full-width">
<?php if ($this->countModules('footer-menu', true)) : ?>
<div class="grid-child footer-menu">
<jdoc:include type="modules" name="footer-menu" />
</div>
<?php endif; ?>
<?php if ($this->countModules('footer', true)) : ?>
<div class="grid-child">
<jdoc:include type="modules" name="footer" style="none" />
</div>
<?php endif; ?>
</footer>
<?php if ($this->params->get('backTop') == 1) : ?>
<a href="#top" id="back-top" class="back-to-top-link" aria-label="<?php echo Text::_('TPL_MOKOONYX_BACKTOTOP'); ?>">
<span class="fa-solid fa-arrow-up" aria-hidden="true"></span>
</a>
<?php endif; ?>
<?php if ($this->countModules('drawer-left', true)) : ?>
<!-- Left Offcanvas Drawer -->
<aside class="offcanvas offcanvas-start" tabindex="-1" id="drawer-left">
<div class="offcanvas-header justify-content-end">
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="<?php echo Text::_('JLIB_HTML_BEHAVIOR_CLOSE'); ?>"><span class="fa fa-close"></span></button>
</div>
<div class="offcanvas-body">
<jdoc:include type="modules" name="drawer-left" style="none" />
</div>
</aside>
<?php endif; ?>
<?php if ($this->countModules('drawer-right', true)) : ?>
<!-- Right Offcanvas Drawer -->
<aside class="offcanvas offcanvas-end" tabindex="-1" id="drawer-right">
<div class="offcanvas-header justify-content-start">
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="<?php echo Text::_('JLIB_HTML_BEHAVIOR_CLOSE'); ?>"><span class="fa fa-close"></span></button>
</div>
<div class="offcanvas-body">
<jdoc:include type="modules" name="drawer-right" style="none" />
</div>
</aside>
<?php endif; ?>
<jdoc:include type="modules" name="debug" style="none" />
</body>
</html>

248
src/joomla.asset.json Normal file
View File

@@ -0,0 +1,248 @@
{
"$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
"name": "mokoonyx",
"description": "MokoOnyx template assets",
"license": "GPL-3.0-or-later",
"x-header": {
"copyright_year": 2026,
"author": "Jonathan Miller",
"owner": "Moko Consulting",
"contact": "hello@mokoconsulting.tech",
"project": "MokoOnyx Template",
"spdx_license": "GPL-3.0-or-later",
"notice": "This file is part of a Moko Consulting project.",
"disclaimer": "This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License; 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/.",
"repo": "https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx",
"file_information": {
"defgroup": "Joomla.Template.Site",
"ingroup": "MokoOnyx.Template.Assets",
"path": "./media/templates/site/mokoonyx/joomla.asset.json",
"version": "01.00.00",
"brief": "Joomla asset registry for MokoOnyx"
}
},
"assets": [
{
"name": "template.base",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/template.css",
"attributes": {"media": "all"}
},
{
"name": "template.base.min",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/template.min.css",
"attributes": {"media": "all"}
},
{
"name": "template.offline",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/offline.css",
"attributes": {"media": "all"}
},
{
"name": "template.offline.min",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/offline.min.css",
"attributes": {"media": "all"}
},
{
"name": "template.user",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/user.css",
"attributes": {"media": "all"}
},
{
"name": "template.user.min",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/user.min.css",
"attributes": {"media": "all"}
},
{
"name": "user.js",
"type": "script",
"uri": "media/templates/site/mokoonyx/js/user.js",
"attributes": {"defer": true}
},
{
"name": "user.js.min",
"type": "script",
"uri": "media/templates/site/mokoonyx/js/user.min.js",
"attributes": {"defer": true}
},
{
"name": "template.font.osaka",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/fonts/osaka.css",
"attributes": {"media": "all"}
},
{
"name": "template.editor",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/editor.css",
"attributes": {"media": "all"}
},
{
"name": "template.editor.min",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/editor.min.css",
"attributes": {"media": "all"}
},
{
"name": "template.light.standard",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/theme/light.standard.css",
"attributes": {"media": "all"}
},
{
"name": "template.light.standard.min",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/theme/light.standard.min.css",
"attributes": {"media": "all"}
},
{
"name": "template.light.custom",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/theme/light.custom.css",
"attributes": {"media": "all"}
},
{
"name": "template.light.custom.min",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/theme/light.custom.min.css",
"attributes": {"media": "all"}
},
{
"name": "template.dark.standard",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/theme/dark.standard.css",
"attributes": {"media": "all"}
},
{
"name": "template.dark.standard.min",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/theme/dark.standard.min.css",
"attributes": {"media": "all"}
},
{
"name": "template.dark.custom",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/theme/dark.custom.css",
"attributes": {"media": "all"}
},
{
"name": "template.dark.custom.min",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/theme/dark.custom.min.css",
"attributes": {"media": "all"}
},
{
"name": "template.a11y-high-contrast",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/a11y-high-contrast.css",
"attributes": {"media": "all"}
},
{
"name": "template.js",
"type": "script",
"uri": "media/templates/site/mokoonyx/js/template.js",
"attributes": {"defer": true}
},
{
"name": "template.js.min",
"type": "script",
"uri": "media/templates/site/mokoonyx/js/template.min.js",
"attributes": {"defer": true}
},
{
"name": "gtm.js",
"type": "script",
"uri": "media/templates/site/mokoonyx/js/gtm.js",
"attributes": {"defer": true}
},
{
"name": "gtm.min.js",
"type": "script",
"uri": "media/templates/site/mokoonyx/js/gtm.min.js",
"attributes": {"defer": true}
},
{
"name": "vendor.fa7free.all",
"type": "style",
"uri": "media/templates/site/mokoonyx/vendor/fa7free/css/all.css"
},
{
"name": "vendor.fa7free.all.min",
"type": "style",
"uri": "media/templates/site/mokoonyx/vendor/fa7free/css/all.min.css",
"attributes": {"media": "all"}
},
{
"name": "vendor.fa7free.brands",
"type": "style",
"uri": "media/templates/site/mokoonyx/vendor/fa7free/css/brands.css",
"attributes": {"media": "all"}
},
{
"name": "vendor.fa7free.brands.min",
"type": "style",
"uri": "media/templates/site/mokoonyx/vendor/fa7free/css/brands.min.css",
"attributes": {"media": "all"}
},
{
"name": "vendor.fa7free.fontawesome",
"type": "style",
"uri": "media/templates/site/mokoonyx/vendor/fa7free/css/fontawesome.css",
"attributes": {"media": "all"}
},
{
"name": "vendor.fa7free.fontawesome.min",
"type": "style",
"uri": "media/templates/site/mokoonyx/vendor/fa7free/css/fontawesome.min.css",
"attributes": {"media": "all"}
},
{
"name": "vendor.fa7free.regular",
"type": "style",
"uri": "media/templates/site/mokoonyx/vendor/fa7free/css/regular.css",
"attributes": {"media": "all"}
},
{
"name": "vendor.fa7free.regular.min",
"type": "style",
"uri": "media/templates/site/mokoonyx/vendor/fa7free/css/regular.min.css",
"attributes": {"media": "all"}
},
{
"name": "vendor.fa7free.solid",
"type": "style",
"uri": "media/templates/site/mokoonyx/vendor/fa7free/css/solid.css",
"attributes": {"media": "all"}
},
{
"name": "vendor.fa7free.solid.min",
"type": "style",
"uri": "media/templates/site/mokoonyx/vendor/fa7free/css/solid.min.css",
"attributes": {"media": "all"}
},
{
"name": "vendor.vm",
"type": "style",
"uri": "media/templates/site/mokoonyx/css/vendor/vm.css",
"attributes": {"media": "all"}
},
{
"name": "vendor.bootstrap-toc",
"type": "style",
"uri": "media/templates/site/mokoonyx/vendor/bootstrap-toc/bootstrap-toc.css",
"attributes": {"media": "all"}
},
{
"name": "vendor.bootstrap-toc.js",
"type": "script",
"uri": "media/templates/site/mokoonyx/vendor/bootstrap-toc/bootstrap-toc.js",
"dependencies": ["jquery"],
"attributes": {"defer": true}
}
]
}

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,273 @@
; 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
; ===== System / layout =====
TPL_MOKOONYX_DEVELOPMENTMODE_LABEL="Development Mode"
TPL_MOKOONYX_DEVELOPMENTMODE_DESC="When enabled, unminified CSS and JavaScript files are loaded for easier debugging. When disabled, minified assets are served for faster page loads. Google Tag Manager and Google Analytics may also be disabled in development mode."
TPL_MOKOONYX_FLUID_LABEL="Layout"
TPL_MOKOONYX_STATIC="Static"
TPL_MOKOONYX_FLUID="Fluid"
; ===== Custom Code tab =====
TPL_MOKOONYX_CUSTOM_CODE_FIELDSET="Custom Code"
TPL_MOKOONYX_CUSTOM_HEAD_START_LABEL="Custom Head: Start"
TPL_MOKOONYX_CUSTOM_HEAD_START_DESC="This content will be inserted at the beginning of the &lt;head&gt; tag"
TPL_MOKOONYX_CUSTOM_HEAD_END_LABEL="Custom Head: End"
TPL_MOKOONYX_CUSTOM_HEAD_END_DESC="This content will be inserted at the end of the &lt;head&gt; tag"
TPL_MOKOONYX_OFFLINEEMBED_LABEL="Offline Page Embed Code"
TPL_MOKOONYX_OFFLINEEMBED_DESC="In addition to the 'Offline message' defined in 'Global Configuration', this will be displayed on the offline page.<i>Use for Mailchimp code and Social Icons</i>"
; ===== Drawers =====
TPL_MOKOONYX_DRAWERS_FIELDSET_LABEL="Drawers"
TPL_MOKOONYX_DRAWER_LEFT_ICON_LABEL="Drawer Left Icon CSS"
TPL_MOKOONYX_DRAWER_LEFT_ICON_DESC="Enter the Font-Awesome class for the left drawer toggle (e.g. 'fas fa-chevron-left')."
TPL_MOKOONYX_DRAWER_RIGHT_ICON_LABEL="Drawer Right Icon CSS"
TPL_MOKOONYX_DRAWER_RIGHT_ICON_DESC="Enter the Font-Awesome class for the right drawer toggle (e.g. 'fas fa-chevron-right')."
; ===== Favicon =====
TPL_MOKOONYX_FAVICON_FIELDSET_LABEL="Favicon"
TPL_MOKOONYX_FAVICON_NOTE="<p>Upload a square <strong>PNG image</strong> (recommended 512×512 or larger). The template will automatically generate all required favicon sizes including ICO, Apple Touch Icon (180×180), and Android icons (192×192, 512×512). Generated files are cached in <code>images/favicons/</code>.</p>"
TPL_MOKOONYX_FAVICON_SOURCE_LABEL="Favicon Source Image"
TPL_MOKOONYX_FAVICON_SOURCE_DESC="Select a square PNG image to use as the site favicon. Recommended size: 512×512 pixels or larger."
; ===== Google =====
TPL_MOKOONYX_GOOGLE_FIELDSET_LABEL="Google"
TPL_MOKOONYX_GOOGLE_NOTE_TEXT="<h3>PLEASE NOTE:</h3>If fields are left blank, relative Google features will not be used"
TPL_MOKOONYX_GOOGLETAGMANAGER_LABEL="Use Google Tag Manager?"
TPL_MOKOONYX_GOOGLETAGMANAGER_DESC="Do you want to use Google Tag Manager?<br>More information on Google Tag Manager can be found <a target='_blank' href='https://support.google.com/tagmanager/answer/14842164'>here.</a>"
TPL_MOKOONYX_GOOGLETAGMANAGERID_LABEL="Google Tag Manager ID"
TPL_MOKOONYX_GOOGLETAGMANAGERID_DESC="Begins with 'GTM-'"
TPL_MOKOONYX_GOOGLEANALYTICS_LABEL="Use Google Analytics?"
TPL_MOKOONYX_GOOGLEANALYTICS_DESC="Do you want to use Google Analytics?<br>More information on Google Analytics can be found <a target='_blank' href='https://developers.google.com/analytics'>here.</a>"
TPL_MOKOONYX_GOOGLEANALYTICSID_LABEL="Google Analytics ID"
TPL_MOKOONYX_GOOGLEANALYTICSID_DESC="Begins with 'G-'"
TPL_MOKOONYX_GOOGLESITEKEY_LABEL="Google Search Console Verification"
TPL_MOKOONYX_GOOGLESITEKEY_DESC="Paste the content value from the &lt;meta name=&quot;google-site-verification&quot;&gt; tag. Find this in Google Search Console under Ownership Verification &rarr; HTML tag method."
; ===== Branding & icons (Theme tab) =====
TPL_MOKOONYX_BRAND_LABEL="Brand"
TPL_MOKOONYX_LOGO_LABEL="Logo"
TPL_MOKOONYX_TITLE="Title (alternative to logo)"
TPL_MOKOONYX_TAGLINE_LABEL="Tagline"
TPL_MOKOONYX_TAGLINE_DESC="Optional text to show as a subheading"
TPL_MOKOONYX_FA7KITCODE_LABEL="Font Awesome 7 Kit Unique Code"
TPL_MOKOONYX_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 &lt;head&gt; 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) =====
TPL_MOKOONYX_FONT_LABEL="Fonts Scheme"
TPL_MOKOONYX_FONT_LABEL_DESC="Select a font scheme for your site. Local fonts are loaded from the template folder, while web fonts are loaded from external sources (Google Fonts). The default is Roboto (local). See the note below for important privacy and performance considerations."
TPL_MOKOONYX_FONT_GROUP_LOCAL="Fonts from Folder"
TPL_MOKOONYX_FONT_GROUP_WEB="Fonts from Web"
TPL_MOKOONYX_FONT_NOTE_TEXT="Loading fonts from external sources might be against privacy regulations in some countries.<br>Loading fonts from a local folder might have a performance impact on your site."
; ===== Header & navigation (Theme tab) =====
TPL_MOKOONYX_STICKY_LABEL="Sticky Header"
TPL_MOKOONYX_BACKTOTOP="Back to Top"
TPL_MOKOONYX_TOC_TITLE="Table of Contents"
TPL_MOKOONYX_BACKTOTOP_LABEL="Back-to-top Link"
TPL_MOKOONYX_TOC="Table of Contents"
; ===== Color palette choices (shared) =====
TPL_MOKOONYX_COLOR_NAME_STANDARD="Standard"
TPL_MOKOONYX_COLOR_NAME_CUSTOM="Custom"
; New labels for Theme tab dropdowns
TPL_MOKOONYX_COLOR_LIGHT_NAME_LABEL="Light colour palette"
TPL_MOKOONYX_COLOR_LIGHT_NAME_DESC="Select a colour palette for light mode. <strong>Standard</strong> uses the default blue theme with comprehensive styling for all components. <strong>Custom</strong> loads <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code> — copy the starter file from <code>templates/mokoonyx/templates/light.custom.css</code> and customise the CSS variables to match your brand. This file is stored in the media folder and will not be overwritten by template updates."
TPL_MOKOONYX_COLOR_DARK_NAME_LABEL="Dark colour palette"
TPL_MOKOONYX_COLOR_DARK_NAME_DESC="Select a colour palette for dark mode. <strong>Standard</strong> uses the default blue theme optimised for dark backgrounds with proper contrast. <strong>Custom</strong> loads <code>media/templates/site/mokoonyx/css/theme/dark.custom.css</code> — copy the starter file from <code>templates/mokoonyx/templates/dark.custom.css</code> and customise the CSS variables to match your brand. This file is stored in the media folder and will not be overwritten by template updates."
; ===== Theme tab (core feature strings) =====
TPL_MOKO_THEME_FIELDSET="Theme"
TPL_MOKO_THEME_SECTION_GENERAL="General"
TPL_MOKO_THEME_SECTION_VARS="Variables & Palettes"
TPL_MOKO_THEME_SECTION_TYPO="Typography"
TPL_MOKO_THEME_SECTION_BRAND="Branding & Icons"
TPL_MOKO_THEME_SECTION_HEADER="Header & Navigation"
TPL_MOKO_THEME_SECTION_TOGGLE="Theme Toggle UI"
TPL_MOKO_THEME_ENABLED="Enable theme feature"
TPL_MOKO_THEME_ENABLED_DESC="Turn the entire light/dark feature on or off."
TPL_MOKO_THEME_CONTROL_TYPE="Theme Control Type"
TPL_MOKO_THEME_CONTROL_TYPE_DESC="Choose a visible toggle (Switch or Radios), or no control to follow System only."
TPL_MOKO_THEME_DEFAULT_CHOICE="Default Choice"
TPL_MOKO_THEME_DEFAULT_CHOICE_DESC="Initial theme when no user preference is stored."
TPL_MOKO_THEME_AUTO_DARK="Auto Dark Mode"
TPL_MOKO_THEME_AUTO_DARK_DESC="Force the site to switch to dark mode automatically. When enabled, the template will override the default and use dark unless the user explicitly selects otherwise."
TPL_MOKO_THEME_META_COLOR_SCHEME="Add &lt;meta name=&quot;color-scheme&quot;&gt;"
TPL_MOKO_THEME_META_COLOR_SCHEME_DESC="Advertise light/dark support for UA controls."
TPL_MOKO_THEME_META_THEME_COLOR="Add &lt;meta name=&quot;theme-color&quot;&gt;"
TPL_MOKO_THEME_META_THEME_COLOR_DESC="Update mobile address bar color."
TPL_MOKO_THEME_BASE_CSS="Base template CSS"
TPL_MOKO_THEME_BASE_CSS_DESC="Main stylesheet that consumes variables."
TPL_MOKO_THEME_BRIDGE="Sync data-bs-theme with data-aria-theme"
TPL_MOKO_THEME_BRIDGE_DESC="Keep both attributes in lockstep so Bootstrap and custom CSS stay aligned."
TPL_MOKO_THEME_FAB_ENABLED="Show floating theme switch"
TPL_MOKO_THEME_FAB_ENABLED_DESC="Display a persistent, accessible theme toggle."
TPL_MOKO_THEME_FAB_POS="Floating switch position"
TPL_MOKO_THEME_FAB_POS_DESC="Screen corner for the toggle."
; ===== Accessibility toolbar =====
TPL_MOKO_A11Y_TOOLBAR_ENABLED="Accessibility toolbar"
TPL_MOKO_A11Y_TOOLBAR_ENABLED_DESC="Show a floating accessibility toolbar with text resize and colour inversion controls."
TPL_MOKO_A11Y_TEXT_RESIZE="Text resize"
TPL_MOKO_A11Y_TEXT_RESIZE_DESC="Allow visitors to increase or decrease text size."
TPL_MOKO_A11Y_COLOR_INVERSION="Colour inversion"
TPL_MOKO_A11Y_COLOR_INVERSION_DESC="Allow visitors to invert page colours for improved readability."
TPL_MOKO_A11Y_TOOLBAR_POS="Toolbar position"
TPL_MOKO_A11Y_TOOLBAR_POS_DESC="Screen corner for the accessibility toolbar."
TPL_MOKO_A11Y_BTN_LABEL="Accessibility options"
TPL_MOKO_A11Y_TEXT_DECREASE="Decrease text size"
TPL_MOKO_A11Y_TEXT_RESET="Reset text size"
TPL_MOKO_A11Y_TEXT_INCREASE="Increase text size"
TPL_MOKO_A11Y_INVERT_COLORS="Invert colours"
TPL_MOKO_A11Y_HIGH_CONTRAST="High contrast"
TPL_MOKO_A11Y_HIGH_CONTRAST_DESC="Allow visitors to boost page contrast for improved readability."
TPL_MOKO_A11Y_HIGHLIGHT_LINKS="Highlight links"
TPL_MOKO_A11Y_HIGHLIGHT_LINKS_DESC="Allow visitors to outline all links so they stand out from surrounding text."
TPL_MOKO_A11Y_READABLE_FONT="Readable font"
TPL_MOKO_A11Y_READABLE_FONT_DESC="Allow visitors to switch to a clean system font optimised for readability."
TPL_MOKO_A11Y_PAUSE_ANIMATIONS="Pause animations"
TPL_MOKO_A11Y_PAUSE_ANIMATIONS_DESC="Allow visitors to stop all CSS animations and transitions."
; ===== CSS Variables tab =====
TPL_MOKOONYX_CSS_VARS_FIELDSET_LABEL="CSS Variables"
TPL_MOKOONYX_CSS_VARS_INTRO="<p>All colours, spacing and layout values are driven by CSS custom properties. To override any variable without editing the template, add your overrides to <code>media/templates/site/mokoonyx/css/user.css</code>, or create a custom palette file (see the Theme tab). Variables are scoped to <code>:root[data-bs-theme=&quot;light&quot;]</code> or <code>:root[data-bs-theme=&quot;dark&quot;]</code> so light and dark values are independent.</p>"
TPL_MOKOONYX_CSS_VARS_BRAND_LABEL="Brand &amp; Theme Colours"
TPL_MOKOONYX_CSS_VARS_BRAND_DESC="<code>--color-primary</code> — Primary brand colour (default: <code>#112855</code>)<br><code>--accent-color-primary</code> — Primary accent (default: <code>#3f8ff0</code>)<br><code>--accent-color-secondary</code> — Secondary accent"
TPL_MOKOONYX_CSS_VARS_LINKS_LABEL="Links &amp; Link Utilities"
TPL_MOKOONYX_CSS_VARS_LINKS_DESC="<strong>Core link tokens</strong><br><code>--color-link</code> — Base link colour<br><code>--color-hover</code> — Base hover colour<br><code>--link-color</code> / <code>--link-hover-color</code> — Bootstrap link colours<br><code>--link-decoration</code> — Default text-decoration<br><code>--link-active-color</code> — Active state<br><br><strong>Semantic link utilities</strong> (replace <code>{colour}</code> with <code>primary</code>, <code>secondary</code>, <code>success</code>, <code>info</code>, <code>warning</code>, <code>danger</code>, <code>light</code>, <code>dark</code>)<br><code>--link-{colour}-color</code> — Colour for <code>.link-{colour}</code><br><code>--link-{colour}-hover-color</code> — Hover colour"
TPL_MOKOONYX_CSS_VARS_TYPO_LABEL="Typography &amp; Body"
TPL_MOKOONYX_CSS_VARS_TYPO_DESC="<code>--body-color</code> — Default text colour (default: <code>#22262a</code>)<br><code>--body-bg</code> — Page background (default: <code>#fff</code>)<br><code>--body-font-family</code> — Font stack<br><code>--body-font-size</code> — Base size (default: <code>1rem</code>)<br><code>--body-font-weight</code> — Base weight (default: <code>400</code>)<br><code>--body-line-height</code> — Line height (default: <code>1.5</code>)<br><code>--heading-color</code> — Heading colour (default: <code>inherit</code>)<br><code>--muted-color</code> — Muted/secondary text (default: <code>#6d757e</code>)<br><code>--code-color</code> — Inline code colour<br><code>--emphasis-color</code> — Strong emphasis colour<br><code>--secondary-color</code> / <code>--tertiary-color</code> — Stepped text opacities<br><code>--highlight-color</code> / <code>--highlight-bg</code> — <code>&lt;mark&gt;</code> colours<br><code>--font-sans-serif</code> / <code>--font-monospace</code> — Font stacks"
TPL_MOKOONYX_CSS_VARS_NAV_LABEL="Navigation, Navbar &amp; Offcanvas"
TPL_MOKOONYX_CSS_VARS_NAV_DESC="<strong>Theme nav</strong><br><code>--nav-bg-color</code> — Navbar background<br><code>--nav-text-color</code> — Navbar text<br><code>--mainmenu-nav-link-color</code> — Active nav link<br><br><strong>Navbar tokens</strong><br><code>--navbar-padding-x</code> / <code>--navbar-padding-y</code> — Navbar padding<br><code>--navbar-brand-font-size</code> — Brand font size<br><code>--navbar-toggler-border-color</code> — Mobile toggler border<br><code>--nav-link-padding-x</code> / <code>--nav-link-padding-y</code> — Link padding<br><code>--nav-link-font-weight</code> — Link weight<br><code>--nav-link-disabled-color</code> — Disabled link colour<br><br><strong>Offcanvas</strong><br><code>--offcanvas-color</code> — Offcanvas text colour<br><code>--offcanvas-padding-x</code> / <code>--offcanvas-padding-y</code> — Offcanvas padding"
TPL_MOKOONYX_CSS_VARS_LAYOUT_LABEL="Layout &amp; Spacing"
TPL_MOKOONYX_CSS_VARS_LAYOUT_DESC="<code>--padding-x</code> / <code>--padding-y</code> — Default component padding<br><code>--nav-toggle-size</code> — Mobile nav toggle button size (default: <code>3rem</code>)<br><code>--secondary-bg</code> — Secondary surface background (default: <code>#eaedf0</code>)<br><code>--tertiary-bg</code> — Tertiary surface background (default: <code>#f9fafb</code>)<br><code>--hr-color</code> — Horizontal rule colour<br><code>--border-color-soft</code> — Soft border variant<br><code>--kbd-bg</code> / <code>--kbd-ink</code> — Keyboard element colours<br><code>--toc-bg</code> / <code>--toc-ink</code> — Table of contents colours<br><code>--selection-bg</code> / <code>--selection-ink</code> — Text selection colours<br><code>--gradient</code> — Bootstrap gradient overlay value<br><code>--bg-opacity</code> — Background opacity utility base"
TPL_MOKOONYX_CSS_VARS_BP_LABEL="Breakpoints"
TPL_MOKOONYX_CSS_VARS_BP_DESC="Read-only reference values matching Bootstrap breakpoints.<br><code>--bp-xs</code> — <code>0</code><br><code>--bp-sm</code> — <code>576px</code><br><code>--bp-md</code> — <code>768px</code><br><code>--bp-lg</code> — <code>992px</code><br><code>--bp-xl</code> — <code>1200px</code>"
TPL_MOKOONYX_CSS_VARS_BS_LABEL="Bootstrap Semantic Palette"
TPL_MOKOONYX_CSS_VARS_BS_DESC="These map to Bootstrap components (buttons, alerts, badges). Override to retheme all components at once.<br><code>--primary</code> — <code>#010156</code><br><code>--secondary</code> — <code>#6d757e</code><br><code>--success</code> — <code>#448344</code><br><code>--info</code> — <code>#30638d</code><br><code>--warning</code> — <code>#ad6200</code><br><code>--danger</code> — <code>#a51f18</code><br><code>--light</code> — <code>#f9fafb</code><br><code>--dark</code> — <code>#353b41</code><br>Each colour also has an <code>--{color}-rgb</code> variant for use in <code>rgba()</code> expressions."
TPL_MOKOONYX_CSS_VARS_BS_STATES_LABEL="Bootstrap State Colours"
TPL_MOKOONYX_CSS_VARS_BS_STATES_DESC="Contextual state tokens used by alerts, badges and list groups. Replace <code>{color}</code> with <code>primary</code>, <code>secondary</code>, <code>success</code>, <code>info</code>, <code>warning</code>, <code>danger</code>, <code>light</code>, or <code>dark</code>.<br><code>--{color}-text-emphasis</code> — High-contrast text on subtle backgrounds<br><code>--{color}-bg-subtle</code> — Tinted component background<br><code>--{color}-border-subtle</code> — Tinted component border"
TPL_MOKOONYX_CSS_VARS_ALERT_LIST_LABEL="Alert &amp; List Group Colours"
TPL_MOKOONYX_CSS_VARS_ALERT_LIST_DESC="<strong>Alert link colours</strong> — override to adjust link contrast inside <code>.alert-{color}</code> components.<br><code>--alert-{color}-link-color</code> — e.g. <code>--alert-primary-link-color</code><br><br><strong>List group item colours</strong> — contextual surfaces for <code>.list-group-item-{color}</code>.<br><code>--list-group-item-{color}-color</code> — Text colour<br><code>--list-group-item-{color}-bg</code> — Background<br><code>--list-group-item-{color}-active-bg</code> — Active state background"
TPL_MOKOONYX_CSS_VARS_COLORS_LABEL="Standard Colours, Grays &amp; Opacity"
TPL_MOKOONYX_CSS_VARS_COLORS_DESC="<strong>Named colours</strong><br><code>--blue</code>, <code>--indigo</code>, <code>--purple</code>, <code>--pink</code>, <code>--red</code>, <code>--orange</code>, <code>--yellow</code>, <code>--green</code>, <code>--teal</code>, <code>--cyan</code>, <code>--black</code>, <code>--white</code><br><br><strong>Gray scale</strong><br><code>--gray-100</code> through <code>--gray-900</code> plus <code>--white-rgb</code> and <code>--black-rgb</code><br><br><strong>Opacity utilities</strong><br><code>--opacity-0</code>, <code>--opacity-5</code>, <code>--opacity-10</code>, <code>--opacity-15</code>, <code>--opacity-20</code>, <code>--opacity-25</code>, <code>--opacity-30</code>, <code>--opacity-50</code>, <code>--opacity-75</code>, <code>--opacity-100</code>"
TPL_MOKOONYX_CSS_VARS_HERO_LABEL="Hero / Banner Overlay"
TPL_MOKOONYX_CSS_VARS_HERO_DESC="Applied to the <code>.custom-hero</code> / <code>.banner-overlay</code> layout. Set on <code>:root[data-bs-theme]</code> so light and dark values are independent.<br><code>--hero-height</code> — Banner height (default: <code>70vh</code>)<br><code>--hero-color</code> — Base text colour<br><code>--hero-bg-repeat</code> — Background repeat (default: <code>no-repeat</code>)<br><code>--hero-bg-attachment</code> — Background attachment (default: <code>fixed</code>)<br><code>--hero-bg-position</code> — Background position (default: <code>top center</code>)<br><code>--hero-bg-size</code> — Background size (default: <code>cover</code>)<br><code>--hero-border-bottom</code> — Bottom border (default: <code>solid var(--accent-color-secondary)</code>)<br><code>--hero-overlay-bg</code> — Overlay tint colour (light default: <code>hsla(0,0%,0%,0.1)</code> / dark default: <code>hsla(0,0%,0%,0.3)</code>)<br><code>--hero-overlay-padding</code> — Overlay inner padding (default: <code>1em</code>)<br><code>--hero-overlay-text-align</code> — Overlay text alignment (default: <code>center</code>)<br><code>--hero-overlay-text-color</code> — Overlay text colour"
TPL_MOKOONYX_CSS_VARS_HERO_VARIANTS_LABEL="Hero Variants (.hero#primary / .hero#secondary)"
TPL_MOKOONYX_CSS_VARS_HERO_VARIANTS_DESC="Two-variant hero system using <code>.hero#primary</code> and <code>.hero#secondary</code>. Each variant resolves its own CSS variable set per theme.<br><br><strong>Primary variant</strong> — homepage, main landing pages (sky blue tint, softer overlay)<br><code>--hero-primary-bg-color</code> — Fallback background colour<br><code>--hero-primary-overlay</code> — Gradient overlay tint<br><code>--hero-primary-color</code> — Text colour<br><br><strong>Secondary variant</strong> — inner pages, events, about (navy overlay, lighter text)<br><code>--hero-secondary-bg-color</code> — Fallback background colour<br><code>--hero-secondary-overlay</code> — Gradient overlay tint<br><code>--hero-secondary-color</code> — Text colour<br><br><strong>HTML usage:</strong><br><code>&lt;div class=&quot;hero&quot; id=&quot;primary&quot; style=&quot;background-image:url(...)&quot;&gt;</code>"
TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_LABEL="Block Colour System (top-a / top-b / bottom-a / bottom-b)"
TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_DESC="Automatic brand colour palette for modules in <code>top-a</code>, <code>top-b</code>, <code>bottom-a</code>, and <code>bottom-b</code> positions. Colours assigned by <code>:nth-child()</code> order — no classes needed.<br><br><strong>Slot palette</strong><br><code>--block-color-1</code> / <code>--block-text-1</code> — 1st module<br><code>--block-color-2</code> / <code>--block-text-2</code> — 2nd module<br><code>--block-color-3</code> / <code>--block-text-3</code> — 3rd module<br><code>--block-color-4</code> / <code>--block-text-4</code> — 4th module<br><br><strong>Named overrides</strong> (add an ID to the module HTML to bypass slot colour)<br><code>--block-highlight-bg</code> / <code>--block-highlight-text</code> — for <code>#block-highlight</code><br><code>--block-cta-bg</code> / <code>--block-cta-text</code> — for <code>#block-cta</code><br><code>--block-alert-bg</code> / <code>--block-alert-text</code> — for <code>#block-alert</code><br><br><strong>Priority:</strong> Named ID &gt; Slot colour. No <code>!important</code> needed — specificity handles it."
TPL_MOKOONYX_CSS_VARS_HEADER_LABEL="Header Background"
TPL_MOKOONYX_CSS_VARS_HEADER_DESC="Controls the background of the topbar/header area.<br><code>--header-background-image</code> — CSS <code>background-image</code> value (default: built-in SVG pattern)<br><code>--header-background-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--header-background-repeat</code> — e.g. <code>repeat</code>, <code>no-repeat</code><br><code>--header-background-size</code> — e.g. <code>auto</code>, <code>cover</code>, <code>contain</code>"
TPL_MOKOONYX_CSS_VARS_CONTAINERS_LABEL="Container Backgrounds"
TPL_MOKOONYX_CSS_VARS_CONTAINERS_DESC="Each layout container has its own background variables. Replace <code>{pos}</code> with: <code>below-topbar</code>, <code>top-a</code>, <code>top-b</code>, <code>sidebar</code>, <code>bottom-a</code>, or <code>bottom-b</code>.<br><br><code>--container-{pos}-bg-image</code> — Background image (default: <code>none</code>)<br><code>--container-{pos}-bg-color</code> — Background colour (default: <code>transparent</code>)<br><code>--container-{pos}-bg-position</code> — Background position<br><code>--container-{pos}-bg-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--container-{pos}-bg-repeat</code> — Repeat behaviour<br><code>--container-{pos}-bg-size</code> — e.g. <code>cover</code>, <code>auto</code><br><code>--container-{pos}-border</code> — Border shorthand<br><code>--container-{pos}-border-radius</code> — Border radius<br><br>Also: <code>--container-toc-bg</code> / <code>--container-toc-color</code> for the TOC sidebar."
TPL_MOKOONYX_CSS_VARS_BORDERS_LABEL="Borders"
TPL_MOKOONYX_CSS_VARS_BORDERS_DESC="<code>--border-width</code> — Default width (default: <code>1px</code>)<br><code>--border-style</code> — Default style (default: <code>solid</code>)<br><code>--border-color</code> — Default border colour (default: <code>#dfe3e7</code>)<br><code>--border-color-translucent</code> — Semi-transparent border<br><code>--border-radius</code> — Default radius (default: <code>.25rem</code>)<br><code>--border-radius-sm</code> — Small radius<br><code>--border-radius-lg</code> — Large radius<br><code>--border-radius-xl</code> — Extra large radius<br><code>--border-radius-xxl</code> — 2XL radius (default: <code>2rem</code>)<br><code>--border-radius-pill</code> — Pill radius (default: <code>50rem</code>)"
TPL_MOKOONYX_CSS_VARS_SHADOWS_LABEL="Shadows &amp; Shadow Tokens"
TPL_MOKOONYX_CSS_VARS_SHADOWS_DESC="<strong>Box shadows</strong><br><code>--box-shadow</code> — Standard shadow<br><code>--box-shadow-sm</code> — Subtle shadow<br><code>--box-shadow-lg</code> — Prominent shadow<br><code>--box-shadow-inset</code> — Inset shadow<br><br><strong>Shadow colour tokens</strong> — used as building blocks by component shadows<br><code>--shadow-color-light</code> — <code>rgba(black, 0.15)</code><br><code>--shadow-color-medium</code> — <code>rgba(black, 0.25)</code><br><code>--shadow-color-dark</code> — <code>rgba(black, 0.30)</code><br><code>--highlight-translucent</code> — <code>rgba(white, 0.15)</code>"
TPL_MOKOONYX_CSS_VARS_FORMS_LABEL="Focus &amp; Forms"
TPL_MOKOONYX_CSS_VARS_FORMS_DESC="<code>--focus-ring-width</code> — Keyboard focus ring width (default: <code>.25rem</code>)<br><code>--focus-ring-opacity</code> — Focus ring opacity<br><code>--focus-ring-color</code> — Focus ring colour<br><code>--input-color</code> — Input text colour<br><code>--input-bg</code> — Input background<br><code>--input-border-color</code> — Input border colour<br><code>--input-focus-border-color</code> — Focused border colour<br><code>--input-focus-box-shadow</code> — Focused input shadow<br><code>--input-placeholder-color</code> — Placeholder text colour<br><code>--input-disabled-bg</code> — Disabled input background<br><code>--input-disabled-border-color</code> — Disabled input border<br><code>--form-valid-color</code> / <code>--form-valid-border-color</code> — Valid state<br><code>--form-invalid-color</code> / <code>--form-invalid-border-color</code> — Invalid state"
TPL_MOKOONYX_CSS_VARS_BUTTONS_LABEL="Buttons"
TPL_MOKOONYX_CSS_VARS_BUTTONS_DESC="Applied on <code>:root</code> for global button defaults:<br><code>--btn-border-radius</code> — Button border radius<br><code>--btn-box-shadow</code> — Button box shadow<br><br>Applied on <code>.btn</code> for base button tokens (overridable per variant):<br><code>--btn-padding-x</code> / <code>--btn-padding-y</code> — Padding<br><code>--btn-font-size</code> / <code>--btn-font-weight</code> / <code>--btn-line-height</code> — Typography<br><code>--btn-color</code> / <code>--btn-bg</code> / <code>--btn-border-color</code> — Default state<br><code>--btn-hover-color</code> / <code>--btn-hover-bg</code> / <code>--btn-hover-border-color</code> — Hover state<br><code>--btn-active-color</code> / <code>--btn-active-bg</code> / <code>--btn-active-shadow</code> — Active state<br><code>--btn-disabled-opacity</code> — Disabled opacity<br><br>Each <code>.btn-{color}</code> and <code>.btn-outline-{color}</code> class inherits these tokens and sets its own values."
TPL_MOKOONYX_CSS_VARS_CARDS_LABEL="Cards"
TPL_MOKOONYX_CSS_VARS_CARDS_DESC="<code>--card-spacer-y</code> / <code>--card-spacer-x</code> — Body padding (default: <code>1rem</code>)<br><code>--card-title-spacer-y</code> — Title bottom margin (default: <code>0.5rem</code>)<br><code>--card-border-width</code> — Border width (default: <code>1px</code>)<br><code>--card-border-color</code> — Border colour<br><code>--card-border-radius</code> — Border radius<br><code>--card-box-shadow</code> — Card shadow (default: <code>none</code>)<br><code>--card-cap-padding-y</code> / <code>--card-cap-padding-x</code> — Header/footer padding<br><code>--card-cap-bg</code> — Header/footer background<br><code>--card-cap-color</code> — Header/footer text colour<br><code>--card-color</code> — Body text colour<br><code>--card-bg</code> — Card background"
TPL_MOKOONYX_CSS_VARS_ACCORDION_LABEL="Accordion"
TPL_MOKOONYX_CSS_VARS_ACCORDION_DESC="<strong>Colours</strong><br><code>--accordion-color</code> — Panel text colour<br><code>--accordion-bg</code> — Panel background<br><code>--accordion-border-color</code> — Border colour<br><code>--accordion-btn-color</code> — Button text colour<br><code>--accordion-btn-bg</code> — Button background<br><code>--accordion-btn-focus-border-color</code> — Button focus border colour<br><code>--accordion-btn-focus-box-shadow</code> — Button focus ring<br><code>--accordion-active-color</code> — Active item text colour<br><code>--accordion-active-bg</code> — Active item background<br><br><strong>Dimensions</strong><br><code>--accordion-border-width</code> — Border width<br><code>--accordion-border-radius</code> — Outer border radius<br><code>--accordion-inner-border-radius</code> — Inner border radius<br><code>--accordion-btn-padding-x</code> / <code>--accordion-btn-padding-y</code> — Button padding<br><code>--accordion-body-padding-x</code> / <code>--accordion-body-padding-y</code> — Body padding<br><br><strong>Icon &amp; Animation</strong><br><code>--accordion-btn-icon</code> — Collapse icon (collapsed state)<br><code>--accordion-btn-icon-width</code> — Icon size<br><code>--accordion-btn-icon-transform</code> — Icon rotation when expanded<br><code>--accordion-btn-icon-transition</code> — Icon rotation transition<br><code>--accordion-btn-active-icon</code> — Icon (expanded state)<br><code>--accordion-transition</code> — Panel open/close transition<br><br><strong>Stacking</strong><br><code>--accordion-zindex</code> — z-index"
TPL_MOKOONYX_CSS_VARS_ALERT_BASE_LABEL="Alert (Base)"
TPL_MOKOONYX_CSS_VARS_ALERT_BASE_DESC="<strong>Colours</strong><br><code>--alert-color</code> — Alert text colour<br><code>--alert-bg</code> — Alert background<br><code>--alert-border-color</code> — Border colour<br><code>--alert-border</code> — Full border shorthand<br><br><strong>Dimensions</strong><br><code>--alert-padding-x</code> / <code>--alert-padding-y</code> — Inner padding<br><code>--alert-margin-bottom</code> — Bottom margin<br><code>--alert-border-radius</code> — Border radius<br><br><em>Note: per-variant contextual tokens (colours, backgrounds, borders) are covered in the Bootstrap State Colours section above.</em>"
TPL_MOKOONYX_CSS_VARS_BADGE_LABEL="Badge"
TPL_MOKOONYX_CSS_VARS_BADGE_DESC="<strong>Colours</strong><br><code>--badge-color</code> — Badge text colour<br><br><strong>Typography</strong><br><code>--badge-font-size</code> — Font size<br><code>--badge-font-weight</code> — Font weight<br><br><strong>Dimensions</strong><br><code>--badge-padding-x</code> / <code>--badge-padding-y</code> — Padding<br><code>--badge-border-radius</code> — Border radius"
TPL_MOKOONYX_CSS_VARS_BACKDROP_LABEL="Backdrop"
TPL_MOKOONYX_CSS_VARS_BACKDROP_DESC="<strong>Colours</strong><br><code>--backdrop-bg</code> — Backdrop colour (default: <code>#000</code>)<br><code>--backdrop-opacity</code> — Backdrop opacity (default: <code>0.5</code>)<br><br><strong>Stacking</strong><br><code>--backdrop-zindex</code> — z-index (default: <code>1040</code>)"
TPL_MOKOONYX_CSS_VARS_BREADCRUMB_LABEL="Breadcrumb"
TPL_MOKOONYX_CSS_VARS_BREADCRUMB_DESC="<strong>Colours</strong><br><code>--breadcrumb-bg</code> — Background colour<br><code>--breadcrumb-divider-color</code> — Divider colour<br><code>--breadcrumb-item-active-color</code> — Active item colour<br><br><strong>Dimensions</strong><br><code>--breadcrumb-padding-x</code> / <code>--breadcrumb-padding-y</code> — Container padding<br><code>--breadcrumb-margin-bottom</code> — Bottom margin<br><code>--breadcrumb-item-padding-x</code> — Spacing between items<br><code>--breadcrumb-border-radius</code> — Container border radius"
TPL_MOKOONYX_CSS_VARS_DROPDOWN_MENU_LABEL="Dropdown Menu"
TPL_MOKOONYX_CSS_VARS_DROPDOWN_MENU_DESC="<strong>Colours</strong><br><code>--dropdown-color</code> — Default text colour<br><code>--dropdown-bg</code> — Menu background<br><code>--dropdown-border-color</code> — Border colour<br><code>--dropdown-divider-bg</code> — Divider colour<br><code>--dropdown-box-shadow</code> — Menu shadow<br><code>--dropdown-link-color</code> — Link text colour<br><code>--dropdown-link-active-color</code> — Active link text colour<br><code>--dropdown-link-active-bg</code> — Active link background<br><code>--dropdown-link-disabled-color</code> — Disabled link colour<br><code>--dropdown-header-color</code> — Header text colour<br><br><strong>Dimensions</strong><br><code>--dropdown-min-width</code> — Minimum width<br><code>--dropdown-padding-x</code> / <code>--dropdown-padding-y</code> — Menu padding<br><code>--dropdown-spacer</code> — Gap from toggle<br><code>--dropdown-border-width</code> — Border width<br><code>--dropdown-border-radius</code> — Outer border radius<br><code>--dropdown-inner-border-radius</code> — Inner border radius<br><code>--dropdown-divider-margin-y</code> — Divider vertical margin<br><code>--dropdown-item-padding-x</code> / <code>--dropdown-item-padding-y</code> — Item padding<br><code>--dropdown-header-padding-x</code> / <code>--dropdown-header-padding-y</code> — Header padding<br><br><strong>Typography</strong><br><code>--dropdown-font-size</code> — Menu font size<br><br><strong>Stacking</strong><br><code>--dropdown-zindex</code> — z-index"
TPL_MOKOONYX_CSS_VARS_LIST_GROUP_LABEL="List Group"
TPL_MOKOONYX_CSS_VARS_LIST_GROUP_DESC="<strong>Colours</strong><br><code>--list-group-color</code> — Default text colour<br><code>--list-group-bg</code> — Default background<br><code>--list-group-border-color</code> — Border colour<br><code>--list-group-action-color</code> — Clickable item text colour<br><code>--list-group-action-active-color</code> — Clickable item active text colour<br><code>--list-group-action-active-bg</code> — Clickable item active background<br><code>--list-group-disabled-color</code> — Disabled item text colour<br><code>--list-group-disabled-bg</code> — Disabled item background<br><code>--list-group-active-color</code> — Active item text colour<br><code>--list-group-active-bg</code> — Active item background<br><code>--list-group-active-border-color</code> — Active item border colour<br><br><strong>Dimensions</strong><br><code>--list-group-border-width</code> — Border width<br><code>--list-group-border-radius</code> — Border radius<br><code>--list-group-item-padding-x</code> / <code>--list-group-item-padding-y</code> — Item padding"
TPL_MOKOONYX_CSS_VARS_MODAL_LABEL="Modal"
TPL_MOKOONYX_CSS_VARS_MODAL_DESC="<strong>Colours</strong><br><code>--modal-color</code> — Modal text colour<br><code>--modal-bg</code> — Modal background<br><code>--modal-border-color</code> — Outer border colour<br><code>--modal-box-shadow</code> — Modal shadow<br><code>--modal-header-border-color</code> — Header border colour<br><code>--modal-footer-bg</code> — Footer background<br><code>--modal-footer-border-color</code> — Footer border colour<br><br><strong>Dimensions</strong><br><code>--modal-width</code> — Default modal width<br><code>--modal-padding</code> — Body padding<br><code>--modal-margin</code> — Outer margin<br><code>--modal-border-width</code> — Outer border width<br><code>--modal-border-radius</code> — Outer border radius<br><code>--modal-inner-border-radius</code> — Inner border radius<br><code>--modal-header-padding-x</code> / <code>--modal-header-padding-y</code> — Header padding<br><code>--modal-header-padding</code> — Header padding shorthand<br><code>--modal-header-border-width</code> — Header border width<br><code>--modal-title-line-height</code> — Title line height<br><code>--modal-footer-gap</code> — Footer button gap<br><code>--modal-footer-border-width</code> — Footer border width<br><br><strong>Stacking</strong><br><code>--modal-zindex</code> — z-index"
TPL_MOKOONYX_CSS_VARS_NAV_TABS_LABEL="Nav Tabs"
TPL_MOKOONYX_CSS_VARS_NAV_TABS_DESC="<strong>Colours</strong><br><code>--nav-tabs-border-color</code> — Tab bar border colour<br><code>--nav-tabs-link-active-color</code> — Active tab text colour<br><code>--nav-tabs-link-active-bg</code> — Active tab background<br><code>--nav-tabs-link-active-border-color</code> — Active tab border colour<br><br><strong>Dimensions</strong><br><code>--nav-tabs-border-width</code> — Border width<br><code>--nav-tabs-border-radius</code> — Tab border radius"
TPL_MOKOONYX_CSS_VARS_NAV_PILLS_LABEL="Nav Pills"
TPL_MOKOONYX_CSS_VARS_NAV_PILLS_DESC="<strong>Colours</strong><br><code>--nav-pills-link-active-color</code> — Active pill text colour<br><code>--nav-pills-link-active-bg</code> — Active pill background<br><br><strong>Dimensions</strong><br><code>--nav-pills-border-radius</code> — Pill border radius"
TPL_MOKOONYX_CSS_VARS_PAGINATION_LABEL="Pagination"
TPL_MOKOONYX_CSS_VARS_PAGINATION_DESC="<strong>Colours</strong><br><code>--pagination-color</code> — Default link colour<br><code>--pagination-bg</code> — Default background<br><code>--pagination-border-color</code> — Default border colour<br><code>--pagination-focus-color</code> — Focused link colour<br><code>--pagination-focus-bg</code> — Focused background<br><code>--pagination-focus-box-shadow</code> — Focus ring<br><code>--pagination-active-color</code> — Active page text colour<br><code>--pagination-active-bg</code> — Active page background<br><code>--pagination-active-border-color</code> — Active page border colour<br><code>--pagination-disabled-color</code> — Disabled link colour<br><code>--pagination-disabled-bg</code> — Disabled background<br><code>--pagination-disabled-border-color</code> — Disabled border colour<br><br><strong>Dimensions</strong><br><code>--pagination-padding-x</code> / <code>--pagination-padding-y</code> — Item padding<br><code>--pagination-border-width</code> — Border width<br><code>--pagination-border-radius</code> — Border radius<br><br><strong>Typography</strong><br><code>--pagination-font-size</code> — Font size"
TPL_MOKOONYX_CSS_VARS_POPOVER_LABEL="Popover"
TPL_MOKOONYX_CSS_VARS_POPOVER_DESC="<strong>Colours</strong><br><code>--popover-bg</code> — Popover background<br><code>--popover-border-color</code> — Border colour<br><code>--popover-box-shadow</code> — Popover shadow<br><code>--popover-header-color</code> — Header text colour<br><code>--popover-header-bg</code> — Header background<br><code>--popover-body-color</code> — Body text colour<br><code>--popover-arrow-border</code> — Arrow border colour<br><br><strong>Dimensions</strong><br><code>--popover-max-width</code> — Maximum width<br><code>--popover-border-width</code> — Border width<br><code>--popover-border-radius</code> — Outer border radius<br><code>--popover-inner-border-radius</code> — Inner border radius<br><code>--popover-header-padding-x</code> / <code>--popover-header-padding-y</code> — Header padding<br><code>--popover-body-padding-x</code> / <code>--popover-body-padding-y</code> — Body padding<br><code>--popover-arrow-width</code> / <code>--popover-arrow-height</code> — Arrow dimensions<br><br><strong>Typography</strong><br><code>--popover-font-size</code> — Font size<br><code>--popover-header-font-size</code> — Header font size<br><br><strong>Stacking</strong><br><code>--popover-zindex</code> — z-index"
TPL_MOKOONYX_CSS_VARS_PROGRESS_LABEL="Progress Bar"
TPL_MOKOONYX_CSS_VARS_PROGRESS_DESC="<strong>Colours</strong><br><code>--progress-bg</code> — Track background<br><code>--progress-bar-color</code> — Bar text colour<br><code>--progress-bar-bg</code> — Bar fill colour<br><code>--progress-box-shadow</code> — Track shadow<br><br><strong>Dimensions</strong><br><code>--progress-height</code> — Track height<br><code>--progress-border-radius</code> — Track border radius<br><br><strong>Typography</strong><br><code>--progress-font-size</code> — Label font size<br><br><strong>Animation</strong><br><code>--progress-bar-transition</code> — Bar width transition"
TPL_MOKOONYX_CSS_VARS_SPINNER_LABEL="Spinner"
TPL_MOKOONYX_CSS_VARS_SPINNER_DESC="<strong>Dimensions</strong><br><code>--spinner-width</code> — Spinner width<br><code>--spinner-height</code> — Spinner height<br><code>--spinner-vertical-align</code> — Inline vertical alignment<br><code>--spinner-border-width</code> — Border-style spinner track width<br><br><strong>Animation</strong><br><code>--spinner-animation-speed</code> — Rotation duration"
TPL_MOKOONYX_CSS_VARS_TABLE_LABEL="Table"
TPL_MOKOONYX_CSS_VARS_TABLE_DESC="<strong>Colours</strong><br><code>--table-color</code> — Default cell text colour<br><code>--table-bg</code> — Default cell background<br><code>--table-border-color</code> — Border colour<br><code>--table-accent-bg</code> — Accent row background (used by variants)<br><code>--table-striped-color</code> — Striped row text colour<br><code>--table-striped-bg</code> — Striped row background<br><code>--table-active-color</code> — Hovered/active row text colour<br><code>--table-active-bg</code> — Hovered/active row background"
TPL_MOKOONYX_CSS_VARS_TOAST_LABEL="Toast"
TPL_MOKOONYX_CSS_VARS_TOAST_DESC="<strong>Colours</strong><br><code>--toast-color</code> — Body text colour<br><code>--toast-bg</code> — Body background<br><code>--toast-border-color</code> — Border colour<br><code>--toast-box-shadow</code> — Toast shadow<br><code>--toast-header-color</code> — Header text colour<br><code>--toast-header-bg</code> — Header background<br><code>--toast-header-border-color</code> — Header border colour<br><br><strong>Dimensions</strong><br><code>--toast-max-width</code> — Maximum width<br><code>--toast-padding-x</code> / <code>--toast-padding-y</code> — Body padding<br><code>--toast-spacing</code> — Gap between stacked toasts<br><code>--toast-border-width</code> — Border width<br><code>--toast-border-radius</code> — Border radius<br><br><strong>Typography</strong><br><code>--toast-font-size</code> — Font size<br><br><strong>Stacking</strong><br><code>--toast-zindex</code> — z-index"
TPL_MOKOONYX_CSS_VARS_TOOLTIP_LABEL="Tooltip"
TPL_MOKOONYX_CSS_VARS_TOOLTIP_DESC="<strong>Colours</strong><br><code>--tooltip-color</code> — Text colour<br><code>--tooltip-bg</code> — Background<br><code>--tooltip-opacity</code> — Overall opacity<br><br><strong>Dimensions</strong><br><code>--tooltip-max-width</code> — Maximum width<br><code>--tooltip-padding-x</code> / <code>--tooltip-padding-y</code> — Inner padding<br><code>--tooltip-margin</code> — Offset from target<br><code>--tooltip-border-radius</code> — Border radius<br><code>--tooltip-arrow-width</code> / <code>--tooltip-arrow-height</code> — Arrow dimensions<br><br><strong>Typography</strong><br><code>--tooltip-font-size</code> — Font size<br><br><strong>Stacking</strong><br><code>--tooltip-zindex</code> — z-index"
TPL_MOKOONYX_CSS_VARS_COMPONENTS_LABEL="Component &amp; Plugin Colours"
TPL_MOKOONYX_CSS_VARS_COMPONENTS_DESC="<strong>Misc components</strong><br><code>--mod-finder-link-hover</code> — Smart Search hover background<br><code>--form-legend-color</code> — Form legend text colour<br><code>--border-gray</code> — General gray border<br><code>--subhead-color</code> — Subheading colour<br><code>--item-list-color</code> — Item list background<br><code>--notification-badge-bg</code> — Notification badge<br><br><strong>Table of Contents (TOC)</strong><br><code>--toc-link-color</code> — TOC link colour<br><code>--toc-link-active-color</code> — Active TOC link<br><br><strong>Choices.js select</strong><br><code>--choices-inner-bg</code> — Inner background<br><code>--choices-dropdown-bg</code> — Dropdown background<br><code>--choices-item-bg</code> — Tag item background<br><code>--choices-item-hover-bg</code> — Tag hover<br><code>--choices-disabled-bg</code> — Disabled state<br><code>--choices-focused-border</code> — Focused border<br><br><strong>Tab buttons (taba)</strong><br><code>--taba-btn-green</code>, <code>--taba-btn-blue</code>, <code>--taba-btn-red</code>, <code>--taba-btn-gray</code>"
TPL_MOKOONYX_CSS_VARS_OFFCANVAS_LABEL="Offcanvas Panel"
TPL_MOKOONYX_CSS_VARS_OFFCANVAS_DESC="<strong>Dimensions</strong><br><code>--offcanvas-width</code> — Panel width (default: <code>400px</code>)<br><code>--offcanvas-height</code> — Panel height for top/bottom variants (default: <code>30vh</code>)<br><code>--offcanvas-padding-x</code> / <code>--offcanvas-padding-y</code> — Inner padding<br><br><strong>Colours</strong><br><code>--offcanvas-bg</code> — Panel background (default: <code>var(--body-bg)</code>)<br><code>--offcanvas-color</code> — Panel text colour (default: <code>var(--body-color)</code>)<br><code>--offcanvas-border-width</code> / <code>--offcanvas-border-color</code> — Panel border<br><code>--offcanvas-box-shadow</code> — Drop shadow<br><br><strong>Stacking</strong><br><code>--offcanvas-zindex</code> — z-index (default: <code>1045</code>)"
TPL_MOKOONYX_CSS_VARS_VM_LABEL="VirtueMart"
TPL_MOKOONYX_CSS_VARS_VM_DESC="<strong>Surfaces &amp; text</strong><br><code>--vm-surface</code> / <code>--vm-surface-2</code> — Card/panel backgrounds<br><code>--vm-text</code> / <code>--vm-text-strong</code> / <code>--vm-text-muted</code> — Text variants<br><code>--vm-border</code> — Border colour<br><code>--vm-price-color</code> — Price text colour<br><br><strong>Layout &amp; density</strong><br><code>--vm-container-max-width</code> — Max content width (default: <code>1200px</code>)<br><code>--vm-section-gap</code> — Section spacing (default: <code>2rem</code>)<br><code>--vm-block-radius</code> / <code>--vm-block-shadow</code> — Block appearance<br><br><strong>Typography</strong><br><code>--vm-category-title-size</code> — Category heading size<br><code>--vm-product-title-size</code> — Product title size<br><code>--vm-price-size</code> — Price size<br><br><strong>Buttons</strong><br><code>--vm-btn-primary-bg</code> / <code>--vm-btn-primary-text</code> — Primary button<br><code>--vm-btn-secondary-bg</code> / <code>--vm-btn-secondary-text</code> — Secondary button<br><br><strong>Image overlay controls</strong><br><code>--vm-image-overlay-btn-bg</code> / <code>--vm-image-overlay-btn-color</code> — Overlay button appearance"
TPL_MOKOONYX_CSS_VARS_GABLE_LABEL="Gable"
TPL_MOKOONYX_CSS_VARS_GABLE_DESC="Colour tokens used by the Gable extension.<br><code>--gab-blue</code> — <code>#0066cc</code><br><code>--gab-green</code> — <code>#28a745</code><br><code>--gab-red</code> — <code>#dc3545</code><br><code>--gab-orange</code> — <code>#fd7e14</code><br><code>--gab-gray1</code> — <code>#495057</code><br><code>--gab-gray2</code> — <code>#6c757d</code><br><code>--gab-gray3</code> — <code>#adb5bd</code>"
TPL_MOKOONYX_CSS_VARS_FOOTER_LABEL="Footer"
TPL_MOKOONYX_CSS_VARS_FOOTER_DESC="<strong>Spacing</strong><br><code>--footer-padding-top</code> — Top padding (default: <code>1rem</code>)<br><code>--footer-padding-bottom</code> — Bottom padding (default: <code>80px</code>)<br><code>--footer-grid-padding-y</code> — Grid vertical padding (default: <code>2.5rem</code>)<br><code>--footer-grid-padding-x</code> — Grid horizontal padding (default: <code>0.5em</code>)"
; ===== Theme Preview tab =====
TPL_MOKOONYX_THEME_PREVIEW_FIELDSET_LABEL="Theme Preview"
TPL_MOKOONYX_THEME_PREVIEW_INTRO="<p>Live preview of all CSS variables, hero variants, block colours, and Bootstrap components rendered with your active theme. Use the <strong>Toggle Light / Dark</strong> button inside the preview to switch modes. This page is also available as a standalone file at <code>templates/mokoonyx/templates/theme-test.html</code>.</p>"
TPL_MOKOONYX_THEME_PREVIEW_FRAME="<iframe src='../templates/mokoonyx/templates/theme-test.html' style='width:100%;height:80vh;border:1px solid #dee2e6;border-radius:.375rem;' loading='lazy' title='Theme test sheet preview'></iframe>"
; ===== Misc =====
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
JGLOBAL_OFFLINE="Offline"

View File

@@ -0,0 +1,31 @@
; 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
;
TPL_MOKOONYX="MokoOnyx Site template"
TPL_MOKOONYX_GOOGLE_FIELDSET_LABEL="Google"
TPL_MOKOONYX_DRAWERS_FIELDSET_LABEL="Drawers"
TPL_MOKOONYX_MOD_MENU_LAYOUT_COLLAPSE_METISMENU="Collapsible Dropdown"
TPL_MOKOONYX_MOD_MENU_LAYOUT_DROPDOWN_METISMENU="Dropdown"
TPL_MOKOONYX_POSITION_BANNER="Banner"
TPL_MOKOONYX_POSITION_BELOW_TOP="Below Topbar"
TPL_MOKOONYX_POSITION_BRAND_ASIDE="Brand Aside"
TPL_MOKOONYX_POSITION_BOTTOM_A="Bottom-A"
TPL_MOKOONYX_POSITION_BOTTOM_B="Bottom-B"
TPL_MOKOONYX_POSITION_BREADCRUMBS="Breadcrumbs"
TPL_MOKOONYX_POSITION_DEBUG="Debug"
TPL_MOKOONYX_POSITION_FOOTER="Footer"
TPL_MOKOONYX_POSITION_MAIN_BOTTOM="Main-bottom"
TPL_MOKOONYX_POSITION_MAIN_TOP="Main-top"
TPL_MOKOONYX_POSITION_MENU="Menu"
TPL_MOKOONYX_POSITION_SEARCH="Search"
TPL_MOKOONYX_POSITION_SIDEBAR_LEFT="Sidebar-left"
TPL_MOKOONYX_POSITION_SIDEBAR_RIGHT="Sidebar-right"
TPL_MOKOONYX_POSITION_TOP_A="Top-a"
TPL_MOKOONYX_POSITION_TOP_B="Top-b"
TPL_MOKOONYX_POSITION_TOPBAR="Top Bar"
TPL_MOKOONYX_POSITION_DRAWER_LEFT="Drawer-Left"
TPL_MOKOONYX_POSITION_DRAWER_RIGHT="Drawer-Right"
TPL_MOKOONYX_XML_DESCRIPTION="<h3>MokoOnyx Template Description</h3> <p> <strong>MokoOnyx</strong> continues Joomlas tradition of space-themed default templates— building on the legacy of <em>Solarflare</em> (Joomla 1.0), <em>Milkyway</em> (Joomla 1.5), and <em>Protostar</em> (Joomla 3.0). </p> <p> This template is a customized fork of the <strong>Cassiopeia</strong> 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. </p> <h4>Custom Colour Themes</h4> <p> Starter palette files are included with the template. To create a custom colour scheme, copy <code>templates/mokoonyx/templates/light.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code>, or <code>templates/mokoonyx/templates/dark.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/dark.custom.css</code>. Customise the CSS variables to match your brand, then activate your palette in <em>System → Site Templates → MokoOnyx → Theme tab</em> by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the <em>CSS Variables</em> tab in template options. </p> <h4>Custom CSS &amp; JavaScript</h4> <p> For site-specific styles and scripts that should survive template updates, create the following files: </p> <ul> <li><code>media/templates/site/mokoonyx/css/user.css</code> — loaded on every page for custom CSS overrides.</li> <li><code>media/templates/site/mokoonyx/js/user.js</code> — loaded on every page for custom JavaScript.</li> </ul> <p> These files are gitignored and will not be overwritten by template updates. </p> <h4>Code Attribution</h4> <p> This template is based on the original <strong>Cassiopeia</strong> template developed by the <a href=\"https://www.joomla.org\" target=\"_blank\" rel=\"noopener\">Joomla! Project</a> and released under the GNU General Public License. </p> <p> Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards. </p> <p> It includes integration with <a href=\"https://afeld.github.io/bootstrap-toc/\" target=\"_blank\" rel=\"noopener\">Bootstrap TOC</a>, an open-source table of contents generator by A. Feld, licensed under the MIT License. </p> <p> All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable. </p>"

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,273 @@
; 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
; ===== System / layout =====
TPL_MOKOONYX_DEVELOPMENTMODE_LABEL="Development Mode"
TPL_MOKOONYX_DEVELOPMENTMODE_DESC="When enabled, unminified CSS and JavaScript files are loaded for easier debugging. When disabled, minified assets are served for faster page loads. Google Tag Manager and Google Analytics may also be disabled in development mode."
TPL_MOKOONYX_FLUID_LABEL="Layout"
TPL_MOKOONYX_STATIC="Static"
TPL_MOKOONYX_FLUID="Fluid"
; ===== Custom Code tab =====
TPL_MOKOONYX_CUSTOM_CODE_FIELDSET="Custom Code"
TPL_MOKOONYX_CUSTOM_HEAD_START_LABEL="Custom Head: Start"
TPL_MOKOONYX_CUSTOM_HEAD_START_DESC="This content will be inserted at the beginning of the &lt;head&gt; tag"
TPL_MOKOONYX_CUSTOM_HEAD_END_LABEL="Custom Head: End"
TPL_MOKOONYX_CUSTOM_HEAD_END_DESC="This content will be inserted at the end of the &lt;head&gt; tag"
TPL_MOKOONYX_OFFLINEEMBED_LABEL="Offline Page Embed Code"
TPL_MOKOONYX_OFFLINEEMBED_DESC="In addition to the 'Offline message' defined in 'Global Configuration', this will be displayed on the offline page.<i>Use for Mailchimp code and Social Icons</i>"
; ===== Drawers =====
TPL_MOKOONYX_DRAWERS_FIELDSET_LABEL="Drawers"
TPL_MOKOONYX_DRAWER_LEFT_ICON_LABEL="Drawer Left Icon CSS"
TPL_MOKOONYX_DRAWER_LEFT_ICON_DESC="Enter the Font-Awesome class for the left drawer toggle (e.g. 'fas fa-chevron-left')."
TPL_MOKOONYX_DRAWER_RIGHT_ICON_LABEL="Drawer Right Icon CSS"
TPL_MOKOONYX_DRAWER_RIGHT_ICON_DESC="Enter the Font-Awesome class for the right drawer toggle (e.g. 'fas fa-chevron-right')."
; ===== Favicon =====
TPL_MOKOONYX_FAVICON_FIELDSET_LABEL="Favicon"
TPL_MOKOONYX_FAVICON_NOTE="<p>Upload a square <strong>PNG image</strong> (recommended 512×512 or larger). The template will automatically generate all required favicon sizes including ICO, Apple Touch Icon (180×180), and Android icons (192×192, 512×512). Generated files are cached in <code>images/favicons/</code>.</p>"
TPL_MOKOONYX_FAVICON_SOURCE_LABEL="Favicon Source Image"
TPL_MOKOONYX_FAVICON_SOURCE_DESC="Select a square PNG image to use as the site favicon. Recommended size: 512×512 pixels or larger."
; ===== Google =====
TPL_MOKOONYX_GOOGLE_FIELDSET_LABEL="Google"
TPL_MOKOONYX_GOOGLE_NOTE_TEXT="<h3>PLEASE NOTE:</h3>If fields are left blank, relative Google features will not be used"
TPL_MOKOONYX_GOOGLETAGMANAGER_LABEL="Use Google Tag Manager?"
TPL_MOKOONYX_GOOGLETAGMANAGER_DESC="Do you want to use Google Tag Manager?<br>More information on Google Tag Manager can be found <a target='_blank' href='https://support.google.com/tagmanager/answer/14842164'>here.</a>"
TPL_MOKOONYX_GOOGLETAGMANAGERID_LABEL="Google Tag Manager ID"
TPL_MOKOONYX_GOOGLETAGMANAGERID_DESC="Begins with 'GTM-'"
TPL_MOKOONYX_GOOGLEANALYTICS_LABEL="Use Google Analytics?"
TPL_MOKOONYX_GOOGLEANALYTICS_DESC="Do you want to use Google Analytics?<br>More information on Google Analytics can be found <a target='_blank' href='https://developers.google.com/analytics'>here.</a>"
TPL_MOKOONYX_GOOGLEANALYTICSID_LABEL="Google Analytics ID"
TPL_MOKOONYX_GOOGLEANALYTICSID_DESC="Begins with 'G-'"
TPL_MOKOONYX_GOOGLESITEKEY_LABEL="Google Search Console Verification"
TPL_MOKOONYX_GOOGLESITEKEY_DESC="Paste the content value from the &lt;meta name=&quot;google-site-verification&quot;&gt; tag. Find this in Google Search Console under Ownership Verification &rarr; HTML tag method."
; ===== Branding & icons (Theme tab) =====
TPL_MOKOONYX_BRAND_LABEL="Brand"
TPL_MOKOONYX_LOGO_LABEL="Logo"
TPL_MOKOONYX_TITLE="Title (alternative to logo)"
TPL_MOKOONYX_TAGLINE_LABEL="Tagline"
TPL_MOKOONYX_TAGLINE_DESC="Optional text to show as a subheading"
TPL_MOKOONYX_FA7KITCODE_LABEL="Font Awesome 7 Kit Unique Code"
TPL_MOKOONYX_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 &lt;head&gt; 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) =====
TPL_MOKOONYX_FONT_LABEL="Fonts Scheme"
TPL_MOKOONYX_FONT_LABEL_DESC="Select a font scheme for your site. Local fonts are loaded from the template folder, while web fonts are loaded from external sources (Google Fonts). The default is Roboto (local). See the note below for important privacy and performance considerations."
TPL_MOKOONYX_FONT_GROUP_LOCAL="Fonts from Folder"
TPL_MOKOONYX_FONT_GROUP_WEB="Fonts from Web"
TPL_MOKOONYX_FONT_NOTE_TEXT="Loading fonts from external sources might be against privacy regulations in some countries.<br>Loading fonts from a local folder might have a performance impact on your site."
; ===== Header & navigation (Theme tab) =====
TPL_MOKOONYX_STICKY_LABEL="Sticky Header"
TPL_MOKOONYX_BACKTOTOP="Back to Top"
TPL_MOKOONYX_TOC_TITLE="Table of Contents"
TPL_MOKOONYX_BACKTOTOP_LABEL="Back-to-top Link"
TPL_MOKOONYX_TOC="Table of Contents"
; ===== Color palette choices (shared) =====
TPL_MOKOONYX_COLOR_NAME_STANDARD="Standard"
TPL_MOKOONYX_COLOR_NAME_CUSTOM="Custom"
; New labels for Theme tab dropdowns
TPL_MOKOONYX_COLOR_LIGHT_NAME_LABEL="Light color palette"
TPL_MOKOONYX_COLOR_LIGHT_NAME_DESC="Select a color palette for light mode. <strong>Standard</strong> uses the default blue theme with comprehensive styling for all components. <strong>Custom</strong> loads <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code> — copy the starter file from <code>templates/mokoonyx/templates/light.custom.css</code> and customize the CSS variables to match your brand. This file is stored in the media folder and will not be overwritten by template updates."
TPL_MOKOONYX_COLOR_DARK_NAME_LABEL="Dark color palette"
TPL_MOKOONYX_COLOR_DARK_NAME_DESC="Select a color palette for dark mode. <strong>Standard</strong> uses the default blue theme optimized for dark backgrounds with proper contrast. <strong>Custom</strong> loads <code>media/templates/site/mokoonyx/css/theme/dark.custom.css</code> — copy the starter file from <code>templates/mokoonyx/templates/dark.custom.css</code> and customize the CSS variables to match your brand. This file is stored in the media folder and will not be overwritten by template updates."
; ===== Theme tab (core feature strings) =====
TPL_MOKO_THEME_FIELDSET="Theme"
TPL_MOKO_THEME_SECTION_GENERAL="General"
TPL_MOKO_THEME_SECTION_VARS="Variables & Palettes"
TPL_MOKO_THEME_SECTION_TYPO="Typography"
TPL_MOKO_THEME_SECTION_BRAND="Branding & Icons"
TPL_MOKO_THEME_SECTION_HEADER="Header & Navigation"
TPL_MOKO_THEME_SECTION_TOGGLE="Theme Toggle UI"
TPL_MOKO_THEME_ENABLED="Enable theme feature"
TPL_MOKO_THEME_ENABLED_DESC="Turn the entire light/dark feature on or off."
TPL_MOKO_THEME_CONTROL_TYPE="Theme Control Type"
TPL_MOKO_THEME_CONTROL_TYPE_DESC="Choose a visible toggle (Switch or Radios), or no control to follow System only."
TPL_MOKO_THEME_DEFAULT_CHOICE="Default Choice"
TPL_MOKO_THEME_DEFAULT_CHOICE_DESC="Initial theme when no user preference is stored."
TPL_MOKO_THEME_AUTO_DARK="Auto Dark Mode"
TPL_MOKO_THEME_AUTO_DARK_DESC="Force the site to switch to dark mode automatically. When enabled, the template will override the default and use dark unless the user explicitly selects otherwise."
TPL_MOKO_THEME_META_COLOR_SCHEME="Add &lt;meta name=&quot;color-scheme&quot;&gt;"
TPL_MOKO_THEME_META_COLOR_SCHEME_DESC="Advertise light/dark support for UA controls."
TPL_MOKO_THEME_META_THEME_COLOR="Add &lt;meta name=&quot;theme-color&quot;&gt;"
TPL_MOKO_THEME_META_THEME_COLOR_DESC="Update mobile address bar color."
TPL_MOKO_THEME_BASE_CSS="Base template CSS"
TPL_MOKO_THEME_BASE_CSS_DESC="Main stylesheet that consumes variables."
TPL_MOKO_THEME_BRIDGE="Sync data-bs-theme with data-aria-theme"
TPL_MOKO_THEME_BRIDGE_DESC="Keep both attributes in lockstep so Bootstrap and custom CSS stay aligned."
TPL_MOKO_THEME_FAB_ENABLED="Show floating theme switch"
TPL_MOKO_THEME_FAB_ENABLED_DESC="Display a persistent, accessible theme toggle."
TPL_MOKO_THEME_FAB_POS="Floating switch position"
TPL_MOKO_THEME_FAB_POS_DESC="Screen corner for the toggle."
; ===== Accessibility toolbar =====
TPL_MOKO_A11Y_TOOLBAR_ENABLED="Accessibility toolbar"
TPL_MOKO_A11Y_TOOLBAR_ENABLED_DESC="Show a floating accessibility toolbar with text resize and color inversion controls."
TPL_MOKO_A11Y_TEXT_RESIZE="Text resize"
TPL_MOKO_A11Y_TEXT_RESIZE_DESC="Allow visitors to increase or decrease text size."
TPL_MOKO_A11Y_COLOR_INVERSION="Color inversion"
TPL_MOKO_A11Y_COLOR_INVERSION_DESC="Allow visitors to invert page colors for improved readability."
TPL_MOKO_A11Y_TOOLBAR_POS="Toolbar position"
TPL_MOKO_A11Y_TOOLBAR_POS_DESC="Screen corner for the accessibility toolbar."
TPL_MOKO_A11Y_BTN_LABEL="Accessibility options"
TPL_MOKO_A11Y_TEXT_DECREASE="Decrease text size"
TPL_MOKO_A11Y_TEXT_RESET="Reset text size"
TPL_MOKO_A11Y_TEXT_INCREASE="Increase text size"
TPL_MOKO_A11Y_INVERT_COLORS="Invert colors"
TPL_MOKO_A11Y_HIGH_CONTRAST="High contrast"
TPL_MOKO_A11Y_HIGH_CONTRAST_DESC="Allow visitors to boost page contrast for improved readability."
TPL_MOKO_A11Y_HIGHLIGHT_LINKS="Highlight links"
TPL_MOKO_A11Y_HIGHLIGHT_LINKS_DESC="Allow visitors to outline all links so they stand out from surrounding text."
TPL_MOKO_A11Y_READABLE_FONT="Readable font"
TPL_MOKO_A11Y_READABLE_FONT_DESC="Allow visitors to switch to a clean system font optimized for readability."
TPL_MOKO_A11Y_PAUSE_ANIMATIONS="Pause animations"
TPL_MOKO_A11Y_PAUSE_ANIMATIONS_DESC="Allow visitors to stop all CSS animations and transitions."
; ===== CSS Variables tab =====
TPL_MOKOONYX_CSS_VARS_FIELDSET_LABEL="CSS Variables"
TPL_MOKOONYX_CSS_VARS_INTRO="<p>All colors, spacing and layout values are driven by CSS custom properties. To override any variable without editing the template, add your overrides to <code>media/templates/site/mokoonyx/css/user.css</code>, or create a custom palette file (see the Theme tab). Variables are scoped to <code>:root[data-bs-theme=&quot;light&quot;]</code> or <code>:root[data-bs-theme=&quot;dark&quot;]</code> so light and dark values are independent.</p>"
TPL_MOKOONYX_CSS_VARS_BRAND_LABEL="Brand &amp; Theme Colors"
TPL_MOKOONYX_CSS_VARS_BRAND_DESC="<code>--color-primary</code> — Primary brand color (default: <code>#112855</code>)<br><code>--accent-color-primary</code> — Primary accent (default: <code>#3f8ff0</code>)<br><code>--accent-color-secondary</code> — Secondary accent"
TPL_MOKOONYX_CSS_VARS_LINKS_LABEL="Links &amp; Link Utilities"
TPL_MOKOONYX_CSS_VARS_LINKS_DESC="<strong>Core link tokens</strong><br><code>--color-link</code> — Base link color<br><code>--color-hover</code> — Base hover color<br><code>--link-color</code> / <code>--link-hover-color</code> — Bootstrap link colors<br><code>--link-decoration</code> — Default text-decoration<br><code>--link-active-color</code> — Active state<br><br><strong>Semantic link utilities</strong> (replace <code>{color}</code> with <code>primary</code>, <code>secondary</code>, <code>success</code>, <code>info</code>, <code>warning</code>, <code>danger</code>, <code>light</code>, <code>dark</code>)<br><code>--link-{color}-color</code> — Color for <code>.link-{color}</code><br><code>--link-{color}-hover-color</code> — Hover color"
TPL_MOKOONYX_CSS_VARS_TYPO_LABEL="Typography &amp; Body"
TPL_MOKOONYX_CSS_VARS_TYPO_DESC="<code>--body-color</code> — Default text color (default: <code>#22262a</code>)<br><code>--body-bg</code> — Page background (default: <code>#fff</code>)<br><code>--body-font-family</code> — Font stack<br><code>--body-font-size</code> — Base size (default: <code>1rem</code>)<br><code>--body-font-weight</code> — Base weight (default: <code>400</code>)<br><code>--body-line-height</code> — Line height (default: <code>1.5</code>)<br><code>--heading-color</code> — Heading color (default: <code>inherit</code>)<br><code>--muted-color</code> — Muted/secondary text (default: <code>#6d757e</code>)<br><code>--code-color</code> — Inline code color<br><code>--emphasis-color</code> — Strong emphasis color<br><code>--secondary-color</code> / <code>--tertiary-color</code> — Stepped text opacities<br><code>--highlight-color</code> / <code>--highlight-bg</code> — <code>&lt;mark&gt;</code> colors<br><code>--font-sans-serif</code> / <code>--font-monospace</code> — Font stacks"
TPL_MOKOONYX_CSS_VARS_NAV_LABEL="Navigation, Navbar &amp; Offcanvas"
TPL_MOKOONYX_CSS_VARS_NAV_DESC="<strong>Theme nav</strong><br><code>--nav-bg-color</code> — Navbar background<br><code>--nav-text-color</code> — Navbar text<br><code>--mainmenu-nav-link-color</code> — Active nav link<br><br><strong>Navbar tokens</strong><br><code>--navbar-padding-x</code> / <code>--navbar-padding-y</code> — Navbar padding<br><code>--navbar-brand-font-size</code> — Brand font size<br><code>--navbar-toggler-border-color</code> — Mobile toggler border<br><code>--nav-link-padding-x</code> / <code>--nav-link-padding-y</code> — Link padding<br><code>--nav-link-font-weight</code> — Link weight<br><code>--nav-link-disabled-color</code> — Disabled link color<br><br><strong>Offcanvas</strong><br><code>--offcanvas-color</code> — Offcanvas text color<br><code>--offcanvas-padding-x</code> / <code>--offcanvas-padding-y</code> — Offcanvas padding"
TPL_MOKOONYX_CSS_VARS_LAYOUT_LABEL="Layout &amp; Spacing"
TPL_MOKOONYX_CSS_VARS_LAYOUT_DESC="<code>--padding-x</code> / <code>--padding-y</code> — Default component padding<br><code>--nav-toggle-size</code> — Mobile nav toggle button size (default: <code>3rem</code>)<br><code>--secondary-bg</code> — Secondary surface background (default: <code>#eaedf0</code>)<br><code>--tertiary-bg</code> — Tertiary surface background (default: <code>#f9fafb</code>)<br><code>--hr-color</code> — Horizontal rule color<br><code>--border-color-soft</code> — Soft border variant<br><code>--kbd-bg</code> / <code>--kbd-ink</code> — Keyboard element colors<br><code>--toc-bg</code> / <code>--toc-ink</code> — Table of contents colors<br><code>--selection-bg</code> / <code>--selection-ink</code> — Text selection colors<br><code>--gradient</code> — Bootstrap gradient overlay value<br><code>--bg-opacity</code> — Background opacity utility base"
TPL_MOKOONYX_CSS_VARS_BP_LABEL="Breakpoints"
TPL_MOKOONYX_CSS_VARS_BP_DESC="Read-only reference values matching Bootstrap breakpoints.<br><code>--bp-xs</code> — <code>0</code><br><code>--bp-sm</code> — <code>576px</code><br><code>--bp-md</code> — <code>768px</code><br><code>--bp-lg</code> — <code>992px</code><br><code>--bp-xl</code> — <code>1200px</code>"
TPL_MOKOONYX_CSS_VARS_BS_LABEL="Bootstrap Semantic Palette"
TPL_MOKOONYX_CSS_VARS_BS_DESC="These map to Bootstrap components (buttons, alerts, badges). Override to retheme all components at once.<br><code>--primary</code> — <code>#010156</code><br><code>--secondary</code> — <code>#6d757e</code><br><code>--success</code> — <code>#448344</code><br><code>--info</code> — <code>#30638d</code><br><code>--warning</code> — <code>#ad6200</code><br><code>--danger</code> — <code>#a51f18</code><br><code>--light</code> — <code>#f9fafb</code><br><code>--dark</code> — <code>#353b41</code><br>Each color also has an <code>--{color}-rgb</code> variant for use in <code>rgba()</code> expressions."
TPL_MOKOONYX_CSS_VARS_BS_STATES_LABEL="Bootstrap State Colors"
TPL_MOKOONYX_CSS_VARS_BS_STATES_DESC="Contextual state tokens used by alerts, badges and list groups. Replace <code>{color}</code> with <code>primary</code>, <code>secondary</code>, <code>success</code>, <code>info</code>, <code>warning</code>, <code>danger</code>, <code>light</code>, or <code>dark</code>.<br><code>--{color}-text-emphasis</code> — High-contrast text on subtle backgrounds<br><code>--{color}-bg-subtle</code> — Tinted component background<br><code>--{color}-border-subtle</code> — Tinted component border"
TPL_MOKOONYX_CSS_VARS_ALERT_LIST_LABEL="Alert &amp; List Group Colors"
TPL_MOKOONYX_CSS_VARS_ALERT_LIST_DESC="<strong>Alert link colors</strong> — override to adjust link contrast inside <code>.alert-{color}</code> components.<br><code>--alert-{color}-link-color</code> — e.g. <code>--alert-primary-link-color</code><br><br><strong>List group item colors</strong> — contextual surfaces for <code>.list-group-item-{color}</code>.<br><code>--list-group-item-{color}-color</code> — Text color<br><code>--list-group-item-{color}-bg</code> — Background<br><code>--list-group-item-{color}-active-bg</code> — Active state background"
TPL_MOKOONYX_CSS_VARS_COLORS_LABEL="Standard Colors, Grays &amp; Opacity"
TPL_MOKOONYX_CSS_VARS_COLORS_DESC="<strong>Named colors</strong><br><code>--blue</code>, <code>--indigo</code>, <code>--purple</code>, <code>--pink</code>, <code>--red</code>, <code>--orange</code>, <code>--yellow</code>, <code>--green</code>, <code>--teal</code>, <code>--cyan</code>, <code>--black</code>, <code>--white</code><br><br><strong>Gray scale</strong><br><code>--gray-100</code> through <code>--gray-900</code> plus <code>--white-rgb</code> and <code>--black-rgb</code><br><br><strong>Opacity utilities</strong><br><code>--opacity-0</code>, <code>--opacity-5</code>, <code>--opacity-10</code>, <code>--opacity-15</code>, <code>--opacity-20</code>, <code>--opacity-25</code>, <code>--opacity-30</code>, <code>--opacity-50</code>, <code>--opacity-75</code>, <code>--opacity-100</code>"
TPL_MOKOONYX_CSS_VARS_HERO_LABEL="Hero / Banner Overlay"
TPL_MOKOONYX_CSS_VARS_HERO_DESC="Applied to the <code>.custom-hero</code> / <code>.banner-overlay</code> layout. Set on <code>:root[data-bs-theme]</code> so light and dark values are independent.<br><code>--hero-height</code> — Banner height (default: <code>70vh</code>)<br><code>--hero-color</code> — Base text color<br><code>--hero-bg-repeat</code> — Background repeat (default: <code>no-repeat</code>)<br><code>--hero-bg-attachment</code> — Background attachment (default: <code>fixed</code>)<br><code>--hero-bg-position</code> — Background position (default: <code>top center</code>)<br><code>--hero-bg-size</code> — Background size (default: <code>cover</code>)<br><code>--hero-border-bottom</code> — Bottom border (default: <code>solid var(--accent-color-secondary)</code>)<br><code>--hero-overlay-bg</code> — Overlay tint color (light default: <code>hsla(0,0%,0%,0.1)</code> / dark default: <code>hsla(0,0%,0%,0.3)</code>)<br><code>--hero-overlay-padding</code> — Overlay inner padding (default: <code>1em</code>)<br><code>--hero-overlay-text-align</code> — Overlay text alignment (default: <code>center</code>)<br><code>--hero-overlay-text-color</code> — Overlay text color"
TPL_MOKOONYX_CSS_VARS_HERO_VARIANTS_LABEL="Hero Variants (.hero#primary / .hero#secondary)"
TPL_MOKOONYX_CSS_VARS_HERO_VARIANTS_DESC="Two-variant hero system using <code>.hero#primary</code> and <code>.hero#secondary</code>. Each variant resolves its own CSS variable set per theme.<br><br><strong>Primary variant</strong> — homepage, main landing pages (sky blue tint, softer overlay)<br><code>--hero-primary-bg-color</code> — Fallback background color<br><code>--hero-primary-overlay</code> — Gradient overlay tint<br><code>--hero-primary-color</code> — Text color<br><br><strong>Secondary variant</strong> — inner pages, events, about (navy overlay, lighter text)<br><code>--hero-secondary-bg-color</code> — Fallback background color<br><code>--hero-secondary-overlay</code> — Gradient overlay tint<br><code>--hero-secondary-color</code> — Text color<br><br><strong>HTML usage:</strong><br><code>&lt;div class=&quot;hero&quot; id=&quot;primary&quot; style=&quot;background-image:url(...)&quot;&gt;</code>"
TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_LABEL="Block Color System (top-a / top-b / bottom-a / bottom-b)"
TPL_MOKOONYX_CSS_VARS_BLOCK_COLORS_DESC="Automatic brand color palette for modules in <code>top-a</code>, <code>top-b</code>, <code>bottom-a</code>, and <code>bottom-b</code> positions. Colors assigned by <code>:nth-child()</code> order — no classes needed.<br><br><strong>Slot palette</strong><br><code>--block-color-1</code> / <code>--block-text-1</code> — 1st module<br><code>--block-color-2</code> / <code>--block-text-2</code> — 2nd module<br><code>--block-color-3</code> / <code>--block-text-3</code> — 3rd module<br><code>--block-color-4</code> / <code>--block-text-4</code> — 4th module<br><br><strong>Named overrides</strong> (add an ID to the module HTML to bypass slot color)<br><code>--block-highlight-bg</code> / <code>--block-highlight-text</code> — for <code>#block-highlight</code><br><code>--block-cta-bg</code> / <code>--block-cta-text</code> — for <code>#block-cta</code><br><code>--block-alert-bg</code> / <code>--block-alert-text</code> — for <code>#block-alert</code><br><br><strong>Priority:</strong> Named ID &gt; Slot color. No <code>!important</code> needed — specificity handles it."
TPL_MOKOONYX_CSS_VARS_HEADER_LABEL="Header Background"
TPL_MOKOONYX_CSS_VARS_HEADER_DESC="Controls the background of the topbar/header area.<br><code>--header-background-image</code> — CSS <code>background-image</code> value (default: built-in SVG pattern)<br><code>--header-background-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--header-background-repeat</code> — e.g. <code>repeat</code>, <code>no-repeat</code><br><code>--header-background-size</code> — e.g. <code>auto</code>, <code>cover</code>, <code>contain</code>"
TPL_MOKOONYX_CSS_VARS_CONTAINERS_LABEL="Container Backgrounds"
TPL_MOKOONYX_CSS_VARS_CONTAINERS_DESC="Each layout container has its own background variables. Replace <code>{pos}</code> with: <code>below-topbar</code>, <code>top-a</code>, <code>top-b</code>, <code>sidebar</code>, <code>bottom-a</code>, or <code>bottom-b</code>.<br><br><code>--container-{pos}-bg-image</code> — Background image (default: <code>none</code>)<br><code>--container-{pos}-bg-color</code> — Background color (default: <code>transparent</code>)<br><code>--container-{pos}-bg-position</code> — Background position<br><code>--container-{pos}-bg-attachment</code> — <code>fixed</code> or <code>scroll</code><br><code>--container-{pos}-bg-repeat</code> — Repeat behavior<br><code>--container-{pos}-bg-size</code> — e.g. <code>cover</code>, <code>auto</code><br><code>--container-{pos}-border</code> — Border shorthand<br><code>--container-{pos}-border-radius</code> — Border radius<br><br>Also: <code>--container-toc-bg</code> / <code>--container-toc-color</code> for the TOC sidebar."
TPL_MOKOONYX_CSS_VARS_BORDERS_LABEL="Borders"
TPL_MOKOONYX_CSS_VARS_BORDERS_DESC="<code>--border-width</code> — Default width (default: <code>1px</code>)<br><code>--border-style</code> — Default style (default: <code>solid</code>)<br><code>--border-color</code> — Default border color (default: <code>#dfe3e7</code>)<br><code>--border-color-translucent</code> — Semi-transparent border<br><code>--border-radius</code> — Default radius (default: <code>.25rem</code>)<br><code>--border-radius-sm</code> — Small radius<br><code>--border-radius-lg</code> — Large radius<br><code>--border-radius-xl</code> — Extra large radius<br><code>--border-radius-xxl</code> — 2XL radius (default: <code>2rem</code>)<br><code>--border-radius-pill</code> — Pill radius (default: <code>50rem</code>)"
TPL_MOKOONYX_CSS_VARS_SHADOWS_LABEL="Shadows &amp; Shadow Tokens"
TPL_MOKOONYX_CSS_VARS_SHADOWS_DESC="<strong>Box shadows</strong><br><code>--box-shadow</code> — Standard shadow<br><code>--box-shadow-sm</code> — Subtle shadow<br><code>--box-shadow-lg</code> — Prominent shadow<br><code>--box-shadow-inset</code> — Inset shadow<br><br><strong>Shadow color tokens</strong> — used as building blocks by component shadows<br><code>--shadow-color-light</code> — <code>rgba(black, 0.15)</code><br><code>--shadow-color-medium</code> — <code>rgba(black, 0.25)</code><br><code>--shadow-color-dark</code> — <code>rgba(black, 0.30)</code><br><code>--highlight-translucent</code> — <code>rgba(white, 0.15)</code>"
TPL_MOKOONYX_CSS_VARS_FORMS_LABEL="Focus &amp; Forms"
TPL_MOKOONYX_CSS_VARS_FORMS_DESC="<code>--focus-ring-width</code> — Keyboard focus ring width (default: <code>.25rem</code>)<br><code>--focus-ring-opacity</code> — Focus ring opacity<br><code>--focus-ring-color</code> — Focus ring color<br><code>--input-color</code> — Input text color<br><code>--input-bg</code> — Input background<br><code>--input-border-color</code> — Input border color<br><code>--input-focus-border-color</code> — Focused border color<br><code>--input-focus-box-shadow</code> — Focused input shadow<br><code>--input-placeholder-color</code> — Placeholder text color<br><code>--input-disabled-bg</code> — Disabled input background<br><code>--input-disabled-border-color</code> — Disabled input border<br><code>--form-valid-color</code> / <code>--form-valid-border-color</code> — Valid state<br><code>--form-invalid-color</code> / <code>--form-invalid-border-color</code> — Invalid state"
TPL_MOKOONYX_CSS_VARS_BUTTONS_LABEL="Buttons"
TPL_MOKOONYX_CSS_VARS_BUTTONS_DESC="Applied on <code>:root</code> for global button defaults:<br><code>--btn-border-radius</code> — Button border radius<br><code>--btn-box-shadow</code> — Button box shadow<br><br>Applied on <code>.btn</code> for base button tokens (overridable per variant):<br><code>--btn-padding-x</code> / <code>--btn-padding-y</code> — Padding<br><code>--btn-font-size</code> / <code>--btn-font-weight</code> / <code>--btn-line-height</code> — Typography<br><code>--btn-color</code> / <code>--btn-bg</code> / <code>--btn-border-color</code> — Default state<br><code>--btn-hover-color</code> / <code>--btn-hover-bg</code> / <code>--btn-hover-border-color</code> — Hover state<br><code>--btn-active-color</code> / <code>--btn-active-bg</code> / <code>--btn-active-shadow</code> — Active state<br><code>--btn-disabled-opacity</code> — Disabled opacity<br><br>Each <code>.btn-{color}</code> and <code>.btn-outline-{color}</code> class inherits these tokens and sets its own values."
TPL_MOKOONYX_CSS_VARS_CARDS_LABEL="Cards"
TPL_MOKOONYX_CSS_VARS_CARDS_DESC="<code>--card-spacer-y</code> / <code>--card-spacer-x</code> — Body padding (default: <code>1rem</code>)<br><code>--card-title-spacer-y</code> — Title bottom margin (default: <code>0.5rem</code>)<br><code>--card-border-width</code> — Border width (default: <code>1px</code>)<br><code>--card-border-color</code> — Border color<br><code>--card-border-radius</code> — Border radius<br><code>--card-box-shadow</code> — Card shadow (default: <code>none</code>)<br><code>--card-cap-padding-y</code> / <code>--card-cap-padding-x</code> — Header/footer padding<br><code>--card-cap-bg</code> — Header/footer background<br><code>--card-cap-color</code> — Header/footer text color<br><code>--card-color</code> — Body text color<br><code>--card-bg</code> — Card background"
TPL_MOKOONYX_CSS_VARS_ACCORDION_LABEL="Accordion"
TPL_MOKOONYX_CSS_VARS_ACCORDION_DESC="<strong>Colors</strong><br><code>--accordion-color</code> — Panel text color<br><code>--accordion-bg</code> — Panel background<br><code>--accordion-border-color</code> — Border color<br><code>--accordion-btn-color</code> — Button text color<br><code>--accordion-btn-bg</code> — Button background<br><code>--accordion-btn-focus-border-color</code> — Button focus border color<br><code>--accordion-btn-focus-box-shadow</code> — Button focus ring<br><code>--accordion-active-color</code> — Active item text color<br><code>--accordion-active-bg</code> — Active item background<br><br><strong>Dimensions</strong><br><code>--accordion-border-width</code> — Border width<br><code>--accordion-border-radius</code> — Outer border radius<br><code>--accordion-inner-border-radius</code> — Inner border radius<br><code>--accordion-btn-padding-x</code> / <code>--accordion-btn-padding-y</code> — Button padding<br><code>--accordion-body-padding-x</code> / <code>--accordion-body-padding-y</code> — Body padding<br><br><strong>Icon &amp; Animation</strong><br><code>--accordion-btn-icon</code> — Collapse icon (collapsed state)<br><code>--accordion-btn-icon-width</code> — Icon size<br><code>--accordion-btn-icon-transform</code> — Icon rotation when expanded<br><code>--accordion-btn-icon-transition</code> — Icon rotation transition<br><code>--accordion-btn-active-icon</code> — Icon (expanded state)<br><code>--accordion-transition</code> — Panel open/close transition<br><br><strong>Stacking</strong><br><code>--accordion-zindex</code> — z-index"
TPL_MOKOONYX_CSS_VARS_ALERT_BASE_LABEL="Alert (Base)"
TPL_MOKOONYX_CSS_VARS_ALERT_BASE_DESC="<strong>Colors</strong><br><code>--alert-color</code> — Alert text color<br><code>--alert-bg</code> — Alert background<br><code>--alert-border-color</code> — Border color<br><code>--alert-border</code> — Full border shorthand<br><br><strong>Dimensions</strong><br><code>--alert-padding-x</code> / <code>--alert-padding-y</code> — Inner padding<br><code>--alert-margin-bottom</code> — Bottom margin<br><code>--alert-border-radius</code> — Border radius<br><br><em>Note: per-variant contextual tokens (colors, backgrounds, borders) are covered in the Bootstrap State Colors section above.</em>"
TPL_MOKOONYX_CSS_VARS_BADGE_LABEL="Badge"
TPL_MOKOONYX_CSS_VARS_BADGE_DESC="<strong>Colors</strong><br><code>--badge-color</code> — Badge text color<br><br><strong>Typography</strong><br><code>--badge-font-size</code> — Font size<br><code>--badge-font-weight</code> — Font weight<br><br><strong>Dimensions</strong><br><code>--badge-padding-x</code> / <code>--badge-padding-y</code> — Padding<br><code>--badge-border-radius</code> — Border radius"
TPL_MOKOONYX_CSS_VARS_BACKDROP_LABEL="Backdrop"
TPL_MOKOONYX_CSS_VARS_BACKDROP_DESC="<strong>Colors</strong><br><code>--backdrop-bg</code> — Backdrop color (default: <code>#000</code>)<br><code>--backdrop-opacity</code> — Backdrop opacity (default: <code>0.5</code>)<br><br><strong>Stacking</strong><br><code>--backdrop-zindex</code> — z-index (default: <code>1040</code>)"
TPL_MOKOONYX_CSS_VARS_BREADCRUMB_LABEL="Breadcrumb"
TPL_MOKOONYX_CSS_VARS_BREADCRUMB_DESC="<strong>Colors</strong><br><code>--breadcrumb-bg</code> — Background color<br><code>--breadcrumb-divider-color</code> — Divider color<br><code>--breadcrumb-item-active-color</code> — Active item color<br><br><strong>Dimensions</strong><br><code>--breadcrumb-padding-x</code> / <code>--breadcrumb-padding-y</code> — Container padding<br><code>--breadcrumb-margin-bottom</code> — Bottom margin<br><code>--breadcrumb-item-padding-x</code> — Spacing between items<br><code>--breadcrumb-border-radius</code> — Container border radius"
TPL_MOKOONYX_CSS_VARS_DROPDOWN_MENU_LABEL="Dropdown Menu"
TPL_MOKOONYX_CSS_VARS_DROPDOWN_MENU_DESC="<strong>Colors</strong><br><code>--dropdown-color</code> — Default text color<br><code>--dropdown-bg</code> — Menu background<br><code>--dropdown-border-color</code> — Border color<br><code>--dropdown-divider-bg</code> — Divider color<br><code>--dropdown-box-shadow</code> — Menu shadow<br><code>--dropdown-link-color</code> — Link text color<br><code>--dropdown-link-active-color</code> — Active link text color<br><code>--dropdown-link-active-bg</code> — Active link background<br><code>--dropdown-link-disabled-color</code> — Disabled link color<br><code>--dropdown-header-color</code> — Header text color<br><br><strong>Dimensions</strong><br><code>--dropdown-min-width</code> — Minimum width<br><code>--dropdown-padding-x</code> / <code>--dropdown-padding-y</code> — Menu padding<br><code>--dropdown-spacer</code> — Gap from toggle<br><code>--dropdown-border-width</code> — Border width<br><code>--dropdown-border-radius</code> — Outer border radius<br><code>--dropdown-inner-border-radius</code> — Inner border radius<br><code>--dropdown-divider-margin-y</code> — Divider vertical margin<br><code>--dropdown-item-padding-x</code> / <code>--dropdown-item-padding-y</code> — Item padding<br><code>--dropdown-header-padding-x</code> / <code>--dropdown-header-padding-y</code> — Header padding<br><br><strong>Typography</strong><br><code>--dropdown-font-size</code> — Menu font size<br><br><strong>Stacking</strong><br><code>--dropdown-zindex</code> — z-index"
TPL_MOKOONYX_CSS_VARS_LIST_GROUP_LABEL="List Group"
TPL_MOKOONYX_CSS_VARS_LIST_GROUP_DESC="<strong>Colors</strong><br><code>--list-group-color</code> — Default text color<br><code>--list-group-bg</code> — Default background<br><code>--list-group-border-color</code> — Border color<br><code>--list-group-action-color</code> — Clickable item text color<br><code>--list-group-action-active-color</code> — Clickable item active text color<br><code>--list-group-action-active-bg</code> — Clickable item active background<br><code>--list-group-disabled-color</code> — Disabled item text color<br><code>--list-group-disabled-bg</code> — Disabled item background<br><code>--list-group-active-color</code> — Active item text color<br><code>--list-group-active-bg</code> — Active item background<br><code>--list-group-active-border-color</code> — Active item border color<br><br><strong>Dimensions</strong><br><code>--list-group-border-width</code> — Border width<br><code>--list-group-border-radius</code> — Border radius<br><code>--list-group-item-padding-x</code> / <code>--list-group-item-padding-y</code> — Item padding"
TPL_MOKOONYX_CSS_VARS_MODAL_LABEL="Modal"
TPL_MOKOONYX_CSS_VARS_MODAL_DESC="<strong>Colors</strong><br><code>--modal-color</code> — Modal text color<br><code>--modal-bg</code> — Modal background<br><code>--modal-border-color</code> — Outer border color<br><code>--modal-box-shadow</code> — Modal shadow<br><code>--modal-header-border-color</code> — Header border color<br><code>--modal-footer-bg</code> — Footer background<br><code>--modal-footer-border-color</code> — Footer border color<br><br><strong>Dimensions</strong><br><code>--modal-width</code> — Default modal width<br><code>--modal-padding</code> — Body padding<br><code>--modal-margin</code> — Outer margin<br><code>--modal-border-width</code> — Outer border width<br><code>--modal-border-radius</code> — Outer border radius<br><code>--modal-inner-border-radius</code> — Inner border radius<br><code>--modal-header-padding-x</code> / <code>--modal-header-padding-y</code> — Header padding<br><code>--modal-header-padding</code> — Header padding shorthand<br><code>--modal-header-border-width</code> — Header border width<br><code>--modal-title-line-height</code> — Title line height<br><code>--modal-footer-gap</code> — Footer button gap<br><code>--modal-footer-border-width</code> — Footer border width<br><br><strong>Stacking</strong><br><code>--modal-zindex</code> — z-index"
TPL_MOKOONYX_CSS_VARS_NAV_TABS_LABEL="Nav Tabs"
TPL_MOKOONYX_CSS_VARS_NAV_TABS_DESC="<strong>Colors</strong><br><code>--nav-tabs-border-color</code> — Tab bar border color<br><code>--nav-tabs-link-active-color</code> — Active tab text color<br><code>--nav-tabs-link-active-bg</code> — Active tab background<br><code>--nav-tabs-link-active-border-color</code> — Active tab border color<br><br><strong>Dimensions</strong><br><code>--nav-tabs-border-width</code> — Border width<br><code>--nav-tabs-border-radius</code> — Tab border radius"
TPL_MOKOONYX_CSS_VARS_NAV_PILLS_LABEL="Nav Pills"
TPL_MOKOONYX_CSS_VARS_NAV_PILLS_DESC="<strong>Colors</strong><br><code>--nav-pills-link-active-color</code> — Active pill text color<br><code>--nav-pills-link-active-bg</code> — Active pill background<br><br><strong>Dimensions</strong><br><code>--nav-pills-border-radius</code> — Pill border radius"
TPL_MOKOONYX_CSS_VARS_PAGINATION_LABEL="Pagination"
TPL_MOKOONYX_CSS_VARS_PAGINATION_DESC="<strong>Colors</strong><br><code>--pagination-color</code> — Default link color<br><code>--pagination-bg</code> — Default background<br><code>--pagination-border-color</code> — Default border color<br><code>--pagination-focus-color</code> — Focused link color<br><code>--pagination-focus-bg</code> — Focused background<br><code>--pagination-focus-box-shadow</code> — Focus ring<br><code>--pagination-active-color</code> — Active page text color<br><code>--pagination-active-bg</code> — Active page background<br><code>--pagination-active-border-color</code> — Active page border color<br><code>--pagination-disabled-color</code> — Disabled link color<br><code>--pagination-disabled-bg</code> — Disabled background<br><code>--pagination-disabled-border-color</code> — Disabled border color<br><br><strong>Dimensions</strong><br><code>--pagination-padding-x</code> / <code>--pagination-padding-y</code> — Item padding<br><code>--pagination-border-width</code> — Border width<br><code>--pagination-border-radius</code> — Border radius<br><br><strong>Typography</strong><br><code>--pagination-font-size</code> — Font size"
TPL_MOKOONYX_CSS_VARS_POPOVER_LABEL="Popover"
TPL_MOKOONYX_CSS_VARS_POPOVER_DESC="<strong>Colors</strong><br><code>--popover-bg</code> — Popover background<br><code>--popover-border-color</code> — Border color<br><code>--popover-box-shadow</code> — Popover shadow<br><code>--popover-header-color</code> — Header text color<br><code>--popover-header-bg</code> — Header background<br><code>--popover-body-color</code> — Body text color<br><code>--popover-arrow-border</code> — Arrow border color<br><br><strong>Dimensions</strong><br><code>--popover-max-width</code> — Maximum width<br><code>--popover-border-width</code> — Border width<br><code>--popover-border-radius</code> — Outer border radius<br><code>--popover-inner-border-radius</code> — Inner border radius<br><code>--popover-header-padding-x</code> / <code>--popover-header-padding-y</code> — Header padding<br><code>--popover-body-padding-x</code> / <code>--popover-body-padding-y</code> — Body padding<br><code>--popover-arrow-width</code> / <code>--popover-arrow-height</code> — Arrow dimensions<br><br><strong>Typography</strong><br><code>--popover-font-size</code> — Font size<br><code>--popover-header-font-size</code> — Header font size<br><br><strong>Stacking</strong><br><code>--popover-zindex</code> — z-index"
TPL_MOKOONYX_CSS_VARS_PROGRESS_LABEL="Progress Bar"
TPL_MOKOONYX_CSS_VARS_PROGRESS_DESC="<strong>Colors</strong><br><code>--progress-bg</code> — Track background<br><code>--progress-bar-color</code> — Bar text color<br><code>--progress-bar-bg</code> — Bar fill color<br><code>--progress-box-shadow</code> — Track shadow<br><br><strong>Dimensions</strong><br><code>--progress-height</code> — Track height<br><code>--progress-border-radius</code> — Track border radius<br><br><strong>Typography</strong><br><code>--progress-font-size</code> — Label font size<br><br><strong>Animation</strong><br><code>--progress-bar-transition</code> — Bar width transition"
TPL_MOKOONYX_CSS_VARS_SPINNER_LABEL="Spinner"
TPL_MOKOONYX_CSS_VARS_SPINNER_DESC="<strong>Dimensions</strong><br><code>--spinner-width</code> — Spinner width<br><code>--spinner-height</code> — Spinner height<br><code>--spinner-vertical-align</code> — Inline vertical alignment<br><code>--spinner-border-width</code> — Border-style spinner track width<br><br><strong>Animation</strong><br><code>--spinner-animation-speed</code> — Rotation duration"
TPL_MOKOONYX_CSS_VARS_TABLE_LABEL="Table"
TPL_MOKOONYX_CSS_VARS_TABLE_DESC="<strong>Colors</strong><br><code>--table-color</code> — Default cell text color<br><code>--table-bg</code> — Default cell background<br><code>--table-border-color</code> — Border color<br><code>--table-accent-bg</code> — Accent row background (used by variants)<br><code>--table-striped-color</code> — Striped row text color<br><code>--table-striped-bg</code> — Striped row background<br><code>--table-active-color</code> — Hovered/active row text color<br><code>--table-active-bg</code> — Hovered/active row background"
TPL_MOKOONYX_CSS_VARS_TOAST_LABEL="Toast"
TPL_MOKOONYX_CSS_VARS_TOAST_DESC="<strong>Colors</strong><br><code>--toast-color</code> — Body text color<br><code>--toast-bg</code> — Body background<br><code>--toast-border-color</code> — Border color<br><code>--toast-box-shadow</code> — Toast shadow<br><code>--toast-header-color</code> — Header text color<br><code>--toast-header-bg</code> — Header background<br><code>--toast-header-border-color</code> — Header border color<br><br><strong>Dimensions</strong><br><code>--toast-max-width</code> — Maximum width<br><code>--toast-padding-x</code> / <code>--toast-padding-y</code> — Body padding<br><code>--toast-spacing</code> — Gap between stacked toasts<br><code>--toast-border-width</code> — Border width<br><code>--toast-border-radius</code> — Border radius<br><br><strong>Typography</strong><br><code>--toast-font-size</code> — Font size<br><br><strong>Stacking</strong><br><code>--toast-zindex</code> — z-index"
TPL_MOKOONYX_CSS_VARS_TOOLTIP_LABEL="Tooltip"
TPL_MOKOONYX_CSS_VARS_TOOLTIP_DESC="<strong>Colors</strong><br><code>--tooltip-color</code> — Text color<br><code>--tooltip-bg</code> — Background<br><code>--tooltip-opacity</code> — Overall opacity<br><br><strong>Dimensions</strong><br><code>--tooltip-max-width</code> — Maximum width<br><code>--tooltip-padding-x</code> / <code>--tooltip-padding-y</code> — Inner padding<br><code>--tooltip-margin</code> — Offset from target<br><code>--tooltip-border-radius</code> — Border radius<br><code>--tooltip-arrow-width</code> / <code>--tooltip-arrow-height</code> — Arrow dimensions<br><br><strong>Typography</strong><br><code>--tooltip-font-size</code> — Font size<br><br><strong>Stacking</strong><br><code>--tooltip-zindex</code> — z-index"
TPL_MOKOONYX_CSS_VARS_COMPONENTS_LABEL="Component &amp; Plugin Colors"
TPL_MOKOONYX_CSS_VARS_COMPONENTS_DESC="<strong>Misc components</strong><br><code>--mod-finder-link-hover</code> — Smart Search hover background<br><code>--form-legend-color</code> — Form legend text color<br><code>--border-gray</code> — General gray border<br><code>--subhead-color</code> — Subheading color<br><code>--item-list-color</code> — Item list background<br><code>--notification-badge-bg</code> — Notification badge<br><br><strong>Table of Contents (TOC)</strong><br><code>--toc-link-color</code> — TOC link color<br><code>--toc-link-active-color</code> — Active TOC link<br><br><strong>Choices.js select</strong><br><code>--choices-inner-bg</code> — Inner background<br><code>--choices-dropdown-bg</code> — Dropdown background<br><code>--choices-item-bg</code> — Tag item background<br><code>--choices-item-hover-bg</code> — Tag hover<br><code>--choices-disabled-bg</code> — Disabled state<br><code>--choices-focused-border</code> — Focused border<br><br><strong>Tab buttons (taba)</strong><br><code>--taba-btn-green</code>, <code>--taba-btn-blue</code>, <code>--taba-btn-red</code>, <code>--taba-btn-gray</code>"
TPL_MOKOONYX_CSS_VARS_OFFCANVAS_LABEL="Offcanvas Panel"
TPL_MOKOONYX_CSS_VARS_OFFCANVAS_DESC="<strong>Dimensions</strong><br><code>--offcanvas-width</code> — Panel width (default: <code>400px</code>)<br><code>--offcanvas-height</code> — Panel height for top/bottom variants (default: <code>30vh</code>)<br><code>--offcanvas-padding-x</code> / <code>--offcanvas-padding-y</code> — Inner padding<br><br><strong>Colors</strong><br><code>--offcanvas-bg</code> — Panel background (default: <code>var(--body-bg)</code>)<br><code>--offcanvas-color</code> — Panel text color (default: <code>var(--body-color)</code>)<br><code>--offcanvas-border-width</code> / <code>--offcanvas-border-color</code> — Panel border<br><code>--offcanvas-box-shadow</code> — Drop shadow<br><br><strong>Stacking</strong><br><code>--offcanvas-zindex</code> — z-index (default: <code>1045</code>)"
TPL_MOKOONYX_CSS_VARS_VM_LABEL="VirtueMart"
TPL_MOKOONYX_CSS_VARS_VM_DESC="<strong>Surfaces &amp; text</strong><br><code>--vm-surface</code> / <code>--vm-surface-2</code> — Card/panel backgrounds<br><code>--vm-text</code> / <code>--vm-text-strong</code> / <code>--vm-text-muted</code> — Text variants<br><code>--vm-border</code> — Border color<br><code>--vm-price-color</code> — Price text color<br><br><strong>Layout &amp; density</strong><br><code>--vm-container-max-width</code> — Max content width (default: <code>1200px</code>)<br><code>--vm-section-gap</code> — Section spacing (default: <code>2rem</code>)<br><code>--vm-block-radius</code> / <code>--vm-block-shadow</code> — Block appearance<br><br><strong>Typography</strong><br><code>--vm-category-title-size</code> — Category heading size<br><code>--vm-product-title-size</code> — Product title size<br><code>--vm-price-size</code> — Price size<br><br><strong>Buttons</strong><br><code>--vm-btn-primary-bg</code> / <code>--vm-btn-primary-text</code> — Primary button<br><code>--vm-btn-secondary-bg</code> / <code>--vm-btn-secondary-text</code> — Secondary button<br><br><strong>Image overlay controls</strong><br><code>--vm-image-overlay-btn-bg</code> / <code>--vm-image-overlay-btn-color</code> — Overlay button appearance"
TPL_MOKOONYX_CSS_VARS_GABLE_LABEL="Gable"
TPL_MOKOONYX_CSS_VARS_GABLE_DESC="Color tokens used by the Gable extension.<br><code>--gab-blue</code> — <code>#0066cc</code><br><code>--gab-green</code> — <code>#28a745</code><br><code>--gab-red</code> — <code>#dc3545</code><br><code>--gab-orange</code> — <code>#fd7e14</code><br><code>--gab-gray1</code> — <code>#495057</code><br><code>--gab-gray2</code> — <code>#6c757d</code><br><code>--gab-gray3</code> — <code>#adb5bd</code>"
TPL_MOKOONYX_CSS_VARS_FOOTER_LABEL="Footer"
TPL_MOKOONYX_CSS_VARS_FOOTER_DESC="<strong>Spacing</strong><br><code>--footer-padding-top</code> — Top padding (default: <code>1rem</code>)<br><code>--footer-padding-bottom</code> — Bottom padding (default: <code>80px</code>)<br><code>--footer-grid-padding-y</code> — Grid vertical padding (default: <code>2.5rem</code>)<br><code>--footer-grid-padding-x</code> — Grid horizontal padding (default: <code>0.5em</code>)"
; ===== Theme Preview tab =====
TPL_MOKOONYX_THEME_PREVIEW_FIELDSET_LABEL="Theme Preview"
TPL_MOKOONYX_THEME_PREVIEW_INTRO="<p>Live preview of all CSS variables, hero variants, block colors, and Bootstrap components rendered with your active theme. Use the <strong>Toggle Light / Dark</strong> button inside the preview to switch modes. This page is also available as a standalone file at <code>templates/mokoonyx/templates/theme-test.html</code>.</p>"
TPL_MOKOONYX_THEME_PREVIEW_FRAME="<iframe src='../templates/mokoonyx/templates/theme-test.html' style='width:100%;height:80vh;border:1px solid #dee2e6;border-radius:.375rem;' loading='lazy' title='Theme test sheet preview'></iframe>"
; ===== Misc =====
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
JGLOBAL_OFFLINE="Offline"

View File

@@ -0,0 +1,31 @@
; 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
;
TPL_MOKOONYX="MokoOnyx Site template"
TPL_MOKOONYX_GOOGLE_FIELDSET_LABEL="Google"
TPL_MOKOONYX_DRAWERS_FIELDSET_LABEL="Drawers"
TPL_MOKOONYX_MOD_MENU_LAYOUT_COLLAPSE_METISMENU="Collapsible Dropdown"
TPL_MOKOONYX_MOD_MENU_LAYOUT_DROPDOWN_METISMENU="Dropdown"
TPL_MOKOONYX_POSITION_BANNER="Banner"
TPL_MOKOONYX_POSITION_BELOW_TOPBAR="Below Topbar"
TPL_MOKOONYX_POSITION_BRAND_ASIDE="Brand Aside"
TPL_MOKOONYX_POSITION_BOTTOM_A="Bottom-A"
TPL_MOKOONYX_POSITION_BOTTOM_B="Bottom-B"
TPL_MOKOONYX_POSITION_BREADCRUMBS="Breadcrumbs"
TPL_MOKOONYX_POSITION_DEBUG="Debug"
TPL_MOKOONYX_POSITION_FOOTER="Footer"
TPL_MOKOONYX_POSITION_MAIN_BOTTOM="Main-bottom"
TPL_MOKOONYX_POSITION_MAIN_TOP="Main-top"
TPL_MOKOONYX_POSITION_MENU="Menu"
TPL_MOKOONYX_POSITION_SEARCH="Search"
TPL_MOKOONYX_POSITION_SIDEBAR_LEFT="Sidebar-left"
TPL_MOKOONYX_POSITION_SIDEBAR_RIGHT="Sidebar-right"
TPL_MOKOONYX_POSITION_TOP_A="Top-a"
TPL_MOKOONYX_POSITION_TOP_B="Top-b"
TPL_MOKOONYX_POSITION_TOPBAR="Top Bar"
TPL_MOKOONYX_POSITION_DRAWER_LEFT="Drawer-Left"
TPL_MOKOONYX_POSITION_DRAWER_RIGHT="Drawer-Right"
TPL_MOKOONYX_XML_DESCRIPTION="<h3>MokoOnyx Template Description</h3> <p> <strong>MokoOnyx</strong> continues Joomlas tradition of space-themed default templates— building on the legacy of <em>Solarflare</em> (Joomla 1.0), <em>Milkyway</em> (Joomla 1.5), and <em>Protostar</em> (Joomla 3.0). </p> <p> This template is a customized fork of the <strong>Cassiopeia</strong> 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. </p> <h4>Custom Color Themes</h4> <p> Starter palette files are included with the template. To create a custom color scheme, copy <code>templates/mokoonyx/templates/light.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/light.custom.css</code>, or <code>templates/mokoonyx/templates/dark.custom.css</code> to <code>media/templates/site/mokoonyx/css/theme/dark.custom.css</code>. Customize the CSS variables to match your brand, then activate your palette in <em>System → Site Templates → MokoOnyx → Theme tab</em> by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the <em>CSS Variables</em> tab in template options. </p> <h4>Custom CSS &amp; JavaScript</h4> <p> For site-specific styles and scripts that should survive template updates, create the following files: </p> <ul> <li><code>media/templates/site/mokoonyx/css/user.css</code> — loaded on every page for custom CSS overrides.</li> <li><code>media/templates/site/mokoonyx/js/user.js</code> — loaded on every page for custom JavaScript.</li> </ul> <p> These files are gitignored and will not be overwritten by template updates. </p> <h4>Code Attribution</h4> <p> This template is based on the original <strong>Cassiopeia</strong> template developed by the <a href=\"https://www.joomla.org\" target=\"_blank\" rel=\"noopener\">Joomla! Project</a> and released under the GNU General Public License. </p> <p> Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards. </p> <p> It includes integration with <a href=\"https://afeld.github.io/bootstrap-toc/\" target=\"_blank\" rel=\"noopener\">Bootstrap TOC</a>, an open-source table of contents generator by A. Feld, licensed under the MIT License. </p> <p> All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable. </p>"

76
src/language/index.html Normal file
View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,227 @@
@charset "UTF-8";
/* Copyright (C) 2026 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: MokoOnyx.Accessibility
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
* PATH: ./media/css/a11y-high-contrast.css
* VERSION: 03.09.14
* BRIEF: High-contrast stylesheet for accessibility toolbar
*/
/* ===================================================================
* HIGH CONTRAST MODE
* Applied when .a11y-high-contrast is on <html>.
* Overrides theme variables to maximise contrast ratios (WCAG AAA).
* =================================================================== */
/* ── Light mode high contrast ─────────────────────────────────────── */
:root[data-bs-theme="light"].a11y-high-contrast {
--body-color: #000;
--body-color-rgb: 0, 0, 0;
--body-bg: #fff;
--body-bg-rgb: 255, 255, 255;
--heading-color: #000;
--emphasis-color: #000;
--secondary-color: #000000bf;
--tertiary-color: #00000080;
--muted-color: #333;
/* Links — strong blue on white */
--color-link: #0000ee;
--link-color: #0000ee;
--link-color-rgb: 0, 0, 238;
--color-hover: #cc0000;
--link-hover-color: #cc0000;
--link-hover-color-rgb: 204, 0, 0;
/* Borders — visible on all backgrounds */
--border-color: #000;
--border-color-soft: #333;
/* Backgrounds */
--secondary-bg: #e0e0e0;
--secondary-bg-rgb: 224, 224, 224;
--tertiary-bg: #f0f0f0;
--tertiary-bg-rgb: 240, 240, 240;
/* Navigation */
--nav-bg-color: #000;
--nav-text-color: #fff;
--mainmenu-nav-link-color: #fff;
/* Buttons */
--btn-color: #fff;
--btn-bg: #000;
--btn-border-color: #000;
--btn-hover-color: #000;
--btn-hover-bg: #ffff00;
--btn-hover-border-color: #000;
--btn-active-color: #000;
--btn-active-bg: #ffff00;
--btn-active-border-color: #000;
/* Forms */
--input-color: #000;
--input-bg: #fff;
--input-border-color: #000;
--input-focus-color: #000;
--input-focus-bg: #ffffcc;
--input-focus-border-color: #0000ee;
--input-placeholder-color: #555;
/* Cards */
--card-border-color: #000;
--card-bg: #fff;
--card-cap-bg: #e0e0e0;
/* Tables */
--table-color: #000;
--table-bg: #fff;
--table-border-color: #000;
--table-striped-bg: #f0f0f0;
--table-hover-bg: #ffff99;
/* Alerts */
--alert-border-width: 2px;
/* Code */
--code-color: #000;
--code-bg-color: #ffffcc;
/* Selection */
--selection-bg: #0000ee;
--selection-ink: #fff;
/* Focus indicator — always visible */
--focus-ring-color: #0000ee;
--focus-ring-width: 3px;
}
/* ── Dark mode high contrast ──────────────────────────────────────── */
:root[data-bs-theme="dark"].a11y-high-contrast {
--body-color: #fff;
--body-color-rgb: 255, 255, 255;
--body-bg: #000;
--body-bg-rgb: 0, 0, 0;
--heading-color: #fff;
--emphasis-color: #fff;
--secondary-color: #ffffffbf;
--tertiary-color: #ffffff80;
--muted-color: #ccc;
/* Links — bright yellow on black */
--color-link: #ffff00;
--link-color: #ffff00;
--link-color-rgb: 255, 255, 0;
--color-hover: #00ffff;
--link-hover-color: #00ffff;
--link-hover-color-rgb: 0, 255, 255;
/* Borders */
--border-color: #fff;
--border-color-soft: #ccc;
/* Backgrounds */
--secondary-bg: #1a1a1a;
--secondary-bg-rgb: 26, 26, 26;
--tertiary-bg: #111;
--tertiary-bg-rgb: 17, 17, 17;
/* Navigation */
--nav-bg-color: #000;
--nav-text-color: #fff;
--mainmenu-nav-link-color: #ffff00;
/* Buttons */
--btn-color: #000;
--btn-bg: #ffff00;
--btn-border-color: #ffff00;
--btn-hover-color: #000;
--btn-hover-bg: #00ffff;
--btn-hover-border-color: #00ffff;
--btn-active-color: #000;
--btn-active-bg: #00ffff;
--btn-active-border-color: #00ffff;
/* Forms */
--input-color: #fff;
--input-bg: #000;
--input-border-color: #fff;
--input-focus-color: #fff;
--input-focus-bg: #1a1a1a;
--input-focus-border-color: #ffff00;
--input-placeholder-color: #aaa;
/* Cards */
--card-border-color: #fff;
--card-bg: #000;
--card-cap-bg: #1a1a1a;
/* Tables */
--table-color: #fff;
--table-bg: #000;
--table-border-color: #fff;
--table-striped-bg: #111;
--table-hover-bg: #333;
/* Alerts */
--alert-border-width: 2px;
/* Code */
--code-color: #00ff00;
--code-bg-color: #1a1a1a;
/* Selection */
--selection-bg: #ffff00;
--selection-ink: #000;
/* Focus indicator */
--focus-ring-color: #ffff00;
--focus-ring-width: 3px;
}
/* ── Shared high-contrast overrides (both modes) ──────────────────── */
.a11y-high-contrast * {
border-color: var(--border-color) !important;
}
.a11y-high-contrast *:focus-visible {
outline: var(--focus-ring-width, 3px) solid var(--focus-ring-color, #0000ee) !important;
outline-offset: 2px !important;
}
.a11y-high-contrast img {
outline: 2px solid var(--border-color);
}
.a11y-high-contrast a {
text-decoration: underline !important;
text-decoration-thickness: 2px !important;
}
.a11y-high-contrast button,
.a11y-high-contrast .btn,
.a11y-high-contrast input,
.a11y-high-contrast select,
.a11y-high-contrast textarea {
border-width: 2px !important;
border-style: solid !important;
}
.a11y-high-contrast .badge,
.a11y-high-contrast .alert {
border: 2px solid var(--border-color) !important;
}
/* Ensure disabled states are still distinguishable */
.a11y-high-contrast [disabled],
.a11y-high-contrast .disabled {
opacity: .5 !important;
text-decoration: line-through !important;
}

79
src/media/css/editor.css Normal file
View File

@@ -0,0 +1,79 @@
@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
*/
/* STYLES FOR JOOMLA! EDITOR */
body {
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #22262a;
background-color: #fff;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 700;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
a {
text-decoration: none;
}
a:link {
color: #224faa;
}
a:hover {
color: #424077;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
/* STYLES FOR JOOMLA! EDITOR */
hr#system-readmore {
color: #f00;
border: #f00 dashed 1px;
}
span[lang] {
padding: 2px;
border: 1px dashed #bbb;
}
span[lang]:after {
font-size: smaller;
color: #f00;
vertical-align: super;
content: attr(lang);
}

1
src/media/css/editor.min.css vendored Normal file
View File

@@ -0,0 +1 @@
@charset "UTF-8";body{font-size:1rem;font-weight:400;line-height:1.5;color:#22262a;background-color:#fff}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:700;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}h2{font-size:calc(1.325rem + .9vw)}h3{font-size:calc(1.3rem + .6vw)}h4{font-size:calc(1.275rem + .3vw)}h5{font-size:1.25rem}h6{font-size:1rem}a{text-decoration:none}a:link{color:#224faa}a:hover{color:#424077}p{margin-top:0;margin-bottom:1rem}hr#system-readmore{color:red;border:1px dashed red}span[lang]{padding:2px;border:1px dashed #bbb}span[lang]:after{font-size:smaller;color:red;vertical-align:super;content:attr(lang)}

View File

@@ -0,0 +1,10 @@
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Fredoka — self-hosted from src/media/fonts/
*/
@font-face { font-family: 'Fredoka'; font-style: normal; font-weight: 300; font-display: swap; src: url('../../fonts/fredoka-v17-latin-300.woff2') format('woff2'); }
@font-face { font-family: 'Fredoka'; font-style: normal; font-weight: 400; font-display: swap; src: url('../../fonts/fredoka-v17-latin-regular.woff2') format('woff2'); }
@font-face { font-family: 'Fredoka'; font-style: normal; font-weight: 500; font-display: swap; src: url('../../fonts/fredoka-v17-latin-500.woff2') format('woff2'); }
@font-face { font-family: 'Fredoka'; font-style: normal; font-weight: 600; font-display: swap; src: url('../../fonts/fredoka-v17-latin-600.woff2') format('woff2'); }
@font-face { font-family: 'Fredoka'; font-style: normal; font-weight: 700; font-display: swap; src: url('../../fonts/fredoka-v17-latin-700.woff2') format('woff2'); }

View File

@@ -0,0 +1,14 @@
/* 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
*/
@font-face {
font-family: 'Osaka';
src: url('../../fonts/osaka-re.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}

View File

@@ -0,0 +1,6 @@
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Pacifico — self-hosted from src/media/fonts/
*/
@font-face { font-family: 'Pacifico'; font-style: normal; font-weight: 400; font-display: swap; src: url('../../fonts/pacifico-v23-latin-regular.woff2') format('woff2'); }

View File

@@ -0,0 +1,23 @@
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Roboto — self-hosted from src/media/fonts/
*/
@font-face { font-family: 'Roboto'; font-style: normal; font-weight: 100; font-display: swap; src: url('../../fonts/roboto-v51-latin-100.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: italic; font-weight: 100; font-display: swap; src: url('../../fonts/roboto-v51-latin-100italic.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: normal; font-weight: 200; font-display: swap; src: url('../../fonts/roboto-v51-latin-200.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: italic; font-weight: 200; font-display: swap; src: url('../../fonts/roboto-v51-latin-200italic.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: normal; font-weight: 300; font-display: swap; src: url('../../fonts/roboto-v51-latin-300.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: italic; font-weight: 300; font-display: swap; src: url('../../fonts/roboto-v51-latin-300italic.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; font-display: swap; src: url('../../fonts/roboto-v51-latin-regular.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: italic; font-weight: 400; font-display: swap; src: url('../../fonts/roboto-v51-latin-italic.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: normal; font-weight: 500; font-display: swap; src: url('../../fonts/roboto-v51-latin-500.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: italic; font-weight: 500; font-display: swap; src: url('../../fonts/roboto-v51-latin-500italic.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: normal; font-weight: 600; font-display: swap; src: url('../../fonts/roboto-v51-latin-600.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: italic; font-weight: 600; font-display: swap; src: url('../../fonts/roboto-v51-latin-600italic.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: normal; font-weight: 700; font-display: swap; src: url('../../fonts/roboto-v51-latin-700.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: italic; font-weight: 700; font-display: swap; src: url('../../fonts/roboto-v51-latin-700italic.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: normal; font-weight: 800; font-display: swap; src: url('../../fonts/roboto-v51-latin-800.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: italic; font-weight: 800; font-display: swap; src: url('../../fonts/roboto-v51-latin-800italic.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: normal; font-weight: 900; font-display: swap; src: url('../../fonts/roboto-v51-latin-900.woff2') format('woff2'); }
@font-face { font-family: 'Roboto'; font-style: italic; font-weight: 900; font-display: swap; src: url('../../fonts/roboto-v51-latin-900italic.woff2') format('woff2'); }

76
src/media/css/index.html Normal file
View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

258
src/media/css/offline.css Normal file
View File

@@ -0,0 +1,258 @@
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later
*/
/* === Offline Page — Full-viewport background with centered overlay card === */
.moko-offline-wrap {
min-height: 100vh;
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: offline_image set inline, or fall back to header background */
background-color: var(--color-primary, #112855);
background-image: var(--header-background-image, none);
background-position: var(--header-background-position, center);
background-attachment: var(--header-background-attachment, fixed);
background-repeat: no-repeat;
background-size: cover;
}
/* Dark theme: overlay to darken the background */
:root[data-bs-theme="dark"] .moko-offline-wrap {
position: relative;
}
:root[data-bs-theme="dark"] .moko-offline-wrap::before {
content: "";
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 0;
}
/* === Centered Card Overlay === */
.moko-offline-card {
width: 100%;
max-width: 720px;
background: var(--offline-card-bg, 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) {
.moko-offline-card {
padding: 3rem;
}
}
@media (max-width: 575.98px) {
.moko-offline-wrap {
padding: 1rem 0.75rem;
}
.moko-offline-card {
padding: 2rem 1.25rem;
}
}
/* === Logo header area === */
.moko-offline-brand {
display: block;
text-align: center;
text-decoration: none;
color: #fff;
margin-bottom: 1.5rem;
}
.moko-offline-brand:hover {
color: var(--accent-color-primary, #3f8ff0);
}
.moko-offline-brand img {
max-width: 100%;
height: auto;
}
.moko-offline-brand .site-title {
display: block;
font-size: 2rem;
font-weight: 700;
font-family: 'Osaka', var(--body-font-family, sans-serif);
color: var(--accent-color-secondary, #6fb3ff);
}
.moko-offline-brand .brand-tagline {
display: block;
opacity: 0.7;
font-size: 0.9rem;
margin-top: 0.25rem;
}
/* === Offline Message === */
.moko-offline-message {
margin-bottom: 1.5rem;
}
.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-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;
}
.moko-offline-card .accordion-item {
background: transparent;
border-color: rgba(255, 255, 255, 0.15);
}
.moko-offline-card .accordion-button {
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: 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: transparent;
padding: 1rem;
}
/* === Form Controls (glass effect) === */
.moko-offline-card .form-control {
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 {
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: rgba(255, 255, 255, 0.8);
font-size: 0.875rem;
}
.moko-offline-card .form-check-label {
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: rgba(255, 255, 255, 0.15);
color: #fff;
}
.moko-offline-card .btn-primary:hover {
background-color: var(--accent-color-primary, #3f8ff0);
border-color: var(--accent-color-primary, #3f8ff0);
}
/* === Links === */
.moko-offline-card a {
color: var(--accent-color-primary, #3f8ff0);
}
.moko-offline-card a:hover {
color: #fff;
}
/* === Joomla system messages === */
.moko-offline-messages {
width: 100%;
max-width: 720px;
margin-bottom: 1rem;
}
/* === 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;
}

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

View File

@@ -0,0 +1,57 @@
@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
*/
.js-stools-container-bar {
padding: 10px 20px;
}
.js-stools-container-bar .btn-toolbar {
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
}
.js-stools-container-bar .btn-toolbar > * {
margin: 4px 0;
-webkit-margin-end: 8px;
margin-inline-end: 8px;
}
.js-stools-container-bar .btn-toolbar .js-stools-btn-clear {
background-color: hsl(207, 49%, 37%);
border: 0;
}
.js-stools-container-bar .ordering-select {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.js-stools-container-filters {
display: none;
padding: 0 20px;
margin-bottom: 20px;
}
.js-stools-container-filters-visible {
display: grid;
grid-gap: 8px;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
padding: 10px;
background-color: hsl(0, 0%, 100%);
}
.js-stools-container-filters > * {
margin: 4px 0;
-webkit-margin-end: 8px;
margin-inline-end: 8px;
}
.js-stools-field-list + .js-stools-field-list {
-webkit-margin-start: 8px;
margin-inline-start: 8px;
}
.js-stools-field-selector .form-select {
width: auto;
}

File diff suppressed because it is too large Load Diff

23449
src/media/css/template.css Normal file

File diff suppressed because it is too large Load Diff

1
src/media/css/template.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,76 @@
<!-- 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
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Redirecting…</title>
<!-- Search engines: do not index this placeholder redirect page -->
<meta name="robots" content="noindex, nofollow, noarchive" />
<!-- Instant redirect fallback even if JavaScript is disabled -->
<meta http-equiv="refresh" content="0; url=/" />
<!-- Canonical root reference -->
<link rel="canonical" href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function redirectToRoot() {
// Configuration object with safe defaults.
var opts = {
fallbackPath: "/", // string: fallback destination if origin is unavailable
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
behavior: "replace" // enum: "replace" | "assign"
};
// Determine absolute origin in all mainstream browsers.
var origin = (typeof location.origin === "string" && location.origin)
|| (location.protocol + "//" + location.host);
// Final destination: absolute root of the current site, or fallback path.
var destination = origin ? origin + "/" : opts.fallbackPath;
function go() {
if (opts.behavior === "assign") {
location.assign(destination);
} else {
location.replace(destination);
}
}
// Execute redirect, optionally after a short delay.
if (opts.delayMs > 0) {
setTimeout(go, opts.delayMs);
} else {
go();
}
})();
</script>
<!--
Secondary meta-refresh for no-JS environments is already set above.
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
-->
<noscript>
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
<style>
html, body { height:100%; }
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
.msg { opacity: .75; text-align: center; }
</style>
</noscript>
</head>
<body>
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,31 @@
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
VERSION: 03.09.10
-->
# Self-Hosted Google Fonts
Fonts are served locally to avoid external CDN dependencies and improve privacy/performance.
## Available Fonts
| Font | Weights | Styles | CSS File |
|------|---------|--------|----------|
| Roboto | 100900 | Normal + Italic | `css/fonts/roboto.css` |
| Fredoka | 300700 | Normal | `css/fonts/fredoka.css` |
| Pacifico | 400 | Normal | `css/fonts/pacifico.css` |
| Osaka | — | — | `css/fonts/osaka.css` |
## Adding New Fonts
1. Run `php scripts/download-google-fonts.php` or manually download woff2 files
2. Place files in `src/media/fonts/`
3. Create a CSS file in `src/media/css/fonts/` with `@font-face` declarations
4. Register the CSS in `joomla.asset.json`
5. Add the font as an option in `templateDetails.xml` font selector
## File Naming Convention
`{font-name}-v{version}-latin-{weight}.woff2`
Examples: `roboto-v51-latin-regular.woff2`, `fredoka-v17-latin-700.woff2`

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More