Merge branch 'version/03'

This commit is contained in:
2026-04-08 05:22:08 -05:00
22 changed files with 2712 additions and 367 deletions

View File

@@ -19,9 +19,7 @@ on:
push: push:
branches: branches:
- main - main
pull_request: - version/*
branches:
- main
workflow_dispatch: workflow_dispatch:
permissions: permissions:

View File

@@ -19,14 +19,7 @@ on:
push: push:
branches: branches:
- main - main
- dev/** - version/*
- rc/**
- version/**
pull_request:
branches:
- main
- dev/**
- rc/**
workflow_dispatch: workflow_dispatch:
permissions: permissions:

View File

@@ -21,14 +21,7 @@ on:
push: push:
branches: branches:
- main - main
- dev/** - version/*
- rc/**
- version/**
pull_request:
branches:
- main
- dev/**
- rc/**
schedule: schedule:
# Weekly on Monday at 06:00 UTC # Weekly on Monday at 06:00 UTC
- cron: '0 6 * * 1' - cron: '0 6 * * 1'

View File

@@ -89,9 +89,7 @@ env:
on: on:
push: push:
branches: [main, dev/**, rc/**, version/**] branches: [main, version/*]
pull_request:
branches: [main, dev/**, rc/**]
workflow_dispatch: workflow_dispatch:
permissions: permissions:

View File

@@ -22,10 +22,8 @@ name: Update Joomla Update Server XML Feed
on: on:
push: push:
branches: branches:
- 'dev/**' - main
- 'alpha/**' - version/*
- 'beta/**'
- 'rc/**'
paths: paths:
- 'src/**' - 'src/**'
- 'htdocs/**' - 'htdocs/**'

View File

@@ -10,49 +10,20 @@
/** /**
* Default layout override for mod_breadcrumbs. * Default layout override for mod_breadcrumbs.
* Bootstrap 5 breadcrumb with schema.org BreadcrumbList markup. * Bootstrap 5 breadcrumb with schema.org BreadcrumbList markup.
* Respects showHome, showLast, homeText module settings. * Module settings (showHome, showLast, homeText) are handled by Joomla core
* before $list reaches this template.
*/ */
defined('_JEXEC') or die; defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text; use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Uri\Uri;
Factory::getApplication()->getLanguage()->load('mod_breadcrumbs', JPATH_SITE);
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8'); $suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), 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'); $headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
$showHome = $params->get('showHome', 1); $showHere = $params->get('showHere', 1);
$showLast = $params->get('showLast', 1);
$homeText = $params->get('homeText', '') ?: Text::_('MOD_BREADCRUMBS_HOME');
// Build filtered list respecting module settings if (empty($list)) {
$items = [];
$count = count($list);
foreach ($list as $key => $item) {
// Skip Home item if showHome is off
if ($key === 0 && !$showHome) {
continue;
}
// Replace Home text if custom homeText is set
if ($key === 0 && $showHome) {
$item->name = $homeText;
}
// Skip last item if showLast is off
if ($key === $count - 1 && !$showLast) {
continue;
}
$items[] = $item;
}
if (empty($items)) {
return; return;
} }
?> ?>
@@ -60,9 +31,12 @@ if (empty($items)) {
<?php if ($module->showtitle) : ?> <?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-breadcrumbs__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>> <<?php echo $headerTag; ?> class="mod-breadcrumbs__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?> <?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"> <ol class="breadcrumb" itemscope itemtype="https://schema.org/BreadcrumbList">
<?php foreach ($items as $key => $item) : ?> <?php foreach ($list as $key => $item) : ?>
<?php $isLast = ($key === array_key_last($items)); ?> <?php $isLast = ($key === array_key_last($list)); ?>
<li class="breadcrumb-item<?php echo $isLast ? ' active' : ''; ?>" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem" <li class="breadcrumb-item<?php echo $isLast ? ' active' : ''; ?>" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"
<?php echo $isLast ? ' aria-current="page"' : ''; ?>> <?php echo $isLast ? ' aria-current="page"' : ''; ?>>
<?php if (!$isLast && !empty($item->link)) : ?> <?php if (!$isLast && !empty($item->link)) : ?>

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

@@ -30,12 +30,12 @@ $moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COM
<nav class="mod-menu mod-menu-main navbar navbar-expand-lg<?php echo $moduleclass_sfx; ?>"<?php echo $id; ?>> <nav class="mod-menu mod-menu-main navbar navbar-expand-lg<?php echo $moduleclass_sfx; ?>"<?php echo $id; ?>>
<div class="container-fluid"> <div class="container-fluid">
<!-- Hamburger toggle button for mobile --> <!-- Hamburger toggle button for mobile -->
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainMenuCollapse" aria-controls="mainMenuCollapse" aria-expanded="false" aria-label="Toggle Main Menu"> <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> <span class="fa-solid fa-bars" aria-hidden="true"></span>
</button> </button>
<!-- Collapsible menu content --> <!-- Collapsible menu content -->
<div class="collapse navbar-collapse" id="mainMenuCollapse"> <div class="collapse navbar-collapse" id="mainMenuCollapse-<?php echo $module->id; ?>">
<ul class="navbar-nav mod-menu-main__list"> <ul class="navbar-nav mod-menu-main__list">
<?php foreach ($list as $i => &$item) : <?php foreach ($list as $i => &$item) :
$itemParams = $item->getParams(); $itemParams = $item->getParams();

View File

@@ -1,56 +0,0 @@
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later
*/
/*
* IMPORTANT: Font files must be downloaded separately
*
* This CSS file references Fira Sans font files that must be manually downloaded
* and placed in the fonts directory. See GOOGLE_FONTS_README.md in the fonts
* directory for download instructions.
*
* Required files:
* - fira-sans-v17-latin-100.woff2
* - fira-sans-v17-latin-300.woff2
* - fira-sans-v17-latin-regular.woff2
* - fira-sans-v17-latin-700.woff2
*/
/* Fira Sans Thin (100) */
@font-face {
font-family: 'Fira Sans';
font-style: normal;
font-weight: 100;
font-display: swap;
src: url('../../fonts/fira-sans-v17-latin-100.woff2') format('woff2');
}
/* Fira Sans Light (300) */
@font-face {
font-family: 'Fira Sans';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url('../../fonts/fira-sans-v17-latin-300.woff2') format('woff2');
}
/* Fira Sans Regular (400) */
@font-face {
font-family: 'Fira Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('../../fonts/fira-sans-v17-latin-regular.woff2') format('woff2');
}
/* Fira Sans Bold (700) */
@font-face {
font-family: 'Fira Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('../../fonts/fira-sans-v17-latin-700.woff2') format('woff2');
}

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

@@ -1,56 +0,0 @@
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later
*/
/*
* IMPORTANT: Font files must be downloaded separately
*
* This CSS file references Noto Sans font files that must be manually downloaded
* and placed in the fonts directory. See GOOGLE_FONTS_README.md in the fonts
* directory for download instructions.
*
* Required files:
* - noto-sans-v36-latin-100.woff2
* - noto-sans-v36-latin-300.woff2
* - noto-sans-v36-latin-regular.woff2
* - noto-sans-v36-latin-700.woff2
*/
/* Noto Sans Thin (100) */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 100;
font-display: swap;
src: url('../../fonts/noto-sans-v36-latin-100.woff2') format('woff2');
}
/* Noto Sans Light (300) */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url('../../fonts/noto-sans-v36-latin-300.woff2') format('woff2');
}
/* Noto Sans Regular (400) */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('../../fonts/noto-sans-v36-latin-regular.woff2') format('woff2');
}
/* Noto Sans Bold (700) */
@font-face {
font-family: 'Noto Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('../../fonts/noto-sans-v36-latin-700.woff2') format('woff2');
}

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

@@ -1,56 +1,23 @@
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech> /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
This file is part of a Moko Consulting project. * Roboto — self-hosted from src/media/fonts/
SPDX-License-Identifier: GPL-3.0-or-later
*/ */
/* @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 100; font-display: swap; src: url('../../fonts/roboto-v51-latin-100.woff2') format('woff2'); }
* IMPORTANT: Font files must be downloaded separately @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'); }
* This CSS file references Roboto font files that must be manually downloaded @font-face { font-family: 'Roboto'; font-style: italic; font-weight: 200; font-display: swap; src: url('../../fonts/roboto-v51-latin-200italic.woff2') format('woff2'); }
* and placed in the fonts directory. See GOOGLE_FONTS_README.md in the fonts @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 300; font-display: swap; src: url('../../fonts/roboto-v51-latin-300.woff2') format('woff2'); }
* directory for download instructions. @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'); }
* Required files: @font-face { font-family: 'Roboto'; font-style: italic; font-weight: 400; font-display: swap; src: url('../../fonts/roboto-v51-latin-italic.woff2') format('woff2'); }
* - roboto-v30-latin-100.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'); }
* - roboto-v30-latin-300.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'); }
* - roboto-v30-latin-regular.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'); }
* - roboto-v30-latin-700.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'); }
/* Roboto Thin (100) */ @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-face { font-family: 'Roboto'; font-style: italic; font-weight: 800; font-display: swap; src: url('../../fonts/roboto-v51-latin-800italic.woff2') format('woff2'); }
font-family: 'Roboto'; @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-style: normal; @font-face { font-family: 'Roboto'; font-style: italic; font-weight: 900; font-display: swap; src: url('../../fonts/roboto-v51-latin-900italic.woff2') format('woff2'); }
font-weight: 100;
font-display: swap;
src: url('../../fonts/roboto-v30-latin-100.woff2') format('woff2');
}
/* Roboto Light (300) */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url('../../fonts/roboto-v30-latin-300.woff2') format('woff2');
}
/* Roboto Regular (400) */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('../../fonts/roboto-v30-latin-regular.woff2') format('woff2');
}
/* Roboto Bold (700) */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('../../fonts/roboto-v30-latin-700.woff2') format('woff2');
}

View File

@@ -13987,21 +13987,10 @@ meter {
} }
.footer { .footer {
margin-top: 1em; padding-top: 1rem;
color: var(--body-bg, #e6ebf1); color: var(--body-bg, #e6ebf1);
background-color: var(--nav-bg-color); background-color: var(--nav-bg-color);
padding-left: 100px; padding-bottom: 80px;
padding-right: 60px;
padding-bottom: 60px;
}
/* Increase footer right padding when floating controls are present */
body[data-theme-fab-enabled="1"] .footer {
padding-right: 220px;
}
body[data-theme-fab-enabled="1"][data-a11y-toolbar="1"] .footer {
padding-right: 420px;
} }
.footer .grid-child { .footer .grid-child {
@@ -14624,6 +14613,10 @@ iframe {
line-height: 1.3; line-height: 1.3;
} }
/* Module title alignment — apply via module class suffix */
.title-center [class*="__title"] { text-align: center; }
.title-right [class*="__title"] { text-align: right; }
/* ── MODULE: Statistics ── */ /* ── MODULE: Statistics ── */
.mod-stats__list { .mod-stats__list {
margin: 0; margin: 0;
@@ -14760,6 +14753,21 @@ iframe {
} }
/* Breadcrumbs module */ /* Breadcrumbs module */
.mod-breadcrumbs {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
}
.mod-breadcrumbs__here {
float: left;
font-weight: 600;
white-space: nowrap;
color: var(--body-font-color, #444);
padding-right: .15rem;
}
.mod-breadcrumbs .breadcrumb { .mod-breadcrumbs .breadcrumb {
background: transparent; background: transparent;
padding: 0; padding: 0;
@@ -14853,8 +14861,10 @@ iframe {
.container-top-a, .container-top-a,
.container-top-b, .container-top-b,
.container-bottom-a, .container-bottom-a,
.container-bottom-b { .container-bottom-b,
.mod-breadcrumbs {
position: relative; position: relative;
clear: both;
} }
.container-top-a>*, .container-top-a>*,
@@ -15771,7 +15781,7 @@ body.wrapper-fluid header>.grid-child {
} }
footer .grid-child>div { footer .grid-child>div {
padding: 1rem 4em; padding: 1rem 0 0;
} }
header .grid-child .navbar-brand { header .grid-child .navbar-brand {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,131 +1,31 @@
<!-- <!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later SPDX-License-Identifier: GPL-3.0-or-later
VERSION: 03.09.10
-->
# FILE INFORMATION # Self-Hosted Google Fonts
DEFGROUP: Joomla.Template.Site
INGROUP: MokoCassiopeia.Documentation
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
FILE: src/media/fonts/GOOGLE_FONTS_README.md
VERSION: 03.09.03
BRIEF: Instructions for downloading Google Fonts for self-hosting
-->
# Google Fonts - Download Instructions Fonts are served locally to avoid external CDN dependencies and improve privacy/performance.
This directory should contain self-hosted Google Font files to eliminate CDN dependencies. ## Available Fonts
## ⚠️ Manual Download Required | 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` |
The Google Font `.woff2` files are **NOT included** in the repository and must be downloaded manually before using non-default font schemes. ## Adding New Fonts
**Currently Available:** 1. Run `php scripts/download-google-fonts.php` or manually download woff2 files
- ✅ Osaka font (local TTF file, included) 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
**Requires Manual Download:** ## File Naming Convention
- ❌ Roboto fonts (4 weight variants)
- ❌ Noto Sans fonts (4 weight variants)
- ❌ Fira Sans fonts (4 weight variants)
## Required Font Files `{font-name}-v{version}-latin-{weight}.woff2`
Download the following `.woff2` font files and place them in this directory: Examples: `roboto-v51-latin-regular.woff2`, `fredoka-v17-latin-700.woff2`
### Roboto Font Files
- `roboto-v30-latin-100.woff2` (Thin)
- `roboto-v30-latin-300.woff2` (Light)
- `roboto-v30-latin-regular.woff2` (Regular)
- `roboto-v30-latin-700.woff2` (Bold)
### Noto Sans Font Files
- `noto-sans-v36-latin-100.woff2` (Thin)
- `noto-sans-v36-latin-300.woff2` (Light)
- `noto-sans-v36-latin-regular.woff2` (Regular)
- `noto-sans-v36-latin-700.woff2` (Bold)
### Fira Sans Font Files
- `fira-sans-v17-latin-100.woff2` (Thin)
- `fira-sans-v17-latin-300.woff2` (Light)
- `fira-sans-v17-latin-regular.woff2` (Regular)
- `fira-sans-v17-latin-700.woff2` (Bold)
## How to Download
### Option 1: Using google-webfonts-helper (Recommended)
1. Visit https://gwfh.mranftl.com/
2. Search for each font (Roboto, Noto Sans, Fira Sans)
3. Select character sets: **latin** (or add latin-ext if needed)
4. Select styles:
- ☑ 100 (thin)
- ☑ 300 (light)
- ☑ 400 (regular)
- ☑ 700 (bold)
5. In step 3, ensure **Modern Browsers** is selected (woff2 format)
6. In step 4, click **Download files**
7. Extract the `.woff2` files to this directory
### Option 2: Using google-font-installer (Node.js)
```bash
npm install -g google-font-installer
cd src/media/fonts/
# Download Roboto
google-font-installer Roboto:100,300,400,700
# Download Noto Sans
google-font-installer "Noto Sans:100,300,400,700"
# Download Fira Sans
google-font-installer "Fira Sans:100,300,400,700"
```
### Option 3: Manual Download Script (Linux/macOS)
```bash
#!/bin/bash
# Run this from src/media/fonts/ directory
download_font() {
local font_url="$1"
local output_dir="."
# Download CSS
css=$(curl -s -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" "$font_url")
# Extract and download woff2 files
echo "$css" | grep -oP 'https://fonts\.gstatic\.com[^\)]*\.woff2' | while read url; do
filename=$(basename "$url")
echo "Downloading $filename..."
curl -s "$url" -o "$output_dir/$filename"
done
}
download_font "https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;700&display=swap"
download_font "https://fonts.googleapis.com/css2?family=Noto+Sans:wght@100;300;400;700&display=swap"
download_font "https://fonts.googleapis.com/css2?family=Fira+Sans:wght@100;300;400;700&display=swap"
```
## Font CSS Files
The corresponding CSS files with `@font-face` declarations are located in:
- `../css/fonts/roboto.css`
- `../css/fonts/noto-sans.css`
- `../css/fonts/fira-sans.css`
These CSS files reference the `.woff2` files in this directory.
## License
All Google Fonts are open source and licensed under the SIL Open Font License (OFL).
- Roboto: Apache License 2.0
- Noto Sans: SIL Open Font License 1.1
- Fira Sans: SIL Open Font License 1.1
## References
- Google Fonts: https://fonts.google.com/
- google-webfonts-helper: https://gwfh.mranftl.com/
- Font Licensing: https://fonts.google.com/attribution

View File

@@ -36,13 +36,13 @@
</server> </server>
</updateservers> </updateservers>
<name>MokoCassiopeia</name> <name>MokoCassiopeia</name>
<version>03.09.09</version> <version>03.09.12</version>
<scriptfile>script.php</scriptfile> <scriptfile>script.php</scriptfile>
<creationDate>2026-03-26</creationDate> <creationDate>2026-03-26</creationDate>
<author>Jonathan Miller || Moko Consulting</author> <author>Jonathan Miller || Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<copyright>(C)GNU General Public License Version 3 - 2026 Moko Consulting</copyright> <copyright>(C)GNU General Public License Version 3 - 2026 Moko Consulting</copyright>
<description><![CDATA[<h3>MokoCassiopeia Template Description</h3> <p> <strong>MokoCassiopeia</strong> continues Joomla's 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/mokocassiopeia/templates/light.custom.css</code> to <code>media/templates/site/mokocassiopeia/css/theme/light.custom.css</code>, or <code>templates/mokocassiopeia/templates/dark.custom.css</code> to <code>media/templates/site/mokocassiopeia/css/theme/dark.custom.css</code>. Customise the CSS variables to match your brand, then activate your palette in <em>System → Site Templates → MokoCassiopeia → 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/mokocassiopeia/css/user.css</code> — loaded on every page for custom CSS overrides.</li> <li><code>media/templates/site/mokocassiopeia/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>]]></description> <description><![CDATA[<p><img src="https://img.shields.io/badge/version-03.09.12-blue.svg?logo=v&amp;logoColor=white" alt="Version 03.09.12" /> <img src="https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&amp;logoColor=white" alt="License" /> <img src="https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&amp;logoColor=white" alt="Joomla" /> <img src="https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&amp;logoColor=white" alt="PHP" /></p> <h3>MokoCassiopeia Template Description</h3> <p> <strong>MokoCassiopeia</strong> continues Joomla's 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/mokocassiopeia/templates/light.custom.css</code> to <code>media/templates/site/mokocassiopeia/css/theme/light.custom.css</code>, or <code>templates/mokocassiopeia/templates/dark.custom.css</code> to <code>media/templates/site/mokocassiopeia/css/theme/dark.custom.css</code>. Customise the CSS variables to match your brand, then activate your palette in <em>System → Site Templates → MokoCassiopeia → 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/mokocassiopeia/css/user.css</code> — loaded on every page for custom CSS overrides.</li> <li><code>media/templates/site/mokocassiopeia/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>]]></description>
<inheritable>1</inheritable> <inheritable>1</inheritable>
<files> <files>
<filename>component.php</filename> <filename>component.php</filename>