diff --git a/src/packages/mod_mokojoomhero/language/en-GB/mod_mokojoomhero.ini b/src/packages/mod_mokojoomhero/language/en-GB/mod_mokojoomhero.ini
index 2d480a4..3accf02 100644
--- a/src/packages/mod_mokojoomhero/language/en-GB/mod_mokojoomhero.ini
+++ b/src/packages/mod_mokojoomhero/language/en-GB/mod_mokojoomhero.ini
@@ -27,6 +27,14 @@ MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
MOD_MOKOJOOMHERO_MODE_COLOR="Solid Colour"
MOD_MOKOJOOMHERO_MODE_GRADIENT="Gradient"
+; Transition type
+MOD_MOKOJOOMHERO_FADE_TYPE_LABEL="Transition Type"
+MOD_MOKOJOOMHERO_FADE_TYPE_DESC="How images transition between slides."
+MOD_MOKOJOOMHERO_FADE_CROSSFADE="Crossfade"
+MOD_MOKOJOOMHERO_FADE_SLIDE="Slide"
+MOD_MOKOJOOMHERO_FADE_BLACK="Fade to Black"
+MOD_MOKOJOOMHERO_FADE_ZOOM="Zoom (Ken Burns)"
+
; Image settings
MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL="Image Folder"
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
diff --git a/src/packages/mod_mokojoomhero/language/en-GB/mod_mokojoomhero.sys.ini b/src/packages/mod_mokojoomhero/language/en-GB/mod_mokojoomhero.sys.ini
index 0b8aeff..b4cb5e6 100644
--- a/src/packages/mod_mokojoomhero/language/en-GB/mod_mokojoomhero.sys.ini
+++ b/src/packages/mod_mokojoomhero/language/en-GB/mod_mokojoomhero.sys.ini
@@ -21,10 +21,20 @@ MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white backgro
; Hero mode
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
-MOD_MOKOJOOMHERO_MODE_DESC="Choose between a slideshow of images, an embedded video (YouTube/Vimeo), or a local video file."
+MOD_MOKOJOOMHERO_MODE_DESC="Choose between a slideshow of images, an embedded video (YouTube/Vimeo), a local video file, a solid colour, or a gradient."
MOD_MOKOJOOMHERO_MODE_IMAGES="Images"
MOD_MOKOJOOMHERO_MODE_VIDEO="Video (YouTube/Vimeo)"
MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
+MOD_MOKOJOOMHERO_MODE_COLOR="Solid Colour"
+MOD_MOKOJOOMHERO_MODE_GRADIENT="Gradient"
+
+; Transition type
+MOD_MOKOJOOMHERO_FADE_TYPE_LABEL="Transition Type"
+MOD_MOKOJOOMHERO_FADE_TYPE_DESC="How images transition between slides."
+MOD_MOKOJOOMHERO_FADE_CROSSFADE="Crossfade"
+MOD_MOKOJOOMHERO_FADE_SLIDE="Slide"
+MOD_MOKOJOOMHERO_FADE_BLACK="Fade to Black"
+MOD_MOKOJOOMHERO_FADE_ZOOM="Zoom (Ken Burns)"
; Image settings
MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL="Image Folder"
@@ -50,6 +60,18 @@ MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card
MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL="Show Mute Toggle"
MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC="Show a mute/unmute button on the hero video. Videos always start muted (required for autoplay)."
+; Solid colour background
+MOD_MOKOJOOMHERO_BG_COLOR_LABEL="Background Colour"
+MOD_MOKOJOOMHERO_BG_COLOR_DESC="Solid background colour for the hero section."
+
+; Gradient background
+MOD_MOKOJOOMHERO_GRADIENT_START_LABEL="Gradient Start Colour"
+MOD_MOKOJOOMHERO_GRADIENT_START_DESC="Starting colour of the gradient."
+MOD_MOKOJOOMHERO_GRADIENT_END_LABEL="Gradient End Colour"
+MOD_MOKOJOOMHERO_GRADIENT_END_DESC="Ending colour of the gradient."
+MOD_MOKOJOOMHERO_GRADIENT_ANGLE_LABEL="Gradient Angle"
+MOD_MOKOJOOMHERO_GRADIENT_ANGLE_DESC="Direction of the gradient in degrees (0 = bottom to top, 90 = left to right, 135 = diagonal)."
+
; Hero height
MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL="Hero Height"
MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC="Height of the hero section. Use px for fixed pixels (e.g. 400px) or vh for viewport height (e.g. 60vh for 60%% of screen)."
diff --git a/src/packages/mod_mokojoomhero/language/en-US/mod_mokojoomhero.ini b/src/packages/mod_mokojoomhero/language/en-US/mod_mokojoomhero.ini
index a6f1ba4..2c4ae6d 100644
--- a/src/packages/mod_mokojoomhero/language/en-US/mod_mokojoomhero.ini
+++ b/src/packages/mod_mokojoomhero/language/en-US/mod_mokojoomhero.ini
@@ -27,6 +27,14 @@ MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
MOD_MOKOJOOMHERO_MODE_COLOR="Solid Color"
MOD_MOKOJOOMHERO_MODE_GRADIENT="Gradient"
+; Transition type
+MOD_MOKOJOOMHERO_FADE_TYPE_LABEL="Transition Type"
+MOD_MOKOJOOMHERO_FADE_TYPE_DESC="How images transition between slides."
+MOD_MOKOJOOMHERO_FADE_CROSSFADE="Crossfade"
+MOD_MOKOJOOMHERO_FADE_SLIDE="Slide"
+MOD_MOKOJOOMHERO_FADE_BLACK="Fade to Black"
+MOD_MOKOJOOMHERO_FADE_ZOOM="Zoom (Ken Burns)"
+
; Image settings
MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL="Image Folder"
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
diff --git a/src/packages/mod_mokojoomhero/language/en-US/mod_mokojoomhero.sys.ini b/src/packages/mod_mokojoomhero/language/en-US/mod_mokojoomhero.sys.ini
index 18cc2ab..9bf627a 100644
--- a/src/packages/mod_mokojoomhero/language/en-US/mod_mokojoomhero.sys.ini
+++ b/src/packages/mod_mokojoomhero/language/en-US/mod_mokojoomhero.sys.ini
@@ -21,10 +21,20 @@ MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white backgro
; Hero mode
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
-MOD_MOKOJOOMHERO_MODE_DESC="Choose between a slideshow of images, an embedded video (YouTube/Vimeo), or a local video file."
+MOD_MOKOJOOMHERO_MODE_DESC="Choose between a slideshow of images, an embedded video (YouTube/Vimeo), a local video file, a solid color, or a gradient."
MOD_MOKOJOOMHERO_MODE_IMAGES="Images"
MOD_MOKOJOOMHERO_MODE_VIDEO="Video (YouTube/Vimeo)"
MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
+MOD_MOKOJOOMHERO_MODE_COLOR="Solid Color"
+MOD_MOKOJOOMHERO_MODE_GRADIENT="Gradient"
+
+; Transition type
+MOD_MOKOJOOMHERO_FADE_TYPE_LABEL="Transition Type"
+MOD_MOKOJOOMHERO_FADE_TYPE_DESC="How images transition between slides."
+MOD_MOKOJOOMHERO_FADE_CROSSFADE="Crossfade"
+MOD_MOKOJOOMHERO_FADE_SLIDE="Slide"
+MOD_MOKOJOOMHERO_FADE_BLACK="Fade to Black"
+MOD_MOKOJOOMHERO_FADE_ZOOM="Zoom (Ken Burns)"
; Image settings
MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL="Image Folder"
@@ -50,6 +60,18 @@ MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card
MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL="Show Mute Toggle"
MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC="Show a mute/unmute button on the hero video. Videos always start muted (required for autoplay)."
+; Solid color background
+MOD_MOKOJOOMHERO_BG_COLOR_LABEL="Background Color"
+MOD_MOKOJOOMHERO_BG_COLOR_DESC="Solid background color for the hero section."
+
+; Gradient background
+MOD_MOKOJOOMHERO_GRADIENT_START_LABEL="Gradient Start Color"
+MOD_MOKOJOOMHERO_GRADIENT_START_DESC="Starting color of the gradient."
+MOD_MOKOJOOMHERO_GRADIENT_END_LABEL="Gradient End Color"
+MOD_MOKOJOOMHERO_GRADIENT_END_DESC="Ending color of the gradient."
+MOD_MOKOJOOMHERO_GRADIENT_ANGLE_LABEL="Gradient Angle"
+MOD_MOKOJOOMHERO_GRADIENT_ANGLE_DESC="Direction of the gradient in degrees (0 = bottom to top, 90 = left to right, 135 = diagonal)."
+
; Hero height
MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL="Hero Height"
MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC="Height of the hero section. Use px for fixed pixels (e.g. 400px) or vh for viewport height (e.g. 60vh for 60% of screen)."
diff --git a/src/packages/mod_mokojoomhero/media/css/mod_mokojoomhero.css b/src/packages/mod_mokojoomhero/media/css/mod_mokojoomhero.css
index ffda708..7ffda51 100644
--- a/src/packages/mod_mokojoomhero/media/css/mod_mokojoomhero.css
+++ b/src/packages/mod_mokojoomhero/media/css/mod_mokojoomhero.css
@@ -6,9 +6,9 @@
* DEFGROUP: MokoJoomHero.Module.Assets
* INGROUP: MokoJoomHero.Module
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
- * PATH: /src/css/template.css
+ * PATH: /src/packages/mod_mokojoomhero/media/css/mod_mokojoomhero.css
* VERSION: 01.04.01
- * BRIEF: Hero module stylesheet — slideshow, video background, overlay
+ * BRIEF: Hero module stylesheet — slideshow, video, colour/gradient, overlay, card, mute toggle, responsive
*/
/* ============================================================
@@ -31,7 +31,7 @@
}
/* ============================================================
- Image slides
+ Image slides — base
============================================================ */
.mokojoomhero__slide {
position: absolute;
@@ -40,13 +40,51 @@
background-position: center;
background-repeat: no-repeat;
opacity: 0;
- transition: opacity 1s ease;
}
.mokojoomhero__slide--active {
opacity: 1;
}
+/* ── Crossfade (default) ── */
+.mokojoomhero[data-transition="crossfade"] .mokojoomhero__slide {
+ transition: opacity 1s ease;
+}
+
+/* ── Slide ── */
+.mokojoomhero[data-transition="slide"] .mokojoomhero__slide {
+ opacity: 1;
+ transform: translateX(100%);
+ transition: transform 0.8s ease;
+}
+
+.mokojoomhero[data-transition="slide"] .mokojoomhero__slide--active {
+ transform: translateX(0);
+}
+
+.mokojoomhero[data-transition="slide"] .mokojoomhero__slide--exit {
+ transform: translateX(-100%);
+}
+
+/* ── Fade to black ── */
+.mokojoomhero[data-transition="fade-black"] .mokojoomhero__slide {
+ transition: opacity 0.6s ease;
+}
+
+/* ── Zoom (Ken Burns) ── */
+.mokojoomhero[data-transition="zoom"] .mokojoomhero__slide {
+ transition: opacity 1s ease;
+}
+
+.mokojoomhero[data-transition="zoom"] .mokojoomhero__slide--active {
+ animation: mokojoomhero-zoom 8s ease forwards;
+}
+
+@keyframes mokojoomhero-zoom {
+ from { transform: scale(1); }
+ to { transform: scale(1.08); }
+}
+
/* ============================================================
Video background
============================================================ */
@@ -186,8 +224,17 @@ iframe.mokojoomhero__video {
display: none;
}
+ /* Keep colour/gradient backgrounds visible on mobile */
+ .mokojoomhero__color {
+ position: relative;
+ }
+
.mokojoomhero__overlay {
padding: 1rem;
+ }
+
+ /* Only clear overlay background when media is hidden (image/video modes) */
+ .mokojoomhero:not(:has(.mokojoomhero__color)) .mokojoomhero__overlay {
background-color: transparent !important;
}
diff --git a/src/packages/mod_mokojoomhero/media/js/mod_mokojoomhero.js b/src/packages/mod_mokojoomhero/media/js/mod_mokojoomhero.js
index c5ddbe8..17dbfdc 100644
--- a/src/packages/mod_mokojoomhero/media/js/mod_mokojoomhero.js
+++ b/src/packages/mod_mokojoomhero/media/js/mod_mokojoomhero.js
@@ -7,9 +7,9 @@
* DEFGROUP: MokoJoomHero.Module.Assets
* INGROUP: MokoJoomHero.Module
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
- * PATH: /src/js/template.js
+ * PATH: /src/packages/mod_mokojoomhero/media/js/mod_mokojoomhero.js
* VERSION: 01.04.01
- * BRIEF: Hero module JavaScript — image slideshow crossfade
+ * BRIEF: Hero module JavaScript — slideshow crossfade, video viewport control, mute toggle
*/
'use strict';
@@ -22,23 +22,54 @@ document.addEventListener('DOMContentLoaded', function () {
// ── Image slideshow ──
document.querySelectorAll('.mokojoomhero[data-slides]').forEach(function (hero) {
- var slides = hero.querySelectorAll('.mokojoomhero__slide');
- var interval = parseInt(hero.dataset.interval, 10) || 5000;
- var current = 0;
+ var slides = hero.querySelectorAll('.mokojoomhero__slide');
+ var interval = parseInt(hero.dataset.interval, 10) || 5000;
+ var transition = hero.dataset.transition || 'crossfade';
+ var current = 0;
if (slides.length < 2) {
return;
}
- setInterval(function () {
- slides[current].classList.remove('mokojoomhero__slide--active');
- slides[current].setAttribute('aria-hidden', 'true');
-
+ function advanceSlide() {
+ var prev = current;
current = (current + 1) % slides.length;
- slides[current].classList.add('mokojoomhero__slide--active');
- slides[current].setAttribute('aria-hidden', 'false');
- }, interval);
+ if (transition === 'slide') {
+ slides[prev].classList.add('mokojoomhero__slide--exit');
+ slides[prev].classList.remove('mokojoomhero__slide--active');
+ slides[prev].setAttribute('aria-hidden', 'true');
+
+ slides[current].classList.add('mokojoomhero__slide--active');
+ slides[current].setAttribute('aria-hidden', 'false');
+
+ // Reset exiting slide after transition completes
+ setTimeout(function () {
+ slides[prev].classList.remove('mokojoomhero__slide--exit');
+ }, 800);
+
+ } else if (transition === 'fade-black') {
+ // Phase 1: fade out current
+ slides[prev].classList.remove('mokojoomhero__slide--active');
+ slides[prev].setAttribute('aria-hidden', 'true');
+
+ // Phase 2: fade in next after a brief black gap
+ setTimeout(function () {
+ slides[current].classList.add('mokojoomhero__slide--active');
+ slides[current].setAttribute('aria-hidden', 'false');
+ }, 600);
+
+ } else {
+ // Crossfade and zoom use the same JS logic
+ slides[prev].classList.remove('mokojoomhero__slide--active');
+ slides[prev].setAttribute('aria-hidden', 'true');
+
+ slides[current].classList.add('mokojoomhero__slide--active');
+ slides[current].setAttribute('aria-hidden', 'false');
+ }
+ }
+
+ setInterval(advanceSlide, interval);
});
// ── Pause/resume videos when out of viewport ──
diff --git a/src/packages/mod_mokojoomhero/mod_mokojoomhero.php b/src/packages/mod_mokojoomhero/mod_mokojoomhero.php
index e264f9e..4ae2aa1 100644
--- a/src/packages/mod_mokojoomhero/mod_mokojoomhero.php
+++ b/src/packages/mod_mokojoomhero/mod_mokojoomhero.php
@@ -6,6 +6,7 @@
*
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GPL-3.0-or-later
+ * SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
@@ -33,8 +34,14 @@ $heroMode = $params->get('heroMode', 'images');
$imageFolder = $params->get('imageFolder', 'images/heroes');
$imageCount = (int) $params->get('imageCount', 5);
$slideInterval = (int) $params->get('slideInterval', 5000);
+$fadeType = $params->get('fadeType', 'crossfade');
$videoFile = $params->get('videoFile', '');
$heroHeight = $params->get('heroHeight', '60vh');
+
+// Validate heroHeight to prevent CSS injection
+if (!preg_match('/^\d+(\.\d+)?(px|vh|vw|em|rem|%)$/', $heroHeight)) {
+ $heroHeight = '60vh';
+}
$overlayColor = $params->get('overlayColor', '#000000');
$overlayOpacity = (float) $params->get('overlayOpacity', 0.5);
$textAlign = $params->get('textAlign', 'center');
@@ -98,7 +105,4 @@ if ($heroMode === 'localvideo' && $localVideoFile) {
}
}
-// Module content from the editor (overlay text)
-$content = $module->content ?? '';
-
require ModuleHelper::getLayoutPath('mod_mokojoomhero', $params->get('layout', 'default'));
diff --git a/src/packages/mod_mokojoomhero/mod_mokojoomhero.xml b/src/packages/mod_mokojoomhero/mod_mokojoomhero.xml
index 36a9544..3e47e1b 100644
--- a/src/packages/mod_mokojoomhero/mod_mokojoomhero.xml
+++ b/src/packages/mod_mokojoomhero/mod_mokojoomhero.xml
@@ -10,7 +10,7 @@
DEFGROUP: MokoJoomHero.Module
INGROUP: MokoJoomHero
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
- PATH: /src/mod_mokojoomhero.xml
+ PATH: /src/packages/mod_mokojoomhero/mod_mokojoomhero.xml
VERSION: 01.00.20
BRIEF: Joomla module manifest — random hero image with content overlay
-->
@@ -99,6 +99,19 @@
step="15"
showon="heroMode:gradient"
/>
+
+
+
+
+
+
id;
// Convert hex overlay colour to rgba
@@ -47,6 +45,7 @@ $heightAttr = htmlspecialchars($heroHeight, ENT_QUOTES, 'UTF-8');
1) : ?>
data-slides=""
data-interval=""
+ data-transition=""
>
diff --git a/src/packages/plg_system_mokojoomhero/mokojoomhero.xml b/src/packages/plg_system_mokojoomhero/mokojoomhero.xml
index e8782d2..d03d5cc 100644
--- a/src/packages/plg_system_mokojoomhero/mokojoomhero.xml
+++ b/src/packages/plg_system_mokojoomhero/mokojoomhero.xml
@@ -7,7 +7,7 @@
* @license GNU General Public License version 3 or later; see LICENSE
-->
- plg_system_mokojoomhero
+ PLG_SYSTEM_MOKOJOOMHERO
01.04.01-dev
2026-06-02
Moko Consulting
@@ -25,10 +25,10 @@
src
-
- language/en-GB/plg_system_mokojoomhero.ini
- language/en-GB/plg_system_mokojoomhero.sys.ini
- language/en-US/plg_system_mokojoomhero.ini
- language/en-US/plg_system_mokojoomhero.sys.ini
+
+ en-GB/plg_system_mokojoomhero.ini
+ en-GB/plg_system_mokojoomhero.sys.ini
+ en-US/plg_system_mokojoomhero.ini
+ en-US/plg_system_mokojoomhero.sys.ini
diff --git a/src/packages/plg_system_mokojoomhero/src/Extension/MokoJoomHero.php b/src/packages/plg_system_mokojoomhero/src/Extension/MokoJoomHero.php
index 810af7f..85be0eb 100644
--- a/src/packages/plg_system_mokojoomhero/src/Extension/MokoJoomHero.php
+++ b/src/packages/plg_system_mokojoomhero/src/Extension/MokoJoomHero.php
@@ -83,8 +83,12 @@ class MokoJoomHero extends CMSPlugin implements SubscriberInterface
. 'for the MokoJoomHero update site.',
'warning'
);
- } catch (\Throwable $e) {
- // Don't break admin over a license check
+ } catch (\Exception $e) {
+ // Don't break admin over a license check — log for diagnostics
+ $this->getApplication()->getLogger()->warning(
+ 'MokoJoomHero license check failed: ' . $e->getMessage(),
+ ['exception' => $e]
+ );
}
}
}
diff --git a/src/pkg_script.php b/src/pkg_script.php
index 1901b3a..5976c07 100644
--- a/src/pkg_script.php
+++ b/src/pkg_script.php
@@ -16,7 +16,7 @@ use Joomla\CMS\Installer\InstallerAdapter;
class Pkg_MokoJoomHeroInstallerScript
{
/**
- * Called after install/update.
+ * Called after install/update — only enables the system plugin on fresh install.
*
* @param string $type Action type
* @param InstallerAdapter $parent Installer adapter
@@ -26,18 +26,34 @@ class Pkg_MokoJoomHeroInstallerScript
public function postflight(string $type, InstallerAdapter $parent): void
{
if ($type === 'install') {
- $db = Factory::getDbo();
+ try {
+ $db = Factory::getDbo();
- // Enable the system plugin automatically on fresh install
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__extensions'))
- ->set($db->quoteName('enabled') . ' = 1')
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('mokojoomhero'));
+ // Enable the system plugin automatically on fresh install
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__extensions'))
+ ->set($db->quoteName('enabled') . ' = 1')
+ ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
+ ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
+ ->where($db->quoteName('element') . ' = ' . $db->quote('mokojoomhero'));
- $db->setQuery($query);
- $db->execute();
+ $db->setQuery($query);
+ $db->execute();
+
+ if ($db->getAffectedRows() === 0) {
+ Factory::getApplication()->enqueueMessage(
+ 'MokoJoomHero: The system plugin could not be auto-enabled. '
+ . 'Please enable it manually in Extensions → Plugins.',
+ 'warning'
+ );
+ }
+ } catch (\Exception $e) {
+ Factory::getApplication()->enqueueMessage(
+ 'MokoJoomHero: Failed to auto-enable system plugin: ' . $e->getMessage()
+ . ' — Please enable it manually in Extensions → Plugins.',
+ 'warning'
+ );
+ }
}
}
}