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' + ); + } } } }