diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d47807..1e4a9ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,34 @@ All notable changes to the MokoCassiopeia Joomla template are documented in this The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [03.09.02] - 2026-03-26 + +### Added - Hero Variant System & Block Color System + +#### Hero Variants +- **`.hero#primary`** and **`.hero#secondary`** CSS variant system for visually distinct hero treatments +- Shared `.hero` base class with `background-size: cover`, `border-radius: .5rem`, and `overflow: hidden` +- Six new CSS variables (`--hero-primary-bg-color`, `--hero-primary-overlay`, `--hero-primary-color`, and secondary equivalents) +- Light and dark mode defaults in custom palette templates + +#### Block Color System +- Automatic `:nth-child()` slot palette for `top-a`, `top-b`, `bottom-a`, `bottom-b` module positions +- Four color slots (`--block-color-1` through `--block-color-4`) with matching text variables +- Named per-module overrides: `#block-highlight`, `#block-cta`, `#block-alert` +- ID specificity wins over `:nth-child()` — no `!important` needed + +#### Files Modified +- `src/media/css/template.css` — hero variant rules, block color `:nth-child()` rules, named override rules +- `src/templates/light.custom.css` — hero and block color variables (light mode) +- `src/templates/dark.custom.css` — hero and block color variables (dark mode) +- `docs/CSS_VARIABLES.md` — full variable reference for both systems +- `CHANGELOG.md` — this entry + +#### Files Added +- `src/templates/theme-test.html` — Bootstrap-style test page showing all CSS variables and new features + +--- + ## [03.08.03] - 2026-02-27 ### Added - Main Menu Collapsible Dropdown Override diff --git a/docs/CSS_VARIABLES.md b/docs/CSS_VARIABLES.md index f4adec4..3a0017c 100644 --- a/docs/CSS_VARIABLES.md +++ b/docs/CSS_VARIABLES.md @@ -38,6 +38,8 @@ This document provides a complete reference of all CSS variables available in th - [Responsive Tokens & Breakpoints](#responsive-tokens--breakpoints) - [VirtueMart Variables](#virtuemart-variables) - [Gable Variables](#gable-variables) +- [Hero Variant Variables](#hero-variant-variables) +- [Block Color Variables](#block-color-variables) --- @@ -1357,6 +1359,81 @@ These ensure optimal readability for links within alert boxes. --- +## Hero Variant Variables + +### `--hero-primary-bg-color` +- **Description**: Fallback background color for the primary hero variant +- **Light Mode Default**: `var(--color-primary)` +- **Dark Mode Default**: `#0d1e3a` +- **Usage**: `.hero#primary` background when no image loads + +### `--hero-primary-overlay` +- **Description**: Gradient overlay tint for primary hero +- **Light Mode Default**: `linear-gradient(rgba(163, 205, 226, .45), rgba(163, 205, 226, .45))` +- **Dark Mode Default**: `linear-gradient(rgba(13, 30, 58, .65), rgba(13, 30, 58, .65))` +- **Usage**: Semi-transparent color wash over hero background image + +### `--hero-primary-color` +- **Description**: Text color for primary hero content +- **Light Mode Default**: `var(--color-primary)` +- **Dark Mode Default**: `#f1f5f9` +- **Usage**: Headings and body text inside `.hero#primary` + +### `--hero-secondary-bg-color` +- **Description**: Fallback background color for the secondary hero variant +- **Light Mode Default**: `var(--color-primary)` +- **Dark Mode Default**: `#080f1e` +- **Usage**: `.hero#secondary` background when no image loads + +### `--hero-secondary-overlay` +- **Description**: Gradient overlay tint for secondary hero +- **Light Mode Default**: `linear-gradient(rgba(17, 40, 85, .75), rgba(17, 40, 85, .75))` +- **Dark Mode Default**: `linear-gradient(rgba(8, 15, 30, .80), rgba(8, 15, 30, .80))` +- **Usage**: Stronger overlay for inner-page heroes + +### `--hero-secondary-color` +- **Description**: Text color for secondary hero content +- **Light Mode Default**: `#f1f5f9` +- **Dark Mode Default**: `#f1f5f9` +- **Usage**: Headings and body text inside `.hero#secondary` + +--- + +## Block Color Variables + +### Slot Palette (automatic by position order) + +| Variable | Purpose | Light Default | Dark Default | +|---|---|---|---| +| `--block-color-1` | Background for 1st module | `var(--color-primary)` | `var(--secondary-bg)` | +| `--block-text-1` | Text for 1st module | `var(--body-color)` | `var(--body-color)` | +| `--block-color-2` | Background for 2nd module | `var(--accent-color-primary)` | `var(--accent-color-primary)` | +| `--block-text-2` | Text for 2nd module | `#fff` | `#fff` | +| `--block-color-3` | Background for 3rd module | `var(--warning, #eec234)` | `rgba(238, 194, 52, .15)` | +| `--block-text-3` | Text for 3rd module | `var(--body-color)` | `var(--body-color)` | +| `--block-color-4` | Background for 4th module | `var(--success-bg-subtle, #eef7f0)` | `rgba(74, 166, 100, .15)` | +| `--block-text-4` | Text for 4th module | `var(--body-color)` | `var(--body-color)` | + +### Named Per-Module Overrides + +| Variable | Purpose | +|---|---| +| `--block-highlight-bg` | Background for `#block-highlight` module | +| `--block-highlight-text` | Text color for `#block-highlight` module | +| `--block-cta-bg` | Background for `#block-cta` module | +| `--block-cta-text` | Text color for `#block-cta` module | +| `--block-alert-bg` | Background for `#block-alert` module | +| `--block-alert-text` | Text color for `#block-alert` module | + +### Override Priority + +| Priority | Method | How applied | +|---|---|---| +| 1 (highest) | Named module ID (`#block-highlight`) | ID in module HTML, named variable in palette | +| 2 | Slot color (`--block-color-1` etc.) | Automatic by `:nth-child()` order | + +--- + ## Metadata * Document: docs/CSS_VARIABLES.md @@ -1372,5 +1449,6 @@ These ensure optimal readability for links within alert boxes. | Date | Change Summary | Author | | ---------- | ----------------------------------------------------- | --------------- | +| 2026-03-26 | Added hero variant and block color variable docs | Claude | | 2026-02-07 | Added missing CSS variable documentation | GitHub Copilot | | 2026-01-30 | Initial CSS variables reference documentation created | GitHub Copilot | diff --git a/src/language/en-GB/tpl_mokocassiopeia.ini b/src/language/en-GB/tpl_mokocassiopeia.ini index b7ff1bd..52d7017 100644 --- a/src/language/en-GB/tpl_mokocassiopeia.ini +++ b/src/language/en-GB/tpl_mokocassiopeia.ini @@ -148,6 +148,12 @@ TPL_MOKOCASSIOPEIA_CSS_VARS_COLORS_DESC="Named colours
.card:nth-child(1), +.container-top-b > .card:nth-child(1), +.container-bottom-a > .card:nth-child(1), +.container-bottom-b > .card:nth-child(1) { + background-color: var(--block-color-1); + color: var(--block-text-1); +} + +.container-top-a > .card:nth-child(2), +.container-top-b > .card:nth-child(2), +.container-bottom-a > .card:nth-child(2), +.container-bottom-b > .card:nth-child(2) { + background-color: var(--block-color-2); + color: var(--block-text-2); +} + +.container-top-a > .card:nth-child(3), +.container-top-b > .card:nth-child(3), +.container-bottom-a > .card:nth-child(3), +.container-bottom-b > .card:nth-child(3) { + background-color: var(--block-color-3); + color: var(--block-text-3); +} + +.container-top-a > .card:nth-child(4), +.container-top-b > .card:nth-child(4), +.container-bottom-a > .card:nth-child(4), +.container-bottom-b > .card:nth-child(4) { + background-color: var(--block-color-4); + color: var(--block-text-4); +} + +/* ── BLOCK COLOR — Named per-module overrides ── */ +.container-top-a #block-highlight, +.container-top-b #block-highlight, +.container-bottom-a #block-highlight, +.container-bottom-b #block-highlight { + background-color: var(--block-highlight-bg); + color: var(--block-highlight-text); +} + +.container-top-a #block-cta, +.container-top-b #block-cta, +.container-bottom-a #block-cta, +.container-bottom-b #block-cta { + background-color: var(--block-cta-bg); + color: var(--block-cta-text); +} + +.container-top-a #block-alert, +.container-top-b #block-alert, +.container-bottom-a #block-alert, +.container-bottom-b #block-alert { + background-color: var(--block-alert-bg); + color: var(--block-alert-text); +} + .container-component nav { position: relative; } diff --git a/src/sync_custom_vars.php b/src/sync_custom_vars.php new file mode 100644 index 0000000..a71672c --- /dev/null +++ b/src/sync_custom_vars.php @@ -0,0 +1,406 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + * + * CSS Variable Sync Utility + * + * Compares a user's custom palette file against the template starter file and + * injects any missing CSS variable declarations. Existing user values are + * never overwritten — only genuinely new variables are added. + * + * Usage (CLI): + * php sync_custom_vars.php + * + * Usage (from Joomla script.php or plugin): + * require_once __DIR__ . '/sync_custom_vars.php'; + * MokoCssVarSync::run(); + * + * The script auto-detects Joomla's root by walking up from __DIR__. + */ + +defined('_JEXEC') or define('MOKO_CLI', true); + +final class MokoCssVarSync +{ + /** + * Template name used in Joomla's media path. + */ + private const TPL = 'mokocassiopeia'; + + /** + * Palette pairs: [starter template path relative to this file, user file relative to Joomla root]. + */ + private const PALETTES = [ + [ + 'starter' => 'templates/light.custom.css', + 'user' => 'media/templates/site/%s/css/theme/light.custom.css', + ], + [ + 'starter' => 'templates/dark.custom.css', + 'user' => 'media/templates/site/%s/css/theme/dark.custom.css', + ], + ]; + + /** + * Run the sync for all palette pairs. + * + * @param string|null $joomlaRoot Absolute path to Joomla root (auto-detected if null). + * @return array Results keyed by file path. + */ + public static function run(?string $joomlaRoot = null): array + { + $tplDir = self::resolveTemplateDir(); + $root = $joomlaRoot ?? self::detectJoomlaRoot(); + + $results = []; + + foreach (self::PALETTES as $pair) { + $starterPath = $tplDir . '/' . $pair['starter']; + $userPath = $root . '/' . sprintf($pair['user'], self::TPL); + + if (!is_file($starterPath)) { + self::log("SKIP starter not found: {$starterPath}"); + continue; + } + + if (!is_file($userPath)) { + self::log("SKIP user file not found (custom palette not deployed): {$userPath}"); + continue; + } + + $result = self::syncFile($starterPath, $userPath); + $results[$userPath] = $result; + + $addedCount = count($result['added']); + if ($addedCount > 0) { + self::log("ADDED {$addedCount} variable(s) to {$userPath}"); + foreach ($result['added'] as $var) { + self::log(" + {$var}"); + } + } else { + self::log("OK {$userPath} — all variables present"); + } + } + + return $results; + } + + /** + * Compare a starter file against a user file and inject missing variables. + * + * @param string $starterPath Absolute path to the starter template CSS. + * @param string $userPath Absolute path to the user's custom CSS. + * @return array{added: string[], skipped: string[]} + */ + private static function syncFile(string $starterPath, string $userPath): array + { + $starterVars = self::extractVarsWithContext($starterPath); + $userVars = self::extractVarNames($userPath); + + $missing = []; + foreach ($starterVars as $name => $declaration) { + if (!isset($userVars[$name])) { + $missing[$name] = $declaration; + } + } + + if (empty($missing)) { + return ['added' => [], 'skipped' => []]; + } + + // Group missing variables by their section comment header. + $sections = self::groupBySection($missing, $starterPath); + + // Build the injection block. + $injection = self::buildInjectionBlock($sections); + + // Insert before the closing } of the :root rule. + $userCss = file_get_contents($userPath); + $userCss = self::injectBeforeRootClose($userCss, $injection); + + // Write back (atomic: write to .tmp then rename). + $tmpPath = $userPath . '.tmp'; + file_put_contents($tmpPath, $userCss); + rename($tmpPath, $userPath); + + return ['added' => array_keys($missing), 'skipped' => []]; + } + + /** + * Extract CSS custom property declarations with their full text (name: value). + * Only extracts from the first :root block. + * + * @return array Variable name => full declaration line. + */ + private static function extractVarsWithContext(string $filePath): array + { + $css = file_get_contents($filePath); + $vars = []; + + // Match --variable-name: value (possibly spanning multiple lines until ;) + if (preg_match_all('/^\s*(--[\w-]+)\s*:\s*([^;]+);/m', $css, $matches, PREG_SET_ORDER)) { + foreach ($matches as $m) { + $name = trim($m[1]); + $value = trim($m[2]); + $vars[$name] = "{$name}: {$value};"; + } + } + + return $vars; + } + + /** + * Extract just the variable names present in a CSS file. + * + * @return array + */ + private static function extractVarNames(string $filePath): array + { + $css = file_get_contents($filePath); + $vars = []; + + if (preg_match_all('/^\s*(--[\w-]+)\s*:/m', $css, $matches)) { + foreach ($matches[1] as $name) { + $vars[trim($name)] = true; + } + } + + return $vars; + } + + /** + * Group missing variables by the section comment they appear under in the starter file. + * + * @param array $missing Variable name => declaration. + * @param string $starterPath Path to starter file. + * @return array Section header => list of declarations. + */ + private static function groupBySection(array $missing, string $starterPath): array + { + $lines = file($starterPath, FILE_IGNORE_NEW_LINES); + $section = 'Uncategorised'; + $map = []; // variable name => section + + foreach ($lines as $line) { + // Detect section comment headers like /* ===== HERO VARIANTS ===== */ + if (preg_match('/\/\*\s*=+\s*(.+?)\s*=+\s*\*\//', $line, $m)) { + $section = trim($m[1]); + } + // Detect variable declaration + if (preg_match('/^\s*(--[\w-]+)\s*:/', $line, $m)) { + $name = trim($m[1]); + if (isset($missing[$name])) { + $map[$name] = $section; + } + } + } + + // Group by section + $sections = []; + foreach ($missing as $name => $declaration) { + $sec = $map[$name] ?? 'Uncategorised'; + $sections[$sec][] = $declaration; + } + + return $sections; + } + + /** + * Build a CSS block from grouped sections ready for injection. + */ + private static function buildInjectionBlock(array $sections): string + { + $lines = []; + $lines[] = ''; + $lines[] = '/* ===== VARIABLES ADDED BY SYNC (' . date('Y-m-d') . ') ===== */'; + + foreach ($sections as $sectionName => $declarations) { + $lines[] = ''; + $lines[] = "/* -- {$sectionName} -- */"; + foreach ($declarations as $decl) { + $lines[] = $decl; + } + } + + $lines[] = ''; + + return implode("\n", $lines); + } + + /** + * Inject a block of CSS just before the closing } of the :root[data-bs-theme] rule. + */ + private static function injectBeforeRootClose(string $css, string $block): string + { + // Find the :root block's closing brace. The :root rule is the first major + // rule in the file; its closing } is on its own line. + // Strategy: find the LAST } that is preceded only by CSS variable content. + // More robustly: find the first } that appears on its own line (possibly + // with whitespace), which closes the :root block. + + // Walk backwards from each } to see if it's inside the :root block. + // Simple approach: the :root closing } is the first bare } on its own line. + $pos = self::findRootClosingBrace($css); + + if ($pos === false) { + // Fallback: append before last } + $pos = strrpos($css, '}'); + } + + if ($pos === false) { + // Last resort: append to end + return $css . $block; + } + + return substr($css, 0, $pos) . $block . substr($css, $pos); + } + + /** + * Find the byte position of the closing } for the :root rule. + */ + private static function findRootClosingBrace(string $css): int|false + { + // Find where :root starts + $rootStart = preg_match('/:root\b/', $css, $m, PREG_OFFSET_CAPTURE); + if (!$rootStart) { + return false; + } + + $offset = $m[0][1]; + $depth = 0; + $len = strlen($css); + + for ($i = $offset; $i < $len; $i++) { + if ($css[$i] === '{') { + $depth++; + } elseif ($css[$i] === '}') { + $depth--; + if ($depth === 0) { + return $i; + } + } + } + + return false; + } + + /** + * Resolve the template source directory (where this file lives). + */ + private static function resolveTemplateDir(): string + { + return dirname(__FILE__); + } + + /** + * Auto-detect Joomla root by walking up from template dir looking for + * configuration.php or the media/templates directory structure. + */ + private static function detectJoomlaRoot(): string + { + $dir = dirname(__FILE__); + + // Walk up max 10 levels + for ($i = 0; $i < 10; $i++) { + if (is_file($dir . '/configuration.php')) { + return $dir; + } + // Also check for the media/templates structure (works in dev too) + if (is_dir($dir . '/media/templates')) { + return $dir; + } + $parent = dirname($dir); + if ($parent === $dir) { + break; + } + $dir = $parent; + } + + // Fallback for dev: if JPATH_ROOT is defined, use it + if (defined('JPATH_ROOT')) { + return JPATH_ROOT; + } + + self::log('WARNING: Could not auto-detect Joomla root. Pass it explicitly.'); + return dirname(__FILE__); + } + + /** + * Log a message (CLI: stdout, web: Joomla enqueueMessage if available). + */ + private static function log(string $message): void + { + if (defined('MOKO_CLI') || PHP_SAPI === 'cli') { + echo $message . PHP_EOL; + } + } + + /** + * Dry-run mode: report what would be added without writing. + * + * @return array File path => list of missing variable names. + */ + public static function dryRun(?string $joomlaRoot = null): array + { + $tplDir = self::resolveTemplateDir(); + $root = $joomlaRoot ?? self::detectJoomlaRoot(); + $report = []; + + foreach (self::PALETTES as $pair) { + $starterPath = $tplDir . '/' . $pair['starter']; + $userPath = $root . '/' . sprintf($pair['user'], self::TPL); + + if (!is_file($starterPath) || !is_file($userPath)) { + continue; + } + + $starterVars = self::extractVarsWithContext($starterPath); + $userVars = self::extractVarNames($userPath); + + $missing = []; + foreach ($starterVars as $name => $declaration) { + if (!isset($userVars[$name])) { + $missing[] = $name; + } + } + + if (!empty($missing)) { + $report[$userPath] = $missing; + } + } + + return $report; + } +} + +// CLI entry point +if (PHP_SAPI === 'cli' && realpath($argv[0] ?? '') === realpath(__FILE__)) { + $dryRun = in_array('--dry-run', $argv, true); + + echo "MokoCassiopeia CSS Variable Sync\n"; + echo str_repeat('─', 40) . "\n\n"; + + if ($dryRun) { + echo "DRY RUN — no files will be modified\n\n"; + $report = MokoCssVarSync::dryRun(); + if (empty($report)) { + echo "All custom palettes are up to date.\n"; + } else { + foreach ($report as $file => $vars) { + echo "MISSING in {$file}:\n"; + foreach ($vars as $var) { + echo " - {$var}\n"; + } + echo "\n"; + } + } + } else { + MokoCssVarSync::run(); + } + + echo "\nDone.\n"; +} diff --git a/src/templateDetails.xml b/src/templateDetails.xml index 3301e86..43ed88b 100644 --- a/src/templateDetails.xml +++ b/src/templateDetails.xml @@ -264,6 +264,8 @@ + + diff --git a/src/templates/dark.custom.css b/src/templates/dark.custom.css index a5b2781..fea9660 100644 --- a/src/templates/dark.custom.css +++ b/src/templates/dark.custom.css @@ -512,6 +512,40 @@ color-scheme: dark; --vm-vendor-menu-link-active: var(--primary); --vm-vendor-menu-hover-bg: var(--tertiary-bg); +/* ===== HERO VARIANTS ===== */ +/* Primary — deep navy, dark overlay */ +--hero-primary-bg-color: #0d1e3a; +--hero-primary-overlay: linear-gradient(rgba(13, 30, 58, .65), rgba(13, 30, 58, .65)); +--hero-primary-color: #f1f5f9; + +/* Secondary — darker navy, heavier overlay */ +--hero-secondary-bg-color: #080f1e; +--hero-secondary-overlay: linear-gradient(rgba(8, 15, 30, .80), rgba(8, 15, 30, .80)); +--hero-secondary-color: #f1f5f9; + +/* ===== BLOCK COLORS (top-a / top-b / bottom-a / bottom-b) ===== */ +--block-color-1: var(--secondary-bg); +--block-text-1: var(--body-color); + +--block-color-2: var(--accent-color-primary); +--block-text-2: #fff; + +--block-color-3: rgba(238, 194, 52, .15); +--block-text-3: var(--body-color); + +--block-color-4: rgba(74, 166, 100, .15); +--block-text-4: var(--body-color); + +/* ===== BLOCK COLOR OVERRIDES ===== */ +--block-highlight-bg: var(--accent-color-primary); +--block-highlight-text: #fff; + +--block-cta-bg: var(--color-primary); +--block-cta-text: #f1f5f9; + +--block-alert-bg: var(--danger, #c23a31); +--block-alert-text: #fff; + /* ===== GABLE ===== */ --gab-blue: #4d9fff; --gab-green: #5cb85c; diff --git a/src/templates/light.custom.css b/src/templates/light.custom.css index 5fe651c..378830d 100644 --- a/src/templates/light.custom.css +++ b/src/templates/light.custom.css @@ -511,6 +511,40 @@ color-scheme: light; --vm-vendor-menu-link-active: var(--primary); --vm-vendor-menu-hover-bg: var(--secondary-bg); +/* ===== HERO VARIANTS ===== */ +/* Primary — sky blue, light overlay */ +--hero-primary-bg-color: var(--color-primary); +--hero-primary-overlay: linear-gradient(rgba(163, 205, 226, .45), rgba(163, 205, 226, .45)); +--hero-primary-color: var(--color-primary); + +/* Secondary — navy, stronger overlay */ +--hero-secondary-bg-color: var(--color-primary); +--hero-secondary-overlay: linear-gradient(rgba(17, 40, 85, .75), rgba(17, 40, 85, .75)); +--hero-secondary-color: #f1f5f9; + +/* ===== BLOCK COLORS (top-a / top-b / bottom-a / bottom-b) ===== */ +--block-color-1: var(--color-primary); +--block-text-1: var(--body-color); + +--block-color-2: var(--accent-color-primary); +--block-text-2: #fff; + +--block-color-3: var(--warning, #eec234); +--block-text-3: var(--body-color); + +--block-color-4: var(--success-bg-subtle, #eef7f0); +--block-text-4: var(--body-color); + +/* ===== BLOCK COLOR OVERRIDES ===== */ +--block-highlight-bg: var(--accent-color-primary); +--block-highlight-text: #fff; + +--block-cta-bg: var(--color-primary); +--block-cta-text: #fff; + +--block-alert-bg: var(--danger, #a51f18); +--block-alert-text: #fff; + /* ===== GABLE ===== */ --gab-blue: #0066cc; --gab-green: #28a745; diff --git a/src/templates/theme-test.html b/src/templates/theme-test.html new file mode 100644 index 0000000..04a248e --- /dev/null +++ b/src/templates/theme-test.html @@ -0,0 +1,611 @@ + + + + + + +MokoCassiopeia — Theme Test Sheet + + + + + + + + +
+ +
+ +
+ + +

MokoCassiopeia Theme Test Sheet

+

Visual reference for CSS variables, Bootstrap components, hero variants, and block color system. Toggle light/dark mode with the button in the top-right corner.

+ +
+ + +

1. Brand & Theme Colors

+
+
+
+
--color-primary
+
+
+
+
--accent-color-primary
+
+
+
+
--accent-color-secondary
+
+
+
+
--body-bg
+
+
+
+
--body-color
+
+
+
+
--secondary-bg
+
+
+
+
--tertiary-bg
+
+
+
+
--border-color
+
+
+ + +

2. Bootstrap Color Palette

+
+
+
+
--primary
+
+
+
+
--secondary
+
+
+
+
--success
+
+
+
+
--info
+
+
+
+
--warning
+
+
+
+
--danger
+
+
+
+
--light
+
+
+
+
--dark
+
+
+ + +

3. Gray Scale

+
+
+
+
--gray-100
+
+
+
+
--gray-200
+
+
+
+
--gray-300
+
+
+
+
--gray-400
+
+
+
+
--gray-500
+
+
+
+
--gray-600
+
+
+
+
--gray-700
+
+
+
+
--gray-800
+
+
+
+
--gray-900
+
+
+ + +

4. Standard Colors

+
+
+
+
--blue
+
+
+
+
--indigo
+
+
+
+
--purple
+
+
+
+
--pink
+
+
+
+
--red
+
+
+
+
--orange
+
+
+
+
--yellow
+
+
+
+
--green
+
+
+
+
--teal
+
+
+
+
--cyan
+
+
+ + +

5. Typography

+
+

Heading 1 h1

+

Heading 2 h2

+

Heading 3 h3

+

Heading 4 h4

+
Heading 5 h5
+
Heading 6 h6
+
+

This is regular body text using --body-color on --body-bg. Font family: --body-font-family. Size: --body-font-size (1rem).

+

Bold text. Italic text. This is a link. Inline code. Highlighted text.

+

This is lead text styled with --muted-color.

+ + +

6. Link Colors

+ + + + + + +
VariablePreview
--link-colorSample link
--link-hover-colorHover state
--color-linkcolor-link value
--color-hovercolor-hover value
+ + +

7. Buttons

+
+ + + + + + + + +
+
+ + + + +
+ + +

8. Cards

+
+
+
+
Card Header
+
+
Card Title
+

Card body using --card-bg, --card-color, and --card-border-color.

+ +
+
+
+
+
+
Simple Card
+

No header, just body content. Uses the same card variables.

+
+
+
+ + +

9. Form Elements

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +

10. Alerts

+
+ Primary alert. Uses --primary-bg-subtle and --primary-text-emphasis. +
+
+ Success alert. Uses --success-bg-subtle and --success-text-emphasis. +
+
+ Warning alert. Uses --warning-bg-subtle and --warning-text-emphasis. +
+
+ Danger alert. Uses --danger-bg-subtle and --danger-text-emphasis. +
+
+ Info alert. Uses --info-bg-subtle and --info-text-emphasis. +
+ + +

11. Borders & Shadows

+
+
+ Default border: --border-width / --border-color / --border-radius +
+
+ --box-shadow-sm +
+
+ --box-shadow +
+
+ --box-shadow-lg +
+
+ + +

12. Navigation Colors

+
+
+
+
--nav-bg-color
+
+
+
+
--nav-text-color
+
+
+
+
--mainmenu-nav-link-color
+
+
+ + +

13. Container Background Variables

+ + + + + + + + +
ContainerBG ColorBG ImageBorder
below-topbar--container-below-topbar-bg-color--container-below-topbar-bg-image--container-below-topbar-border
top-a--container-top-a-bg-color--container-top-a-bg-image--container-top-a-border
top-b--container-top-b-bg-color--container-top-b-bg-image--container-top-b-border
bottom-a--container-bottom-a-bg-color--container-bottom-a-bg-image--container-bottom-a-border
bottom-b--container-bottom-b-bg-color--container-bottom-b-bg-image--container-bottom-b-border
sidebar--container-sidebar-bg-color--container-sidebar-bg-image--container-sidebar-border
+ + +

14. Hero Variants NEW

+

The .hero#primary and .hero#secondary variants use CSS variables for background color, overlay gradient, and text color. Each adapts automatically with the active theme.

+ +

Primary Variant — .hero#primary

+
+
+

Primary Hero

+

Homepage & main landing pages — sky blue tint, softer overlay

+
+
+ +

Secondary Variant — .hero#secondary

+
+
+

Secondary Hero

+

Inner pages, events, about — navy overlay, lighter text

+
+
+ +

Hero Variable Reference

+ + + + + + + + +
VariableVariantPurpose
--hero-primary-bg-colorPrimaryFallback background color
--hero-primary-overlayPrimaryGradient overlay tint
--hero-primary-colorPrimaryText color
--hero-secondary-bg-colorSecondaryFallback background color
--hero-secondary-overlaySecondaryGradient overlay tint
--hero-secondary-colorSecondaryText color
+ + +

15. Block Color System NEW

+

Modules in top-a, top-b, bottom-a, and bottom-b positions automatically receive brand colors based on their order. No classes needed — :nth-child() handles assignment.

+ +

Slot Palette Preview

+
+
+ Slot 1
+ --block-color-1 +
+
+ Slot 2
+ --block-color-2 +
+
+ Slot 3
+ --block-color-3 +
+
+ Slot 4
+ --block-color-4 +
+
+ +

Named Override Preview

+
+
+ #block-highlight
+ --block-highlight-bg +
+
+ #block-cta
+ --block-cta-bg +
+
+ #block-alert
+ --block-alert-bg +
+
+ +

Block Variable Reference

+ + + + + + + + + +
VariablePurpose
--block-color-1 / --block-text-11st module in position (automatic)
--block-color-2 / --block-text-22nd module in position (automatic)
--block-color-3 / --block-text-33rd module in position (automatic)
--block-color-4 / --block-text-44th module in position (automatic)
--block-highlight-bg / --block-highlight-textNamed override for #block-highlight
--block-cta-bg / --block-cta-textNamed override for #block-cta
--block-alert-bg / --block-alert-textNamed override for #block-alert
+ +

Override Priority

+ + + + +
PriorityMethodHow Applied
1 (highest)Named module ID (#block-highlight)ID in module HTML + named variable
2 (default)Slot color (--block-color-N)Automatic by :nth-child() order
+ + +

16. VirtueMart Surface Colors

+
+
+
+
--vm-surface
+
+
+
+
--vm-surface-2
+
+
+
+
--vm-price-color
+
+
+ + +

17. Gable Colors

+
+
+
+
--gab-blue
+
+
+
+
--gab-green
+
+
+
+
--gab-red
+
+
+
+
--gab-orange
+
+
+ + +

18. Code & Preformatted Text

+

Inline code: var(--color-primary)

+
/* Example: overriding block slot 1 in colors_custom.css */
+--block-color-1: var(--accent-color-primary);
+--block-text-1:  #fff;
+
+/* Hero variant usage in module HTML */
+<div class="hero" id="primary"
+  style="background-image:url('/images/hero/main.jpg')">
+  <div class="col-12 py-5 px-4 text-center">
+    ...content...
+  </div>
+</div>
+ + +

19. Opacity Scale

+
+
5%
+
10%
+
15%
+
25%
+
50%
+
75%
+
100%
+
+ +
+

+ MokoCassiopeia Theme Test Sheet — v03.09.02 — © 2026 Moko Consulting +

+ +
+ + + +