merge: dev into main — Pa11y + Gitea refs
Some checks failed
Repo Health / Access control (push) Failing after 2s
Repo Health / Release configuration (push) Has been skipped
Repo Health / Scripts governance (push) Has been skipped
Repo Health / Repository health (push) Has been skipped

This commit is contained in:
Jonathan Miller
2026-04-14 20:38:55 -05:00
15 changed files with 697 additions and 354 deletions

374
Makefile
View File

@@ -1,53 +1,27 @@
# Makefile for Joomla Extensions
# Makefile for MokoCassiopeia Joomla Template
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This is a reference Makefile for building Joomla extensions.
# Copy this to your repository root as "Makefile" and customize as needed.
#
# Supports: Modules, Plugins, Components, Packages, Templates
# Build and validation powered by MokoStandards Enterprise API
# Install: composer install
# ==============================================================================
# CONFIGURATION - Customize these for your extension
# CONFIGURATION
# ==============================================================================
# Extension Configuration
EXTENSION_NAME := mokoexample
EXTENSION_TYPE := module
# Options: module, plugin, component, package, template
EXTENSION_VERSION := 1.0.0
EXTENSION_NAME := mokocassiopeia
EXTENSION_TYPE := template
EXTENSION_VERSION := $(shell grep -oP 'VERSION:\s*\K[0-9.]+' README.md 2>/dev/null || echo "0.0.0")
# Module Configuration (for modules only)
MODULE_TYPE := site
# Options: site, admin
# Plugin Configuration (for plugins only)
PLUGIN_GROUP := system
# Options: system, content, user, authentication, etc.
# Directories
SRC_DIR := .
SRC_DIR := src
BUILD_DIR := build
DIST_DIR := dist
DOCS_DIR := docs
# Joomla Installation (for local testing - customize paths)
JOOMLA_ROOT := /var/www/html/joomla
JOOMLA_VERSION := 4
# Tools
PHP := php
COMPOSER := composer
NPM := npm
PHPCS := vendor/bin/phpcs
PHPCBF := vendor/bin/phpcbf
PHPUNIT := vendor/bin/phpunit
ZIP := zip
MOKO := vendor/bin/moko
# Coding Standards
PHPCS_STANDARD := Joomla
# Colors for output
# Colors
COLOR_RESET := \033[0m
COLOR_GREEN := \033[32m
COLOR_YELLOW := \033[33m
@@ -61,92 +35,85 @@ COLOR_RED := \033[31m
.PHONY: help
help: ## Show this help message
@echo "$(COLOR_BLUE)╔════════════════════════════════════════════════════════════╗$(COLOR_RESET)"
@echo "$(COLOR_BLUE) Joomla Extension Makefile $(COLOR_RESET)"
@echo "$(COLOR_BLUE)MokoCassiopeia Template Build$(COLOR_RESET)"
@echo "$(COLOR_BLUE)╚════════════════════════════════════════════════════════════╝$(COLOR_RESET)"
@echo ""
@echo "Extension: $(EXTENSION_NAME) ($(EXTENSION_TYPE)) v$(EXTENSION_VERSION)"
@echo "Powered by: MokoStandards Enterprise API"
@echo ""
@echo "$(COLOR_GREEN)Available targets:$(COLOR_RESET)"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(COLOR_BLUE)%-20s$(COLOR_RESET) %s\n", $$1, $$2}'
@echo ""
@echo "$(COLOR_YELLOW)Quick Start:$(COLOR_RESET)"
@echo " 1. make install-deps # Install dependencies"
@echo " 2. make build # Build extension package"
@echo " 3. make test # Run tests"
@echo ""
# ── Dependencies ──────────────────────────────────────────────────────────────
.PHONY: install-deps
install-deps: ## Install all dependencies (Composer + npm)
install-deps: ## Install Composer dependencies (includes MokoStandards API)
@echo "$(COLOR_BLUE)Installing dependencies...$(COLOR_RESET)"
@if [ -f "composer.json" ]; then \
$(COMPOSER) install; \
echo "$(COLOR_GREEN)✓ Composer dependencies installed$(COLOR_RESET)"; \
fi
@if [ -f "package.json" ]; then \
$(NPM) install; \
echo "$(COLOR_GREEN)✓ npm dependencies installed$(COLOR_RESET)"; \
fi
@$(COMPOSER) install
@echo "$(COLOR_GREEN)✓ Dependencies installed$(COLOR_RESET)"
.PHONY: update-deps
update-deps: ## Update all dependencies
update-deps: ## Update Composer dependencies
@echo "$(COLOR_BLUE)Updating dependencies...$(COLOR_RESET)"
@if [ -f "composer.json" ]; then \
$(COMPOSER) update; \
echo "$(COLOR_GREEN)✓ Composer dependencies updated$(COLOR_RESET)"; \
fi
@if [ -f "package.json" ]; then \
$(NPM) update; \
echo "$(COLOR_GREEN)✓ npm dependencies updated$(COLOR_RESET)"; \
@$(COMPOSER) update
@echo "$(COLOR_GREEN)✓ Dependencies updated$(COLOR_RESET)"
# ── Validation (MokoStandards API) ────────────────────────────────────────────
.PHONY: check-moko
check-moko:
@if [ ! -f "$(MOKO)" ]; then \
echo "$(COLOR_RED)✗ MokoStandards CLI not found. Run: make install-deps$(COLOR_RESET)"; \
exit 1; \
fi
.PHONY: lint
lint: ## Run PHP linter (syntax check)
@echo "$(COLOR_BLUE)Running PHP linter...$(COLOR_RESET)"
@find . -name "*.php" ! -path "./vendor/*" ! -path "./node_modules/*" ! -path "./$(BUILD_DIR)/*" \
-exec $(PHP) -l {} \; | grep -v "No syntax errors" || true
@echo "$(COLOR_GREEN)✓ PHP linting complete$(COLOR_RESET)"
lint: check-moko ## PHP syntax check via MokoStandards
@echo "$(COLOR_BLUE)Running PHP syntax check...$(COLOR_RESET)"
@$(PHP) $(MOKO) check:syntax -- --path .
@echo "$(COLOR_GREEN)✓ PHP syntax OK$(COLOR_RESET)"
.PHONY: phpcs
phpcs: ## Run PHP CodeSniffer (Joomla standards)
@echo "$(COLOR_BLUE)Running PHP CodeSniffer...$(COLOR_RESET)"
@if [ -f "$(PHPCS)" ]; then \
$(PHPCS) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,node_modules,$(BUILD_DIR) .; \
else \
echo "$(COLOR_YELLOW)⚠ PHP CodeSniffer not installed. Run: make install-deps$(COLOR_RESET)"; \
fi
.PHONY: check-joomla
check-joomla: check-moko ## Validate Joomla manifest via MokoStandards
@echo "$(COLOR_BLUE)Validating Joomla manifest...$(COLOR_RESET)"
@$(PHP) $(MOKO) check:joomla -- --path .
@echo "$(COLOR_GREEN)✓ Joomla manifest valid$(COLOR_RESET)"
.PHONY: phpcbf
phpcbf: ## Fix coding standards automatically
@echo "$(COLOR_BLUE)Running PHP Code Beautifier...$(COLOR_RESET)"
@if [ -f "$(PHPCBF)" ]; then \
$(PHPCBF) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,node_modules,$(BUILD_DIR) .; \
echo "$(COLOR_GREEN)✓ Code formatting applied$(COLOR_RESET)"; \
else \
echo "$(COLOR_YELLOW)⚠ PHP Code Beautifier not installed. Run: make install-deps$(COLOR_RESET)"; \
fi
.PHONY: check-version
check-version: check-moko ## Verify version consistency across files
@echo "$(COLOR_BLUE)Checking version consistency...$(COLOR_RESET)"
@$(PHP) $(MOKO) check:version -- --path .
@echo "$(COLOR_GREEN)✓ Versions consistent$(COLOR_RESET)"
.PHONY: check-headers
check-headers: check-moko ## Check license headers on source files
@echo "$(COLOR_BLUE)Checking license headers...$(COLOR_RESET)"
@$(PHP) $(MOKO) check:headers -- --path .
@echo "$(COLOR_GREEN)✓ Headers OK$(COLOR_RESET)"
.PHONY: check-secrets
check-secrets: check-moko ## Scan for leaked credentials
@echo "$(COLOR_BLUE)Scanning for secrets...$(COLOR_RESET)"
@$(PHP) $(MOKO) check:secrets -- --path .
@echo "$(COLOR_GREEN)✓ No secrets found$(COLOR_RESET)"
.PHONY: check-xml
check-xml: check-moko ## Validate XML files are well-formed
@echo "$(COLOR_BLUE)Checking XML files...$(COLOR_RESET)"
@$(PHP) $(MOKO) check:xml -- --path .
@echo "$(COLOR_GREEN)✓ XML well-formed$(COLOR_RESET)"
.PHONY: validate
validate: lint phpcs ## Run all validation checks
validate: lint check-joomla check-version check-xml check-headers check-secrets ## Run all MokoStandards validation checks
@echo "$(COLOR_GREEN)✓ All validation checks passed$(COLOR_RESET)"
.PHONY: test
test: ## Run PHPUnit tests
@echo "$(COLOR_BLUE)Running tests...$(COLOR_RESET)"
@if [ -f "$(PHPUNIT)" ] && [ -f "phpunit.xml" ]; then \
$(PHPUNIT); \
else \
echo "$(COLOR_YELLOW)⚠ PHPUnit not configured$(COLOR_RESET)"; \
fi
.PHONY: health
health: check-moko ## Full repository health check via MokoStandards
@echo "$(COLOR_BLUE)Running full health check...$(COLOR_RESET)"
@$(PHP) $(MOKO) health -- --path .
.PHONY: test-coverage
test-coverage: ## Run tests with coverage report
@echo "$(COLOR_BLUE)Running tests with coverage...$(COLOR_RESET)"
@if [ -f "$(PHPUNIT)" ] && [ -f "phpunit.xml" ]; then \
$(PHPUNIT) --coverage-html $(BUILD_DIR)/coverage; \
echo "$(COLOR_GREEN)✓ Coverage report: $(BUILD_DIR)/coverage/index.html$(COLOR_RESET)"; \
else \
echo "$(COLOR_YELLOW)⚠ PHPUnit not configured$(COLOR_RESET)"; \
fi
# ── Build ─────────────────────────────────────────────────────────────────────
.PHONY: clean
clean: ## Clean build artifacts
@@ -155,179 +122,78 @@ clean: ## Clean build artifacts
@echo "$(COLOR_GREEN)✓ Build artifacts cleaned$(COLOR_RESET)"
.PHONY: build
build: clean validate ## Build extension package
@echo "$(COLOR_BLUE)Building Joomla extension package...$(COLOR_RESET)"
@mkdir -p $(DIST_DIR) $(BUILD_DIR)
# Determine package prefix based on extension type
@case "$(EXTENSION_TYPE)" in \
module) \
PACKAGE_PREFIX="mod_$(EXTENSION_NAME)"; \
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
;; \
plugin) \
PACKAGE_PREFIX="plg_$(PLUGIN_GROUP)_$(EXTENSION_NAME)"; \
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
;; \
component) \
PACKAGE_PREFIX="com_$(EXTENSION_NAME)"; \
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
;; \
package) \
PACKAGE_PREFIX="pkg_$(EXTENSION_NAME)"; \
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
;; \
template) \
PACKAGE_PREFIX="tpl_$(EXTENSION_NAME)"; \
BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \
;; \
*) \
echo "$(COLOR_RED)✗ Unknown extension type: $(EXTENSION_TYPE)$(COLOR_RESET)"; \
exit 1; \
;; \
esac; \
\
mkdir -p "$$BUILD_TARGET"; \
\
echo "Building $$PACKAGE_PREFIX..."; \
\
rsync -av --progress \
--exclude='$(BUILD_DIR)' \
--exclude='$(DIST_DIR)' \
--exclude='.git*' \
--exclude='vendor/' \
--exclude='node_modules/' \
--exclude='tests/' \
--exclude='Makefile' \
--exclude='composer.json' \
--exclude='composer.lock' \
--exclude='package.json' \
--exclude='package-lock.json' \
--exclude='phpunit.xml' \
--exclude='*.md' \
--exclude='.editorconfig' \
. "$$BUILD_TARGET/"; \
\
cd $(BUILD_DIR) && $(ZIP) -r "../$(DIST_DIR)/$${PACKAGE_PREFIX}-$(EXTENSION_VERSION).zip" "$${PACKAGE_PREFIX}"; \
\
echo "$(COLOR_GREEN)✓ Package created: $(DIST_DIR)/$${PACKAGE_PREFIX}-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
.PHONY: package
package: build ## Alias for build
@echo "$(COLOR_GREEN)✓ Package ready for distribution$(COLOR_RESET)"
.PHONY: install-local
install-local: build ## Install to local Joomla (upload via admin)
@echo "$(COLOR_BLUE)Package ready for installation$(COLOR_RESET)"
@case "$(EXTENSION_TYPE)" in \
module) PACKAGE="mod_$(EXTENSION_NAME)";; \
plugin) PACKAGE="plg_$(PLUGIN_GROUP)_$(EXTENSION_NAME)";; \
component) PACKAGE="com_$(EXTENSION_NAME)";; \
package) PACKAGE="pkg_$(EXTENSION_NAME)";; \
template) PACKAGE="tpl_$(EXTENSION_NAME)";; \
esac; \
echo "$(COLOR_YELLOW)Upload $(DIST_DIR)/$${PACKAGE}-$(EXTENSION_VERSION).zip via Joomla Administrator$(COLOR_RESET)"; \
echo "Admin URL: $(JOOMLA_ROOT) → Extensions → Install"
.PHONY: dev-install
dev-install: ## Create symlink for development (Joomla 4+)
@echo "$(COLOR_BLUE)Creating development symlink...$(COLOR_RESET)"
@if [ ! -d "$(JOOMLA_ROOT)" ]; then \
echo "$(COLOR_RED)✗ Joomla root not found at $(JOOMLA_ROOT)$(COLOR_RESET)"; \
echo "Update JOOMLA_ROOT in Makefile"; \
build: clean ## Build template installable ZIP from src/
@echo "$(COLOR_BLUE)Building $(EXTENSION_NAME) v$(EXTENSION_VERSION)...$(COLOR_RESET)"
@mkdir -p $(BUILD_DIR)/package $(DIST_DIR)
@cp -r $(SRC_DIR)/* $(BUILD_DIR)/package/
@cd $(BUILD_DIR)/package && \
if command -v zip >/dev/null 2>&1; then \
zip -r "../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" .; \
elif command -v pwsh >/dev/null 2>&1; then \
pwsh -Command "Compress-Archive -Path '*' -DestinationPath '../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip' -Force"; \
elif command -v powershell >/dev/null 2>&1; then \
powershell -Command "Compress-Archive -Path '*' -DestinationPath '../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip' -Force"; \
else \
echo "$(COLOR_RED)✗ No zip tool found (zip, pwsh, powershell)$(COLOR_RESET)"; \
exit 1; \
fi
@echo "$(COLOR_GREEN)✓ Package: $(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
@case "$(EXTENSION_TYPE)" in \
module) \
if [ "$(MODULE_TYPE)" = "admin" ]; then \
TARGET="$(JOOMLA_ROOT)/administrator/modules/mod_$(EXTENSION_NAME)"; \
.PHONY: build-beta
build-beta: clean ## Build beta release ZIP
@echo "$(COLOR_BLUE)Building $(EXTENSION_NAME) v$(EXTENSION_VERSION)-beta...$(COLOR_RESET)"
@mkdir -p $(BUILD_DIR)/package $(DIST_DIR)
@cp -r $(SRC_DIR)/* $(BUILD_DIR)/package/
@cd $(BUILD_DIR)/package && \
if command -v zip >/dev/null 2>&1; then \
zip -r "../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION)-beta.zip" .; \
elif command -v pwsh >/dev/null 2>&1; then \
pwsh -Command "Compress-Archive -Path '*' -DestinationPath '../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION)-beta.zip' -Force"; \
elif command -v powershell >/dev/null 2>&1; then \
powershell -Command "Compress-Archive -Path '*' -DestinationPath '../../$(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION)-beta.zip' -Force"; \
else \
TARGET="$(JOOMLA_ROOT)/modules/mod_$(EXTENSION_NAME)"; \
fi; \
;; \
plugin) \
TARGET="$(JOOMLA_ROOT)/plugins/$(PLUGIN_GROUP)/$(EXTENSION_NAME)"; \
;; \
component) \
echo "$(COLOR_YELLOW)⚠ Components require complex symlink setup$(COLOR_RESET)"; \
echo "Manual setup recommended for component development"; \
echo "$(COLOR_RED)✗ No zip tool found$(COLOR_RESET)"; \
exit 1; \
;; \
*) \
echo "$(COLOR_RED)✗ dev-install not supported for $(EXTENSION_TYPE)$(COLOR_RESET)"; \
exit 1; \
;; \
esac; \
\
rm -rf "$$TARGET"; \
ln -s "$(PWD)" "$$TARGET"; \
echo "$(COLOR_GREEN)✓ Development symlink created at $$TARGET$(COLOR_RESET)"
fi
@echo "$(COLOR_GREEN)✓ Package: $(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION)-beta.zip$(COLOR_RESET)"
.PHONY: watch
watch: ## Watch for changes and rebuild
@echo "$(COLOR_BLUE)Watching for changes...$(COLOR_RESET)"
@echo "$(COLOR_YELLOW)Press Ctrl+C to stop$(COLOR_RESET)"
@while true; do \
inotifywait -r -e modify,create,delete --exclude '($(BUILD_DIR)|$(DIST_DIR)|vendor|node_modules)' . 2>/dev/null || \
(echo "$(COLOR_YELLOW)⚠ inotifywait not installed. Install: apt-get install inotify-tools$(COLOR_RESET)" && sleep 5); \
make build; \
.PHONY: checksum
checksum: ## Generate SHA-256 checksums for dist packages
@echo "$(COLOR_BLUE)Generating checksums...$(COLOR_RESET)"
@for f in $(DIST_DIR)/*.zip; do \
sha256sum "$$f" | tee "$${f}.sha256"; \
done
@echo "$(COLOR_GREEN)✓ Checksums generated$(COLOR_RESET)"
# ── Release ───────────────────────────────────────────────────────────────────
.PHONY: release
release: validate build checksum ## Full release pipeline (validate + build + checksum)
@echo "$(COLOR_GREEN)✓ Release package ready$(COLOR_RESET)"
@echo ""
@echo "$(COLOR_BLUE)Next steps:$(COLOR_RESET)"
@echo " 1. Tag: git tag $(EXTENSION_VERSION)"
@echo " 2. Push: git push origin --tags"
@echo " 3. Create Gitea release and attach $(DIST_DIR)/$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip"
@echo ""
# ── Info ──────────────────────────────────────────────────────────────────────
.PHONY: version
version: ## Display version information
version: ## Display version and extension info
@echo "$(COLOR_BLUE)Extension Information:$(COLOR_RESET)"
@echo " Name: $(EXTENSION_NAME)"
@echo " Type: $(EXTENSION_TYPE)"
@echo " Version: $(EXTENSION_VERSION)"
@if [ "$(EXTENSION_TYPE)" = "module" ]; then \
echo " Module: $(MODULE_TYPE)"; \
fi
@if [ "$(EXTENSION_TYPE)" = "plugin" ]; then \
echo " Group: $(PLUGIN_GROUP)"; \
fi
.PHONY: docs
docs: ## Generate documentation
@echo "$(COLOR_BLUE)Generating documentation...$(COLOR_RESET)"
@mkdir -p $(DOCS_DIR)
@echo "$(COLOR_YELLOW)⚠ Documentation generation not configured$(COLOR_RESET)"
@echo "Consider adding phpDocumentor or similar"
.PHONY: release
release: validate test build ## Create a release (validate + test + build)
@echo "$(COLOR_GREEN)✓ Release package ready$(COLOR_RESET)"
@echo ""
@echo "$(COLOR_BLUE)Release Checklist:$(COLOR_RESET)"
@echo " [ ] Update CHANGELOG.md"
@echo " [ ] Update version in XML manifest"
@echo " [ ] Test installation in clean Joomla"
@echo " [ ] Tag release in git: git tag v$(EXTENSION_VERSION)"
@echo " [ ] Push tags: git push --tags"
@echo " [ ] Create GitHub release"
@echo ""
@case "$(EXTENSION_TYPE)" in \
module) PACKAGE="mod_$(EXTENSION_NAME)";; \
plugin) PACKAGE="plg_$(PLUGIN_GROUP)_$(EXTENSION_NAME)";; \
component) PACKAGE="com_$(EXTENSION_NAME)";; \
package) PACKAGE="pkg_$(EXTENSION_NAME)";; \
template) PACKAGE="tpl_$(EXTENSION_NAME)";; \
esac; \
echo "$(COLOR_GREEN)Package: $(DIST_DIR)/$${PACKAGE}-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
.PHONY: security-check
security-check: ## Run security checks on dependencies
security-check: ## Run Composer security audit
@echo "$(COLOR_BLUE)Running security checks...$(COLOR_RESET)"
@if [ -f "composer.json" ]; then \
$(COMPOSER) audit || echo "$(COLOR_YELLOW)⚠ Vulnerabilities found$(COLOR_RESET)"; \
fi
@if [ -f "package.json" ]; then \
$(NPM) audit || echo "$(COLOR_YELLOW)⚠ Vulnerabilities found$(COLOR_RESET)"; \
fi
@$(COMPOSER) audit
@echo "$(COLOR_GREEN)✓ Security check complete$(COLOR_RESET)"
.PHONY: all
all: install-deps validate test build ## Run complete build pipeline
all: install-deps validate build checksum ## Full pipeline: deps → validate → build → checksum
@echo "$(COLOR_GREEN)✓ Complete build pipeline finished$(COLOR_RESET)"
# Default target
.DEFAULT_GOAL := help

View File

@@ -9,7 +9,7 @@
INGROUP: MokoCassiopeia.Documentation
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
FILE: ./README.md
VERSION: 03.09.13
VERSION: 03.09.14
BRIEF: Documentation for MokoCassiopeia template
-->

View File

@@ -1 +1,29 @@
{
"name": "mokoconsulting/mokocassiopeia",
"description": "MokoCassiopeia — Joomla site template based on Cassiopeia",
"type": "joomla-template",
"license": "GPL-3.0-or-later",
"authors": [
{
"name": "Jonathan Miller",
"email": "hello@mokoconsulting.tech"
}
],
"require": {
"php": ">=8.1"
},
"require-dev": {
"mokoconsulting-tech/enterprise": "^4.0"
},
"config": {
"sort-packages": true,
"optimize-autoloader": true,
"preferred-install": "dist"
},
"repositories": [
{
"type": "composer",
"url": "https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer"
}
]
}

View File

@@ -17,7 +17,7 @@
"defgroup": "Joomla.Template.Site",
"ingroup": "MokoCassiopeia.Template.Assets",
"path": "./media/templates/site/mokocassiopeia/joomla.asset.json",
"version": "03.09.02",
"version": "03.09.14",
"brief": "Joomla asset registry for MokoCassiopeia"
}
},
@@ -124,6 +124,12 @@
"uri": "media/templates/site/mokocassiopeia/css/theme/dark.custom.min.css",
"attributes": {"media": "all"}
},
{
"name": "template.a11y-high-contrast",
"type": "style",
"uri": "media/templates/site/mokocassiopeia/css/a11y-high-contrast.css",
"attributes": {"media": "all"}
},
{
"name": "template.js",
"type": "script",

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: MokoCassiopeia.Accessibility
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia
* 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;
}

View File

@@ -13780,14 +13780,6 @@ meter {
margin-left: auto;
}
.page-link {
color: var(--color-link, white);
}
.page-link:active {
color: var(--color-link, white);
}
.pager .pagination {
-webkit-box-pack: center;
-ms-flex-pack: center;
@@ -13987,10 +13979,9 @@ meter {
}
.footer {
padding-top: 1rem;
color: var(--body-bg, #e6ebf1);
color: var(--mainmenu-nav-link-color, #fff);
background-color: var(--nav-bg-color);
padding-bottom: 80px;
padding-bottom: var(--footer-padding-bottom, 80px);
}
.footer .grid-child {
@@ -14002,7 +13993,6 @@ meter {
-ms-flex-direction: column;
flex-direction: column;
width: 100%;
padding: 2.5rem 0.5em;
}
.footer a {
@@ -14248,8 +14238,8 @@ fieldset>* {
.container-header .navbar-brand {
position: relative;
display: inline-block;
padding-top: 0.3125rem;
padding-bottom: 0.3125rem;
padding-top: var(--navbar-brand-padding-y, 0.3125rem);
padding-bottom: var(--navbar-brand-padding-y, 0.3125rem);
font-size: 2rem;
color: var(--nav-text-color, gray);
-webkit-margin-end: auto;
@@ -15781,7 +15771,7 @@ body.wrapper-fluid header>.grid-child {
}
footer .grid-child>div {
padding: 1rem 0 0;
padding: var(--navbar-padding-y, 1rem) var(--navbar-padding-x, 1rem) 0;
}
header .grid-child .navbar-brand {
@@ -17080,8 +17070,8 @@ form .form-select {
background: var(--muted-color, #6d757e);
box-shadow: var(--box-shadow, 0 .5rem 1rem #00000066);
font: inherit;
color: var(--body-bg, #0e1318);
font-weight: 600px;
color: #fff;
font-weight: 600;
}
#mokoThemeFab.pos-br {
@@ -17143,10 +17133,112 @@ button#mokoThemeSwitch {
#mokoThemeFab .label {
user-select: none;
font-size: .875rem;
color: #fff;
}
#mokoThemeFab button {
color: var(--body-bg, #0e1318);
color: #fff;
}
/* Auto toggle switch (on/off style) */
.auto-toggle-wrap {
display: flex;
align-items: center;
gap: .35rem;
}
.auto-label {
font-size: .75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .04em;
opacity: .7;
user-select: none;
}
.auto-switch {
position: relative;
display: inline-flex;
align-items: center;
width: 32px;
height: 18px;
border: none;
border-radius: 999px;
background: var(--secondary-color, #6c757d);
cursor: pointer;
padding: 0;
transition: background .2s;
}
.auto-switch.on {
background: var(--link-color, #3565e5);
}
.auto-track {
position: relative;
width: 100%;
height: 100%;
}
.auto-knob {
position: absolute;
top: 2px;
left: 2px;
width: 14px;
height: 14px;
border-radius: 50%;
background: #fff;
box-shadow: 0 1px 3px rgba(0,0,0,.3);
transition: transform .2s ease;
}
.auto-switch.on .auto-knob {
transform: translateX(14px);
}
/* FAB divider between theme controls and a11y */
.fab-divider {
display: block;
width: 1px;
height: 24px;
background: currentColor;
opacity: .25;
margin: 0 .15rem;
}
/* Inline a11y toggle inside theme FAB */
.a11y-toggle-inline {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 50%;
border: 1.5px solid currentColor;
background: transparent;
color: inherit;
font-size: 1rem;
cursor: pointer;
padding: 0;
transition: background .2s, color .2s;
opacity: .8;
}
.a11y-toggle-inline:hover,
.a11y-toggle-inline:focus-visible {
opacity: 1;
background: rgba(255,255,255,.15);
}
.a11y-toggle-inline.active {
opacity: 1;
background: rgba(255,255,255,.25);
}
/* Floating a11y panel when inline */
.a11y-toolbar-floating {
position: fixed;
z-index: 1202;
}
body.site.error-page {
@@ -17372,11 +17464,9 @@ html.a11y-pause-animations *::after {
right: 2.5rem;
}
/* When theme FAB is present, sit a11y toolbar to its left */
/* When theme FAB is present, a11y toggle is inline — hide standalone toolbar positioning */
body[data-theme-fab-enabled="1"] #mokoA11yToolbar {
right: auto;
left: 2.5rem;
bottom: 1rem;
position: static;
}
/* Toggle button */

View File

@@ -817,6 +817,12 @@ color-scheme: dark;
--table-active-color: var(--body-color);
--table-active-bg: rgba(var(--white-rgb), 0.1);
/* ===== FOOTER ===== */
--footer-padding-top: 1rem;
--footer-padding-bottom: 80px;
--footer-grid-padding-y: 2.5rem;
--footer-grid-padding-x: 0.5em;
/* ===== BACKDROP ===== */
--backdrop-zindex: 1040;
--backdrop-bg: hsl(0, 0%, 0%);

View File

@@ -817,6 +817,12 @@ color-scheme: dark;
--table-active-color: var(--body-color);
--table-active-bg: rgba(var(--white-rgb), 0.1);
/* ===== FOOTER ===== */
--footer-padding-top: 1rem;
--footer-padding-bottom: 80px;
--footer-grid-padding-y: 2.5rem;
--footer-grid-padding-x: 0.5em;
/* ===== BACKDROP ===== */
--backdrop-zindex: 1040;
--backdrop-bg: hsl(0, 0%, 0%);

View File

@@ -816,6 +816,12 @@ color-scheme: light;
--table-active-color: var(--body-color);
--table-active-bg: rgba(var(--black-rgb), 0.075);
/* ===== FOOTER ===== */
--footer-padding-top: 1rem;
--footer-padding-bottom: 80px;
--footer-grid-padding-y: 2.5rem;
--footer-grid-padding-x: 0.5em;
/* ===== BACKDROP ===== */
--backdrop-zindex: 1040;
--backdrop-bg: hsl(0, 0%, 0%);

View File

@@ -816,6 +816,12 @@ color-scheme: light;
--table-active-color: var(--body-color);
--table-active-bg: rgba(var(--black-rgb), 0.075);
/* ===== FOOTER ===== */
--footer-padding-top: 1rem;
--footer-padding-bottom: 80px;
--footer-grid-padding-y: 2.5rem;
--footer-grid-padding-x: 0.5em;
/* ===== BACKDROP ===== */
--backdrop-zindex: 1040;
--backdrop-bg: hsl(0, 0%, 0%);

View File

@@ -87,13 +87,40 @@
lblD.className = 'label';
lblD.textContent = 'Dark';
// Auto button
// Auto toggle (on/off switch style)
var autoWrap = doc.createElement('div');
autoWrap.className = 'auto-toggle-wrap';
var autoLabel = doc.createElement('span');
autoLabel.className = 'auto-label';
autoLabel.textContent = 'Auto';
var auto = doc.createElement('button');
auto.id = 'mokoThemeAuto';
auto.type = 'button';
auto.className = 'btn btn-sm btn-link text-decoration-none px-2';
auto.setAttribute('aria-label', 'Follow system theme');
auto.textContent = 'Auto';
auto.className = 'auto-switch';
auto.setAttribute('role', 'switch');
auto.setAttribute('aria-label', 'Automatic theme (follow system)');
auto.setAttribute('aria-checked', getStored() ? 'false' : 'true');
var autoTrack = doc.createElement('span');
autoTrack.className = 'auto-track';
var autoKnob = doc.createElement('span');
autoKnob.className = 'auto-knob';
autoTrack.appendChild(autoKnob);
auto.appendChild(autoTrack);
if (!getStored()) auto.classList.add('on');
autoWrap.appendChild(autoLabel);
autoWrap.appendChild(auto);
// Divider before a11y slot
var divider = doc.createElement('span');
divider.className = 'fab-divider';
// A11y slot — buildA11yToolbar will inject its toggle here
var a11ySlot = doc.createElement('span');
a11ySlot.id = 'mokoA11ySlot';
// Behavior
switchWrap.addEventListener('click', function () {
@@ -101,6 +128,9 @@
var next = current === 'dark' ? 'light' : 'dark';
applyTheme(next);
switchWrap.setAttribute('aria-checked', next === 'dark' ? 'true' : 'false');
// Turn off auto when manually switching
auto.classList.remove('on');
auto.setAttribute('aria-checked', 'false');
// Update meta theme color
var meta = doc.querySelector('meta[name="theme-color"]');
if (meta) {
@@ -109,10 +139,14 @@
});
auto.addEventListener('click', function () {
var isAuto = auto.classList.toggle('on');
auto.setAttribute('aria-checked', isAuto ? 'true' : 'false');
if (isAuto) {
clearStored();
var sys = systemTheme();
applyTheme(sys);
switchWrap.setAttribute('aria-checked', sys === 'dark' ? 'true' : 'false');
}
});
// Respond to OS changes only when not user-forced
@@ -134,7 +168,9 @@
wrap.appendChild(lblL);
wrap.appendChild(switchWrap);
wrap.appendChild(lblD);
wrap.appendChild(auto);
wrap.appendChild(autoWrap);
wrap.appendChild(divider);
wrap.appendChild(a11ySlot);
doc.body.appendChild(wrap);
// Debug helper
@@ -193,6 +229,18 @@
function applyContrast(on) {
root.classList.toggle("a11y-high-contrast", on);
// Lazy-load the high-contrast stylesheet
var hcId = "mokoA11yHcSheet";
var existing = doc.getElementById(hcId);
if (on && !existing) {
var link = doc.createElement("link");
link.id = hcId;
link.rel = "stylesheet";
link.href = (doc.querySelector('link[href*="mokocassiopeia/css/template"]') || {}).href
? (doc.querySelector('link[href*="mokocassiopeia/css/template"]').href.replace(/\/css\/template[^/]*$/, "/css/a11y-high-contrast.css"))
: "media/templates/site/mokocassiopeia/css/a11y-high-contrast.css";
doc.head.appendChild(link);
}
}
function applyLinks(on) {
@@ -209,10 +257,10 @@
/** Create a Font Awesome icon element (safe DOM, no innerHTML). */
function faIcon(classes) {
var span = doc.createElement("span");
span.className = classes;
span.setAttribute("aria-hidden", "true");
return span;
var i = doc.createElement("i");
i.className = classes;
i.setAttribute("aria-hidden", "true");
return i;
}
function buildA11yToolbar() {
@@ -359,23 +407,6 @@
addSwitchOption(showFont, "font", "fa-solid fa-font", "Readable font", applyFont);
addSwitchOption(showAnimations, "paused", "fa-solid fa-pause", "Pause animations", applyPaused);
// Toggle panel open/close
toggle.addEventListener("click", function () {
var isOpen = !panel.hidden;
panel.hidden = isOpen;
toggle.setAttribute("aria-expanded", isOpen ? "false" : "true");
toggle.classList.toggle("active", !isOpen);
});
// Close on outside click
doc.addEventListener("click", function (e) {
if (!toolbar.contains(e.target) && !panel.hidden) {
panel.hidden = true;
toggle.setAttribute("aria-expanded", "false");
toggle.classList.remove("active");
}
});
// Apply saved preferences on load
if (prefs.fontStep !== defaultStep) applyFontSize(prefs.fontStep);
if (prefs.inverted) applyInversion(true);
@@ -384,10 +415,56 @@
if (prefs.font) applyFont(true);
if (prefs.paused) applyPaused(true);
// If theme FAB is present, mount a11y toggle inside it; otherwise standalone
var fabSlot = doc.getElementById("mokoA11ySlot");
if (fabSlot) {
toggle.className = "a11y-toggle a11y-toggle-inline";
fabSlot.appendChild(toggle);
toolbar.className = "a11y-toolbar-floating";
toolbar.appendChild(panel);
body.appendChild(toolbar);
// Position panel near the FAB
toggle.addEventListener("click", function () {
var isOpen = !panel.hidden;
panel.hidden = isOpen;
toggle.setAttribute("aria-expanded", isOpen ? "false" : "true");
toggle.classList.toggle("active", !isOpen);
if (!isOpen) {
var rect = toggle.getBoundingClientRect();
toolbar.style.position = "fixed";
toolbar.style.bottom = (win.innerHeight - rect.top + 8) + "px";
toolbar.style.right = (win.innerWidth - rect.right) + "px";
toolbar.style.zIndex = "1202";
}
});
// Close on outside click for inline mode
doc.addEventListener("click", function (e) {
if (!toggle.contains(e.target) && !toolbar.contains(e.target) && !panel.hidden) {
panel.hidden = true;
toggle.setAttribute("aria-expanded", "false");
toggle.classList.remove("active");
}
});
} else {
// Standalone mode — toggle and close handlers
toggle.addEventListener("click", function () {
var isOpen = !panel.hidden;
panel.hidden = isOpen;
toggle.setAttribute("aria-expanded", isOpen ? "false" : "true");
toggle.classList.toggle("active", !isOpen);
});
doc.addEventListener("click", function (e) {
if (!toolbar.contains(e.target) && !panel.hidden) {
panel.hidden = true;
toggle.setAttribute("aria-expanded", "false");
toggle.classList.remove("active");
}
});
toolbar.appendChild(toggle);
toolbar.appendChild(panel);
body.appendChild(toolbar);
}
}
// ========================================================================
// TEMPLATE UTILITIES

View File

@@ -31,18 +31,21 @@
-->
<extension type="template" client="site" method="upgrade">
<updateservers>
<server type="extension" name="MokoCassiopeia Updates" priority="1">
<server type="extension" priority="1" name="MokoCassiopeia Update Server (Gitea)">
https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/raw/branch/main/updates.xml
</server>
<server type="extension" priority="2" name="MokoCassiopeia Update Server (GitHub)">
https://raw.githubusercontent.com/mokoconsulting-tech/MokoCassiopeia/main/updates.xml
</server>
</updateservers>
<name>MokoCassiopeia</name>
<version>03.09.12</version>
<version>03.09.14</version>
<scriptfile>script.php</scriptfile>
<creationDate>2026-03-26</creationDate>
<creationDate>2026-04-14</creationDate>
<author>Jonathan Miller || Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<copyright>(C)GNU General Public License Version 3 - 2026 Moko Consulting</copyright>
<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>
<description><![CDATA[<p><img src="https://img.shields.io/badge/version-03.09.14-blue.svg?logo=v&amp;logoColor=white" alt="Version 03.09.14" /> <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>
<files>
<filename>component.php</filename>

View File

@@ -813,6 +813,12 @@ color-scheme: dark;
--table-active-color: var(--body-color);
--table-active-bg: rgba(var(--white-rgb), 0.1);
/* ===== FOOTER ===== */
--footer-padding-top: 1rem;
--footer-padding-bottom: 80px;
--footer-grid-padding-y: 2.5rem;
--footer-grid-padding-x: 0.5em;
/* ===== BACKDROP ===== */
--backdrop-zindex: 1040;
--backdrop-bg: hsl(0, 0%, 0%);

View File

@@ -812,6 +812,12 @@ color-scheme: light;
--table-active-color: var(--body-color);
--table-active-bg: rgba(var(--black-rgb), 0.075);
/* ===== FOOTER ===== */
--footer-padding-top: 1rem;
--footer-padding-bottom: 80px;
--footer-grid-padding-y: 2.5rem;
--footer-grid-padding-x: 0.5em;
/* ===== BACKDROP ===== */
--backdrop-zindex: 1040;
--backdrop-bg: hsl(0, 0%, 0%);

View File

@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
VERSION: 03.09.12
VERSION: 03.09.14
-->
<updates>
@@ -13,17 +13,19 @@
<element>mokocassiopeia</element>
<type>template</type>
<client>site</client>
<version>03.09.12</version>
<creationDate>2026-04-08</creationDate>
<infourl title='MokoCassiopeia Dev'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/development</infourl>
<version>03.09.14</version>
<creationDate>2026-04-14</creationDate>
<infourl title='MokoCassiopeia Dev'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/development</infourl>
<downloads>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.12-dev.zip</downloadurl>
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.14-dev.zip</downloadurl>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.14-dev.zip</downloadurl>
</downloads>
<sha256>c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932</sha256>
<sha256>4cbe4fc379182ef17580396e7d12ce4ce95a90017ef364b922bdc2d04b0b3d97</sha256>
<tags><tag>development</tag></tags>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name='joomla' version='(5|6)\..*'/>
<targetplatform name='joomla' version='(5|6).*'/>
<php_minimum>8.1</php_minimum>
</update>
<!-- 2. ALPHA — dev → alpha → -->
@@ -33,17 +35,19 @@
<element>mokocassiopeia</element>
<type>template</type>
<client>site</client>
<version>03.09.12</version>
<creationDate>2026-04-08</creationDate>
<infourl title='MokoCassiopeia Alpha'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/alpha</infourl>
<version>03.09.14</version>
<creationDate>2026-04-14</creationDate>
<infourl title='MokoCassiopeia Alpha'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/alpha</infourl>
<downloads>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.12-alpha.zip</downloadurl>
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.14-alpha.zip</downloadurl>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.14-alpha.zip</downloadurl>
</downloads>
<sha256>c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932</sha256>
<tags><tag>alpha</tag></tags>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name='joomla' version='(5|6)\..*'/>
<targetplatform name='joomla' version='(5|6).*'/>
<php_minimum>8.1</php_minimum>
</update>
<!-- 3. BETA — dev → alpha → beta → -->
@@ -53,17 +57,19 @@
<element>mokocassiopeia</element>
<type>template</type>
<client>site</client>
<version>03.09.12</version>
<creationDate>2026-04-08</creationDate>
<infourl title='MokoCassiopeia Beta'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/beta</infourl>
<version>03.09.14</version>
<creationDate>2026-04-14</creationDate>
<infourl title='MokoCassiopeia Beta'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/beta</infourl>
<downloads>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.12-beta.zip</downloadurl>
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.14-beta.zip</downloadurl>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.14-beta.zip</downloadurl>
</downloads>
<sha256>c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932</sha256>
<sha256>4cbe4fc379182ef17580396e7d12ce4ce95a90017ef364b922bdc2d04b0b3d97</sha256>
<tags><tag>beta</tag></tags>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name='joomla' version='(5|6)\..*'/>
<targetplatform name='joomla' version='(5|6).*'/>
<php_minimum>8.1</php_minimum>
</update>
<!-- 4. RC — dev → alpha → beta → rc → -->
@@ -73,17 +79,19 @@
<element>mokocassiopeia</element>
<type>template</type>
<client>site</client>
<version>03.09.12</version>
<creationDate>2026-04-08</creationDate>
<infourl title='MokoCassiopeia RC'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/release-candidate</infourl>
<version>03.09.14</version>
<creationDate>2026-04-14</creationDate>
<infourl title='MokoCassiopeia RC'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/release-candidate</infourl>
<downloads>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.12-rc.zip</downloadurl>
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.14-rc.zip</downloadurl>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-03.09.14-rc.zip</downloadurl>
</downloads>
<sha256>c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932</sha256>
<tags><tag>rc</tag></tags>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name='joomla' version='(5|6)\..*'/>
<targetplatform name='joomla' version='(5|6).*'/>
<php_minimum>8.1</php_minimum>
</update>
<!-- 5. STABLE — dev → alpha → beta → rc → version/XX → main -->
@@ -93,17 +101,19 @@
<element>mokocassiopeia</element>
<type>template</type>
<client>site</client>
<version>03.09.12</version>
<creationDate>2026-04-08</creationDate>
<infourl title='MokoCassiopeia'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/v03</infourl>
<version>03.09.14</version>
<creationDate>2026-04-14</creationDate>
<infourl title='MokoCassiopeia'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/tag/v03</infourl>
<downloads>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.12.zip</downloadurl>
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.14.zip</downloadurl>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.14.zip</downloadurl>
</downloads>
<sha256>c2660acdf7389244462485f7ab4c286e9f851366a148acc16739a184576f7932</sha256>
<tags><tag>stable</tag></tags>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name='joomla' version='(5|6)\..*'/>
<targetplatform name='joomla' version='(5|6).*'/>
<php_minimum>8.1</php_minimum>
</update>
</updates>