Compare commits

...

81 Commits

Author SHA1 Message Date
gitea-actions[bot] 692f7ab0ef chore(release): build 01.16.00 [skip ci] 2026-06-04 18:13:52 +00:00
jmiller 636d568fe2 Merge pull request 'feat: auto-remove deprecated system plugin on install/upgrade' (#66) from dev into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-04 18:13:42 +00:00
Jonathan Miller f05c30b662 feat: auto-remove deprecated system plugin on install/upgrade
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 5s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 7s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 9s
The install script now uninstalls plg_system_mokojoomhero if present,
cleaning up the old package extension remnant. Uses Joomla Installer
for proper DB and file cleanup. Non-critical — silently skips if the
plugin is already gone.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 13:09:27 -05:00
gitea-actions[bot] 03e29b3945 chore(release): build 01.15.00 [skip ci] 2026-06-04 17:55:48 +00:00
jmiller 8ced44cf1d Merge pull request 'docs: consolidate changelog for all minor versions' (#65) from dev into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-04 17:55:36 +00:00
Jonathan Miller ed2bbebd3e chore: merge main into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 7s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 11s
2026-06-04 12:55:08 -05:00
Jonathan Miller 11dbf4d785 docs: consolidate changelog with release notes for all minor versions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 6s
Universal: Build & Release / Promote to RC (pull_request) Failing after 8s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 12:34:46 -05:00
gitea-actions[bot] 96ae87b19d chore(release): build 01.14.00 [skip ci] 2026-06-04 17:16:56 +00:00
jmiller 8cb2ea30a1 Merge pull request 'fix: update server URL and pretty name in manifest' (#64) from dev into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-04 17:16:41 +00:00
Jonathan Miller 9a62d9d7cc fix: use pretty name for update server
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 5s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 5s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 7s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 15s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 12:14:36 -05:00
Jonathan Miller cf1fb26390 fix: update server URL to new Gitea release system
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Change update server from raw/branch/main/updates.xml to the new
Gitea-managed updates.xml endpoint.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 12:13:44 -05:00
Jonathan Miller a7cf29d220 fix: add updateservers to module manifest
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
The updateservers section was only in the old package manifest and
was lost during the module restructure. Without it Joomla cannot
discover updates.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 12:10:52 -05:00
gitea-actions[bot] e978090bb8 chore(release): build 01.13.00 [skip ci] 2026-06-04 16:31:26 +00:00
jmiller 7f7dd60a4a Merge pull request 'refactor: restructure from package back to standalone module' (#63) from dev into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-04 16:31:10 +00:00
Jonathan Miller e711bb4fa0 chore: merge main into dev, resolve conflicts for module restructure
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 5s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 11s
2026-06-04 11:30:38 -05:00
Jonathan Miller ea9ea5c3a7 refactor: restructure from package back to standalone module
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 0s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 3s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 5s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Universal: Build & Release / Promote to RC (pull_request) Failing after 8s
Remove plg_system_mokojoomhero and package wrapper. Move module files
from src/packages/mod_mokojoomhero/ back to src/. Update Makefile
build target for module ZIP. Update CLAUDE.md and file PATH headers.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 11:07:42 -05:00
jmiller 49f9a762df chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:57:44 +00:00
jmiller a6e9302730 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:56:08 +00:00
Jonathan Miller ce498da605 chore: update creationDate to 2026-06-04 in all manifests
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 10:48:45 -05:00
jmiller d46325e13b chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:40:21 +00:00
jmiller 69362e0aca chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:38:16 +00:00
gitea-actions[bot] 84b3b9acb1 chore(release): build 01.12.00 [skip ci] 2026-06-04 15:36:31 +00:00
jmiller 9f0c01739f Merge pull request 'chore: remove updates.xml - update server migrated' (#62) from dev into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-04 15:36:20 +00:00
Jonathan Miller a7eabcf9d7 chore: remove updates.xml and update.xml — update server migrated
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 5s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 6s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 10s
Update server now managed externally. Remove local updates.xml and
legacy update.xml files.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 10:34:20 -05:00
jmiller 724c4d61f4 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:31:37 +00:00
jmiller 35cb0a988c chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:28:39 +00:00
jmiller 9e27c5c167 chore: remove static updates.xml [skip ci] 2026-06-04 15:22:59 +00:00
jmiller f56bfa6c36 chore: sync updates.xml 01.11.00 from main [skip ci] 2026-06-04 15:22:44 +00:00
gitea-actions[bot] f4dfcc4c7e chore: update channels for 01.11.00 [skip ci] 2026-06-04 15:22:43 +00:00
gitea-actions[bot] f9187cd816 chore(release): build 01.11.00 [skip ci] 2026-06-04 15:22:41 +00:00
jmiller a9d4534f0b Merge pull request 'chore: clean up updates.xml for stable 01.10.00' (#61) from dev into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-04 15:22:33 +00:00
Jonathan Miller a922ef3033 chore: merge main into dev, resolve updates.xml conflicts
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Joomla: Extension CI / Release Readiness Check (pull_request) Successful in 4s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 5s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 11s
2026-06-04 10:21:47 -05:00
jmiller 9fbc273658 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:18:27 +00:00
Jonathan Miller 95319c0513 chore: clean up updates.xml — remove stale entries, keep stable + legacy migration
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 5s
Universal: Build & Release / Promote to RC (pull_request) Failing after 7s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Remove old dev/alpha/beta/rc entries. Keep current stable 01.10.00
package entry and legacy mod_mokojoomhero entry pointing to the
package for migration from old standalone module installs.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 10:14:36 -05:00
jmiller 10412c1120 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:13:06 +00:00
gitea-actions[bot] ab82a8f810 chore: update channels for 01.10.00 [skip ci] 2026-06-04 15:03:47 +00:00
jmiller 19db4cb637 chore: sync updates.xml 01.10.00 from main [skip ci] 2026-06-04 15:03:47 +00:00
gitea-actions[bot] 28bc4f042a chore(release): build 01.10.00 [skip ci] 2026-06-04 15:03:45 +00:00
jmiller 9de47e9fe2 Merge pull request 'feat: scheduling, A/B testing, repo metadata update' (#60) from dev into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-04 15:03:36 +00:00
Jonathan Miller af3acc6fd2 feat: scheduling and A/B testing (#21, #22)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 11s
Add hero scheduling with start/end datetime fields using Joomla
calendar type and site timezone. Hero skips rendering outside the
configured range. Add A/B testing with weighted random variation
selection, session-sticky per module instance via subform repeatable.
Close #23 as substantially complete (animation library implemented
in prior phases). Language strings in all locales.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 09:59:04 -05:00
jmiller ad0c5bc51c feat(update): migrate update server URL to Gitea Pages [skip ci] 2026-06-04 14:33:21 +00:00
jmiller fdf07d833f feat(update): migrate update server URL to Gitea Pages [skip ci] 2026-06-04 14:33:15 +00:00
jmiller 765086d7a0 chore: sync updates.xml 01.09.00 from main [skip ci] 2026-06-04 14:31:56 +00:00
gitea-actions[bot] 9e729c059d chore: update channels for 01.09.00 [skip ci] 2026-06-04 14:31:55 +00:00
gitea-actions[bot] d708ac995d chore(release): build 01.09.00 [skip ci] 2026-06-04 14:31:53 +00:00
jmiller 8895ba9ffb Merge pull request 'feat: restore licensing system with free tier' (#59) from dev into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Merge PR #59: restore licensing system with free tier
2026-06-04 14:31:41 +00:00
jmiller 2f88eb50fb chore: sync .mokogitea/workflows/auto-release.yml from moko-platform [skip ci] 2026-06-04 14:22:50 +00:00
jmiller d22ba60eea chore: sync .mokogitea/workflows/auto-release.yml from moko-platform [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 13s
2026-06-04 14:20:08 +00:00
Jonathan Miller 52e362a769 feat: restore licensing system with free tier (no key required)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 5s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 6s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 9s
Universal: Build & Release / Promote to RC (pull_request) Failing after 11s
Re-enable the system plugin license check with a LICENSE_TYPE constant.
Set to 'free' — check exits immediately with zero overhead. Change to
'pro' to enable download key validation. Update plugin descriptions
and CLAUDE.md to document the licensing model.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 09:14:54 -05:00
jmiller 012f8adc3d chore: sync updates.xml 01.08.00 from main [skip ci] 2026-06-04 13:15:19 +00:00
gitea-actions[bot] 5e7050576a chore: update channels for 01.08.00 [skip ci] 2026-06-04 13:15:18 +00:00
gitea-actions[bot] 95bba21c91 chore(release): build 01.08.00 [skip ci] 2026-06-04 13:15:17 +00:00
jmiller 998f62fcfb Merge pull request 'feat: package restructure, 10 feature expansions, review fixes' (#58) from dev into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-04 13:15:08 +00:00
Jonathan Miller ba5ae04755 chore: merge main into dev, resolve conflicts from package restructure
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 12s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 1m8s
Universal: PR Check / Validate PR (pull_request) Failing after 1m7s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 12s
Resolve modify/delete conflicts for old src/ files that moved to
src/packages/. Keep dev workflow files. Remove deleted cascade-dev.yml.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 08:14:07 -05:00
jmiller b3b410e190 chore: sync updates.xml 01.08.00-rc from rc [skip ci] 2026-06-04 13:02:22 +00:00
jmiller c38307f98b chore: sync updates.xml 01.08.00-rc from rc [skip ci] 2026-06-04 13:02:21 +00:00
Jonathan Miller c3a4a1f28d fix: final review — logging, null guards, XSS filter, stale descriptions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 5s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 6s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Build & Release / Promote to RC (pull_request) Successful in 17s
Add Joomla Log::add for article query, DirectoryIterator, and JSON
decode failures. Filter article content through HTMLHelper content.prepare
to prevent XSS. Add null guards on scroll indicator and mute toggle
hero/icon elements. Add console.warn on slide content JSON parse failure.
Remove stale license key references from package and plugin descriptions.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 07:58:34 -05:00
Jonathan Miller 4d67a32cb0 feat: article content source, per-slide content, remove license check (#56, #57)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Add content source selector — manual editor or Joomla article with
optional article title override. Add per-slide unique content via
subform repeatable field with heading, body, link, and link text per
slide, swapped in sync with background transitions using safe DOM
methods. Remove license key check from system plugin and plugin
dependency from module — extension is now free. Language strings in
all locales.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 07:45:23 -05:00
Jonathan Miller 6f9aa71573 feat: content entrance animations and parallax scroll effect (#48, #52)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Add configurable content entrance animations (fade-in, slide-up,
slide-left, slide-right) triggered by IntersectionObserver on scroll
into view, with adjustable delay. Add parallax scroll effect with
configurable speed (0.1–0.9) using passive scroll listener and GPU-
accelerated transforms. Both features respect prefers-reduced-motion.
Language strings in all locales.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 07:37:59 -05:00
Jonathan Miller 4a18f46c68 feat: reduced motion, scroll indicator, and video poster (#49, #50, #51)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Add prefers-reduced-motion support (WCAG 2.1 AA) — disables slideshow
cycling, CSS transitions/animations, and Ken Burns zoom when OS setting
is enabled. Add optional scroll-down chevron indicator with bounce
animation and smooth-scroll click handler. Add video poster image
fallback displayed while video loads. Language strings in all locales.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 07:30:12 -05:00
Jonathan Miller f5fdf6742f fix: input validation, JS error handling, and package description
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Add hex color validation for all color params, allowlist validation
for textAlign/fadeType/overlayType, range clamp for gradientAngle,
and try-catch around DirectoryIterator. Fix video.play() promise
rejection and iframe.contentWindow null guards in JS. Hardcode
package description in manifest. Normalize tabs throughout.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 07:23:31 -05:00
Jonathan Miller fe3abf6ddb feat: vertical alignment, mobile height, and gradient overlay (#53, #54, #55)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Add vertical text alignment (top/center/bottom) for overlay content,
mobile-specific hero height via CSS custom property, and directional
gradient overlay (dark at bottom/top/left/right) reusing existing
overlay colour controls. Language strings added to all locale files.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 07:13:04 -05:00
Jonathan Miller 601cf77170 fix: address PR review issues and add configurable slide transitions
Fix CSS injection on heroHeight with regex validation, add missing
language keys to .sys.ini files, fix plugin manifest languages folder
attribute and display name, narrow catch to \Exception with logging,
add error handling to pkg_script auto-enable, fix mobile CSS for
color/gradient modes, add SPDX headers, remove dead code, and update
stale file path headers.

Add configurable transition type for image slideshow: crossfade, slide,
fade-to-black, and zoom (Ken Burns).

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 06:46:47 -05:00
Moko Consulting a465ee1d38 chore(ci): sync CI issue reporter from Template-Joomla
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-02 21:32:25 +00:00
Moko Consulting 0742f056be chore(ci): sync CI issue reporter from Template-Joomla
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-02 21:32:23 +00:00
Moko Consulting 1b74b9c8d7 chore(ci): sync CI issue reporter from Template-Joomla
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-02 21:32:21 +00:00
Moko Consulting 77b39a2d52 chore(ci): add CI issue reporter for auto-filing gate failures
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-02 20:36:42 +00:00
Moko Consulting 351e86328f chore(ci): add CI issue reporter for auto-filing gate failures
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-02 20:36:41 +00:00
Moko Consulting 31de00c741 chore(ci): add CI issue reporter for auto-filing gate failures
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-02 20:36:41 +00:00
jmiller f9543058df chore: sync .mokogitea/workflows/cascade-dev.yml from moko-platform [skip ci] 2026-05-31 01:41:22 +00:00
jmiller 4b08e5d889 chore: sync CONTRIBUTING.md from moko-platform [skip ci] 2026-05-31 01:09:44 +00:00
gitea-actions[bot] 69e75f50fb chore: update channels for 01.07.00 [skip ci] 2026-05-30 22:25:29 +00:00
gitea-actions[bot] 806a1de120 chore(release): build 01.07.00 [skip ci] 2026-05-30 22:25:21 +00:00
jmiller b71777a130 Merge pull request 'chore: manifest schema v09.01.00 + Makefile deploy' (#46) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
2026-05-30 22:25:08 +00:00
jmiller 57f6fafcdf chore: sync updates.xml 01.07.00-rc from rc [skip ci] 2026-05-30 22:24:58 +00:00
gitea-actions[bot] 52bdfbbdbd chore: update channels for 01.06.00 [skip ci] 2026-05-30 21:41:03 +00:00
gitea-actions[bot] d68fb4a425 chore(release): build 01.06.00 [skip ci] 2026-05-30 21:40:58 +00:00
jmiller 1aecaac78b Merge pull request 'feat: add deploy target to Makefile' (#45) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
2026-05-30 21:40:50 +00:00
jmiller 5b3a4d8584 chore: sync updates.xml 01.06.00-rc from rc [skip ci] 2026-05-30 21:40:42 +00:00
jmiller d4010c19e2 chore: sync updates.xml from development [skip ci] 2026-05-30 21:13:31 +00:00
jmiller d0c43465c7 Merge pull request 'docs: changelog through 01.04.00' (#44) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 5s
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
2026-05-30 21:13:16 +00:00
58 changed files with 2620 additions and 1536 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
<display-name>Package - MokoJoomHero</display-name>
<org>MokoConsulting</org>
<description>A Joomla Module designed to provide a random image from a folder with content on top as a Hero.</description>
<version>01.06.00</version>
<version>01.16.00</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
+5 -3
View File
@@ -102,13 +102,14 @@ jobs:
run: |
php /tmp/moko-platform-api/cli/release_publish.php \
--path . --stability rc --bump minor --branch rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
--skip-update-stream
- name: Summary
if: always()
run: |
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC + lesser stream releases built, updates.xml synced" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC release built (updates.xml managed by Gitea Pages)" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
release:
@@ -167,7 +168,8 @@ jobs:
run: |
php /tmp/moko-platform-api/cli/release_publish.php \
--path . --stability stable --bump minor --branch main \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
--skip-update-stream
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
- name: "Step 9: Mirror release to GitHub"
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation
# VERSION: 01.04.01
# VERSION: 01.16.00
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+231
View File
@@ -147,6 +147,98 @@ jobs:
echo "PHP lint: ${ERRORS} error(s)"
[ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; }
- name: Joomla JEXEC guard check
if: steps.platform.outputs.platform == 'joomla'
run: |
ERRORS=0
while IFS= read -r -d '' file; do
# Skip vendor, node_modules, and index.html stub files
case "$file" in ./vendor/*|./node_modules/*) continue ;; esac
# Check first 10 lines for JEXEC or JPATH guard
if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then
echo "::error file=${file}::Missing JEXEC guard: ${file}"
ERRORS=$((ERRORS + 1))
fi
done < <(find . -name "*.php" -path "*/src/*" -not -path "./.git/*" -not -path "./vendor/*" -print0)
if [ "$ERRORS" -gt 0 ]; then
echo "::error::${ERRORS} PHP file(s) missing defined('_JEXEC') or die guard"
echo "## JEXEC Guard Check: Failed" >> $GITHUB_STEP_SUMMARY
echo "${ERRORS} file(s) in src/ are missing the Joomla execution guard." >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "JEXEC guard: OK"
- name: Joomla directory listing protection
if: steps.platform.outputs.platform == 'joomla'
run: |
MISSING=0
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && exit 0
while IFS= read -r dir; do
if [ ! -f "${dir}/index.html" ]; then
echo "::warning::Missing index.html in ${dir} (directory listing protection)"
MISSING=$((MISSING + 1))
fi
done < <(find "$SOURCE_DIR" -type d -not -path "./.git/*" -not -path "*/vendor/*" -not -path "*/node_modules/*")
if [ "$MISSING" -gt 0 ]; then
echo "## Directory Protection" >> $GITHUB_STEP_SUMMARY
echo "${MISSING} director(ies) missing index.html" >> $GITHUB_STEP_SUMMARY
fi
echo "Directory protection: ${MISSING} missing (advisory)"
- name: Joomla script file and asset checks
if: steps.platform.outputs.platform == 'joomla'
run: |
ERRORS=0
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
[ -z "$MANIFEST" ] && exit 0
MANIFEST_DIR=$(dirname "$MANIFEST")
# Check scriptfile exists if declared
SCRIPTFILE=$(sed -n 's/.*<scriptfile>\([^<]*\)<\/scriptfile>.*/\1/p' "$MANIFEST" 2>/dev/null)
if [ -n "$SCRIPTFILE" ]; then
if [ ! -f "${MANIFEST_DIR}/${SCRIPTFILE}" ]; then
echo "::error::Manifest declares <scriptfile>${SCRIPTFILE}</scriptfile> but file not found at ${MANIFEST_DIR}/${SCRIPTFILE}"
ERRORS=$((ERRORS + 1))
else
echo "Script file: ${MANIFEST_DIR}/${SCRIPTFILE} (OK)"
fi
fi
# Require joomla.asset.json and validate it
ASSET_JSON=$(find "$MANIFEST_DIR" -name "joomla.asset.json" -not -path "./.git/*" 2>/dev/null | head -1)
if [ -z "$ASSET_JSON" ]; then
echo "::error::joomla.asset.json not found — Joomla asset system is required"
ERRORS=$((ERRORS + 1))
else
if command -v php &> /dev/null; then
php -r "json_decode(file_get_contents('$ASSET_JSON')); if(json_last_error()!==JSON_ERROR_NONE){echo json_last_error_msg();exit(1);}" 2>&1 || {
echo "::error::joomla.asset.json is not valid JSON"
ERRORS=$((ERRORS + 1))
}
fi
echo "joomla.asset.json: valid"
fi
# Validate all XML files in src/ are well-formed
XML_ERRORS=0
if command -v php &> /dev/null; then
while IFS= read -r -d '' xmlfile; do
if ! php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$xmlfile'); if(!\$x){foreach(libxml_get_errors() as \$e) echo trim(\$e->message) . ' in $xmlfile'; exit(1);}" 2>&1; then
XML_ERRORS=$((XML_ERRORS + 1))
fi
done < <(find "$MANIFEST_DIR" -name "*.xml" -not -path "./.git/*" -print0)
fi
if [ "$XML_ERRORS" -gt 0 ]; then
echo "::error::${XML_ERRORS} XML file(s) are malformed"
ERRORS=$((ERRORS + 1))
else
echo "XML well-formedness: OK"
fi
[ "$ERRORS" -gt 0 ] && exit 1
echo "Joomla asset checks: OK"
- name: Validate platform manifest
run: |
PLATFORM="${{ steps.platform.outputs.platform }}"
@@ -164,6 +256,13 @@ jobs:
for ELEMENT in name version description; do
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
done
# Block legacy raw/branch update server URLs on MokoGitea
RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true)
if [ -n "$RAW_URLS" ]; then
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
echo "$RAW_URLS"
exit 1
fi
echo "Joomla manifest valid"
;;
dolibarr)
@@ -196,6 +295,138 @@ jobs:
;;
esac
- name: Validate Joomla language files
if: steps.platform.outputs.platform == 'joomla'
run: |
ERRORS=0
WARNINGS=0
# Require both en-GB and en-US language directories
LANG_ROOT=$(find . -path "*/language" -type d -not -path "./.git/*" 2>/dev/null | head -1)
if [ -z "$LANG_ROOT" ]; then
echo "No language/ directory found — skipping"
exit 0
fi
if [ ! -d "$LANG_ROOT/en-GB" ]; then
echo "::error::Missing en-GB language directory (${LANG_ROOT}/en-GB)"
ERRORS=$((ERRORS + 1))
fi
if [ ! -d "$LANG_ROOT/en-US" ]; then
echo "::error::Missing en-US language directory (${LANG_ROOT}/en-US)"
ERRORS=$((ERRORS + 1))
fi
# Check that en-GB and en-US have matching .ini files
if [ -d "$LANG_ROOT/en-GB" ] && [ -d "$LANG_ROOT/en-US" ]; then
for GB_INI in "$LANG_ROOT/en-GB"/*.ini; do
[ ! -f "$GB_INI" ] && continue
US_INI="$LANG_ROOT/en-US/$(basename "$GB_INI")"
if [ ! -f "$US_INI" ]; then
echo "::error::$(basename "$GB_INI") exists in en-GB but missing from en-US"
ERRORS=$((ERRORS + 1))
fi
done
for US_INI in "$LANG_ROOT/en-US"/*.ini; do
[ ! -f "$US_INI" ] && continue
GB_INI="$LANG_ROOT/en-GB/$(basename "$US_INI")"
if [ ! -f "$GB_INI" ]; then
echo "::error::$(basename "$US_INI") exists in en-US but missing from en-GB"
ERRORS=$((ERRORS + 1))
fi
done
fi
# Find all .ini language files
INI_FILES=$(find . -path "*/language/*/*.ini" -not -path "./.git/*" 2>/dev/null)
if [ -z "$INI_FILES" ]; then
echo "No .ini language files found"
[ "$ERRORS" -gt 0 ] && exit 1
exit 0
fi
echo "Found $(echo "$INI_FILES" | wc -l) language file(s)"
for FILE in $INI_FILES; do
FNAME=$(basename "$FILE")
LINENUM=0
SEEN_KEYS=""
while IFS= read -r line || [ -n "$line" ]; do
LINENUM=$((LINENUM + 1))
# Skip empty lines and comments
[ -z "$line" ] && continue
echo "$line" | grep -qE '^\s*;' && continue
echo "$line" | grep -qE '^\s*$' && continue
# Must match KEY="VALUE" format
if ! echo "$line" | grep -qE '^[A-Z_][A-Z0-9_]*=".*"$'; then
echo "::error file=${FILE},line=${LINENUM}::Malformed line: ${line}"
ERRORS=$((ERRORS + 1))
continue
fi
# Extract key and check for duplicates
KEY=$(echo "$line" | sed 's/=.*//')
if echo "$SEEN_KEYS" | grep -qx "$KEY"; then
echo "::error file=${FILE},line=${LINENUM}::Duplicate key: ${KEY}"
ERRORS=$((ERRORS + 1))
fi
SEEN_KEYS="${SEEN_KEYS}
${KEY}"
done < "$FILE"
echo " ${FILE}: checked ${LINENUM} lines"
done
# Cross-check en-GB vs en-US key consistency
GB_DIR=$(find . -path "*/language/en-GB" -type d -not -path "./.git/*" 2>/dev/null | head -1)
US_DIR=$(find . -path "*/language/en-US" -type d -not -path "./.git/*" 2>/dev/null | head -1)
if [ -n "$GB_DIR" ] && [ -n "$US_DIR" ]; then
for GB_FILE in "$GB_DIR"/*.ini; do
[ ! -f "$GB_FILE" ] && continue
FNAME=$(basename "$GB_FILE")
US_FILE="$US_DIR/$FNAME"
[ ! -f "$US_FILE" ] && continue
GB_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$GB_FILE" 2>/dev/null | sort)
US_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$US_FILE" 2>/dev/null | sort)
# Keys in en-GB but not en-US
MISSING_US=$(comm -23 <(echo "$GB_KEYS") <(echo "$US_KEYS"))
if [ -n "$MISSING_US" ]; then
echo "::warning::Keys in en-GB/$FNAME but missing from en-US/$FNAME:"
echo "$MISSING_US" | while read -r k; do echo " - $k"; done
WARNINGS=$((WARNINGS + 1))
fi
# Keys in en-US but not en-GB
MISSING_GB=$(comm -13 <(echo "$GB_KEYS") <(echo "$US_KEYS"))
if [ -n "$MISSING_GB" ]; then
echo "::warning::Keys in en-US/$FNAME but missing from en-GB/$FNAME:"
echo "$MISSING_GB" | while read -r k; do echo " - $k"; done
WARNINGS=$((WARNINGS + 1))
fi
done
fi
{
echo "### Language File Validation"
echo "| Metric | Count |"
echo "|---|---|"
echo "| Files checked | $(echo "$INI_FILES" | wc -l) |"
echo "| Errors | ${ERRORS} |"
echo "| Warnings | ${WARNINGS} |"
} >> $GITHUB_STEP_SUMMARY
if [ "$ERRORS" -gt 0 ]; then
echo "::error::Language validation failed with ${ERRORS} error(s)"
exit 1
fi
echo "Language files: OK (${WARNINGS} warning(s))"
- name: Check changelog has unreleased entry
run: |
if [ ! -f "CHANGELOG.md" ]; then
+20 -34
View File
@@ -1,43 +1,29 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
Version format: `XX.YY.ZZ` (zero-padded semver).
## [Unreleased]
## [01.04.00] - 2026-05-30
## [01.16.00] --- 2026-06-04
### Added
- Local Video hero mode with Joomla Media Manager file picker (`localVideoFile` param)
- Install script (`script.php`) creates `images/heroes/` folder on install/update
## [01.03.00] - 2026-05-30
## [01.15.00] --- 2026-06-04
### Added
- Configurable card fade-in delay with slide-up animation (`cardDelay` param, 0-5000ms) (#39)
- Video mute/unmute toggle button (`showMuteToggle` param) -- supports YouTube, Vimeo, and native video (#40)
## [01.02.00] - 2026-05-30
### Fixed
- WebAsset registration: added `#style`/`#script` suffixes to preset dependencies (was causing `UnsatisfiedDependencyException`)
- Asset URI resolution: use `extension/filename` pattern instead of `extension/folder/filename` (Joomla auto-inserts `css/`/`js/` folders)
- iframe video cover: split CSS into `<video>` (`object-fit: cover`) and `<iframe>` (oversize + centre-crop) rules since `object-fit` doesn't apply to iframes
- Card link styling: exclude `.btn` elements from `color: inherit` so buttons retain their own styles
### Added
- Module title renders inside the hero card as `<h2>`, respecting Joomla's Show Title toggle
- IntersectionObserver pauses/resumes videos when the hero scrolls out of/into the viewport (YouTube, Vimeo, and native `<video>`)
- YouTube embeds include `enablejsapi=1` for postMessage playback control
## [01.01.00] - 2026-05-30
## [01.14] - 2026-06-04
### Changed
- Migrated all workflow and template paths from `.github/` to `.mokogitea/`
- Template source paths updated
- HCL definition files removed -- Template repos are now the canonical source
- Update server URL to new Gitea release system
- Use pretty name format for update server entry
### Added
- `branch-cleanup.yml`: auto-delete merged feature branches after PR merge
### Fixed
- Add missing `<updateservers>` section to module manifest
## [01.13] - 2026-06-04
### Changed
- Restructure from package extension back to standalone site module
- Remove system plugin (`plg_system_mokojoomhero`) and package wrapper
- Simplify build target for module ZIP
## [01.12] - 2026-06-04
### Changed
- Remove static `updates.xml` and `update.xml` from repository
- Update server now managed by Gitea release system
+11 -18
View File
@@ -32,25 +32,21 @@ composer install # Install PHP dependencies
## Architecture
This is a Joomla **package** extension (`pkg_mokojoomhero`) containing two sub-extensions:
This is a Joomla **site module** (`mod_mokojoomhero`).
### mod_mokojoomhero (Site Module)
- Random hero image slideshow or background video with content overlay
- Supports image folders, YouTube, Vimeo, and local video
- Configurable overlay, text alignment, card animation
- **Requires** `plg_system_mokojoomhero` to be enabled — module silently skips rendering if the system plugin is disabled
### plg_system_mokojoomhero (System Plugin)
- License key validation — warns admin once per session if no download key configured
- Auto-enabled on package install via `pkg_script.php`
- Namespace: `Joomla\Plugin\System\MokoJoomHero`
- Supports image folders, YouTube, Vimeo, local video, solid colour, gradient
- Configurable overlay, text alignment, card animation, parallax, content animations
- A/B testing with weighted variations, scheduling with start/end datetime
- Article content source, per-slide unique content via subform
### Key files
- `src/pkg_mokojoomhero.xml`package manifest
- `src/pkg_script.php` — auto-enables system plugin on install
- `src/packages/mod_mokojoomhero/` — hero module source
- `src/packages/plg_system_mokojoomhero/` — system plugin source
- `updates.xml` — Joomla update server (includes legacy module entries for migration)
- `src/mod_mokojoomhero.xml`module manifest
- `src/mod_mokojoomhero.php` — module entry point
- `src/tmpl/default.php` — template
- `src/media/` — CSS, JS, and web asset registry
- `src/language/` — en-GB and en-US translations
- `src/script.php` — install script (creates default image folder)
## Rules
@@ -65,7 +61,4 @@ This is a Joomla **package** extension (`pkg_mokojoomhero`) containing two sub-e
## Coding Standards
- PHP 8.1+ minimum
- Joomla 5/6 DI container pattern: `services/provider.php` → Extension class
- Legacy stub `.php` file required for plugin loader but empty
- `SubscriberInterface` for event subscription (not `on*` method naming)
- SPDX license headers on all PHP files
+1 -1
View File
@@ -14,7 +14,7 @@
DEFGROUP:
INGROUP: Project.Documentation
REPO:
VERSION: 01.04.01
VERSION: 01.16.00
PATH: ./CODE_OF_CONDUCT.md
BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default
-->
+6 -24
View File
@@ -13,7 +13,7 @@
# Extension Configuration
EXTENSION_NAME := mokojoomhero
EXTENSION_TYPE := package
EXTENSION_TYPE := module
# Options: module, plugin, component, package, template
EXTENSION_VERSION := 1.0.0
@@ -156,29 +156,11 @@ clean: ## Clean build artifacts
.PHONY: build
build: clean ## Build extension package
@echo "$(COLOR_BLUE)Building Joomla package extension...$(COLOR_RESET)"
@mkdir -p $(DIST_DIR) $(BUILD_DIR)/packages
@# --- Build each sub-extension as a separate ZIP ---
@for EXT_DIR in $(SRC_DIR)/packages/*/; do \
EXT_NAME=$$(basename "$$EXT_DIR"); \
[ "$$EXT_NAME" = "index.html" ] && continue; \
echo " Packaging $$EXT_NAME..."; \
cd "$$EXT_DIR" && $(ZIP) -r "$(CURDIR)/$(BUILD_DIR)/packages/$${EXT_NAME}.zip" . \
-x "*.git*" -x "*/index.html" 2>/dev/null; \
cd "$(CURDIR)"; \
done
@# --- Build the outer package ZIP ---
@echo " Assembling pkg_$(EXTENSION_NAME)..."
@cp $(SRC_DIR)/pkg_mokojoomhero.xml $(BUILD_DIR)/pkg_mokojoomhero.xml
@cp $(SRC_DIR)/pkg_script.php $(BUILD_DIR)/pkg_script.php
@cd $(BUILD_DIR) && $(ZIP) -r "$(CURDIR)/$(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" \
pkg_mokojoomhero.xml pkg_script.php packages/
@echo "$(COLOR_GREEN)✓ Package created: $(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
@echo " Contents:"
@unzip -l "$(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" | tail -n +4 | head -20
@echo "$(COLOR_BLUE)Building Joomla module extension...$(COLOR_RESET)"
@mkdir -p $(DIST_DIR)
@cd $(SRC_DIR) && $(ZIP) -r "$(CURDIR)/$(DIST_DIR)/mod_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" . \
-x "*.git*" -x "*/index.html" 2>/dev/null
@echo "$(COLOR_GREEN)✓ Module created: $(DIST_DIR)/mod_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
.PHONY: package
package: build ## Alias for build
+1 -1
View File
@@ -7,7 +7,7 @@
# FILE INFORMATION
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
FILE: ./README.md
VERSION: 01.04.01
VERSION: 01.16.00
BRIEF: MokoJoomHero - Joomla Module
-->
+1 -1
View File
@@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME]
INGROUP: [PROJECT_NAME].Documentation
REPO: [REPOSITORY_URL]
PATH: /SECURITY.md
VERSION: 01.04.01
VERSION: 01.16.00
BRIEF: Security vulnerability reporting and handling policy
-->
+172
View File
@@ -0,0 +1,172 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
;
; FILE INFORMATION
; DEFGROUP: MokoJoomHero.Module.Language
; INGROUP: MokoJoomHero.Module
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
; PATH: /src/language/en-GB/mod_mokojoomhero.ini
; VERSION: 01.16.00
; BRIEF: Language strings for MokoJoomHero module (frontend + admin form fields)
MOD_MOKOJOOMHERO_NO_CONTENT="Add content to this module to display it over the hero image."
; Content fieldset
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
MOD_MOKOJOOMHERO_CONTENT_SOURCE_LABEL="Content Source"
MOD_MOKOJOOMHERO_CONTENT_SOURCE_DESC="Choose whether to enter content manually or pull from a Joomla article."
MOD_MOKOJOOMHERO_SOURCE_MANUAL="Manual Editor"
MOD_MOKOJOOMHERO_SOURCE_ARTICLE="Joomla Article"
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
MOD_MOKOJOOMHERO_ARTICLE_LABEL="Article"
MOD_MOKOJOOMHERO_ARTICLE_DESC="Select a published article to use as the hero content. The article introtext (or fulltext) is displayed."
MOD_MOKOJOOMHERO_ARTICLE_SELECT="- Select Article -"
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_LABEL="Use Article Title"
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_DESC="Replace the module title with the selected article's title."
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
; Hero mode
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
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"
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (15)."
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
; Per-slide content
MOD_MOKOJOOMHERO_SLIDE_CONTENT_LABEL="Slide Content"
MOD_MOKOJOOMHERO_SLIDE_CONTENT_DESC="Define individual slides with unique images and content. When populated, this overrides the random image folder. Leave empty to use the folder-based slideshow."
MOD_MOKOJOOMHERO_SLIDE_IMAGE_LABEL="Image"
MOD_MOKOJOOMHERO_SLIDE_HEADING_LABEL="Heading"
MOD_MOKOJOOMHERO_SLIDE_BODY_LABEL="Body Text"
MOD_MOKOJOOMHERO_SLIDE_LINK_LABEL="Link URL"
MOD_MOKOJOOMHERO_SLIDE_LINK_TEXT_LABEL="Link Text"
; Video settings (embedded)
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
; Local video settings
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
; Content animation
MOD_MOKOJOOMHERO_CONTENT_ANIM_LABEL="Content Animation"
MOD_MOKOJOOMHERO_CONTENT_ANIM_DESC="Entrance animation for the overlay content when the hero scrolls into view."
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_LABEL="Animation Delay (ms)"
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_DESC="Delay before the content animation starts."
MOD_MOKOJOOMHERO_ANIM_NONE="None"
MOD_MOKOJOOMHERO_ANIM_FADE_IN="Fade In"
MOD_MOKOJOOMHERO_ANIM_SLIDE_UP="Slide Up"
MOD_MOKOJOOMHERO_ANIM_SLIDE_LEFT="Slide from Right"
MOD_MOKOJOOMHERO_ANIM_SLIDE_RIGHT="Slide from Left"
; Card delay
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
; Parallax
MOD_MOKOJOOMHERO_PARALLAX_LABEL="Parallax Effect"
MOD_MOKOJOOMHERO_PARALLAX_DESC="Background moves at a slower rate than page content on scroll, creating a depth effect."
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
; A/B testing
MOD_MOKOJOOMHERO_FIELDSET_AB="A/B Testing"
MOD_MOKOJOOMHERO_AB_ENABLED_LABEL="Enable A/B Testing"
MOD_MOKOJOOMHERO_AB_ENABLED_DESC="Randomly show different content variations to visitors. Assignment is sticky per session."
MOD_MOKOJOOMHERO_AB_VARIATIONS_LABEL="Variations"
MOD_MOKOJOOMHERO_AB_VARIATIONS_DESC="Define content variations with relative weights. Higher weight = higher chance of being shown."
MOD_MOKOJOOMHERO_AB_VAR_LABEL="Label"
MOD_MOKOJOOMHERO_AB_VAR_CONTENT="Content"
MOD_MOKOJOOMHERO_AB_VAR_WEIGHT="Weight"
; Scheduling
MOD_MOKOJOOMHERO_FIELDSET_SCHEDULING="Scheduling"
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_LABEL="Enable Scheduling"
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_DESC="Only display the hero during a specific date/time range. Uses the site timezone."
MOD_MOKOJOOMHERO_SCHEDULE_START_LABEL="Start Date/Time"
MOD_MOKOJOOMHERO_SCHEDULE_START_DESC="The hero will not display before this date and time. Leave empty for no start restriction."
MOD_MOKOJOOMHERO_SCHEDULE_END_LABEL="End Date/Time"
MOD_MOKOJOOMHERO_SCHEDULE_END_DESC="The hero will not display after this date and time. Leave empty for no end restriction."
; Video poster
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads. Prevents a blank hero on slow connections."
; Scroll indicator
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_LABEL="Show Scroll Indicator"
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_DESC="Show an animated down-arrow at the bottom of the hero prompting users to scroll."
; Mute toggle
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)."
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
; Hero height (mobile)
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_LABEL="Mobile Hero Height"
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_DESC="Height of the hero on mobile devices. Leave empty for auto height. Uses the same units as Hero Height."
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_HINT="e.g. 40vh or 300px (empty = auto)"
; Overlay fieldset
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay &amp; Text"
MOD_MOKOJOOMHERO_OVERLAY_TYPE_LABEL="Overlay Type"
MOD_MOKOJOOMHERO_OVERLAY_TYPE_DESC="How the overlay is applied. Solid fills evenly; gradient fades from transparent to opaque in the chosen direction."
MOD_MOKOJOOMHERO_OVERLAY_SOLID="Solid"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_BOTTOM="Gradient (dark at bottom)"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_TOP="Gradient (dark at top)"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_LEFT="Gradient (dark at left)"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_RIGHT="Gradient (dark at right)"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Colour"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background colour of the overlay on top of the hero image."
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
MOD_MOKOJOOMHERO_VALIGN_LABEL="Vertical Alignment"
MOD_MOKOJOOMHERO_VALIGN_DESC="Vertical position of the content within the hero."
MOD_MOKOJOOMHERO_VALIGN_TOP="Top"
MOD_MOKOJOOMHERO_VALIGN_CENTER="Centre"
MOD_MOKOJOOMHERO_VALIGN_BOTTOM="Bottom"
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Colour"
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Colour of the text displayed over the hero image."
; Horizontal alignment options
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
MOD_MOKOJOOMHERO_ALIGN_CENTER="Centre"
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
+173
View File
@@ -0,0 +1,173 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
;
; FILE INFORMATION
; DEFGROUP: MokoJoomHero.Module.Language
; INGROUP: MokoJoomHero.Module
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
; PATH: /src/language/en-GB/mod_mokojoomhero.sys.ini
; VERSION: 01.16.00
; BRIEF: System language strings — used in admin Extension Manager and Module Manager
MOD_MOKOJOOMHERO="Module - MokoJoomHero"
MOD_MOKOJOOMHERO_DESCRIPTION="Displays a random hero image slideshow or background video with content overlaid. Designed for MokoOnyx template. By Moko Consulting."
; Content fieldset
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
MOD_MOKOJOOMHERO_CONTENT_SOURCE_LABEL="Content Source"
MOD_MOKOJOOMHERO_CONTENT_SOURCE_DESC="Choose whether to enter content manually or pull from a Joomla article."
MOD_MOKOJOOMHERO_SOURCE_MANUAL="Manual Editor"
MOD_MOKOJOOMHERO_SOURCE_ARTICLE="Joomla Article"
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
MOD_MOKOJOOMHERO_ARTICLE_LABEL="Article"
MOD_MOKOJOOMHERO_ARTICLE_DESC="Select a published article to use as the hero content."
MOD_MOKOJOOMHERO_ARTICLE_SELECT="- Select Article -"
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_LABEL="Use Article Title"
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_DESC="Replace the module title with the selected article's title."
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
; Hero mode
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
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"
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (15)."
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
; Per-slide content
MOD_MOKOJOOMHERO_SLIDE_CONTENT_LABEL="Slide Content"
MOD_MOKOJOOMHERO_SLIDE_CONTENT_DESC="Define individual slides with unique images and content."
MOD_MOKOJOOMHERO_SLIDE_IMAGE_LABEL="Image"
MOD_MOKOJOOMHERO_SLIDE_HEADING_LABEL="Heading"
MOD_MOKOJOOMHERO_SLIDE_BODY_LABEL="Body Text"
MOD_MOKOJOOMHERO_SLIDE_LINK_LABEL="Link URL"
MOD_MOKOJOOMHERO_SLIDE_LINK_TEXT_LABEL="Link Text"
; Video settings (embedded)
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
; Local video settings
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
; Content animation
MOD_MOKOJOOMHERO_CONTENT_ANIM_LABEL="Content Animation"
MOD_MOKOJOOMHERO_CONTENT_ANIM_DESC="Entrance animation for the overlay content when the hero scrolls into view."
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_LABEL="Animation Delay (ms)"
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_DESC="Delay before the content animation starts."
MOD_MOKOJOOMHERO_ANIM_NONE="None"
MOD_MOKOJOOMHERO_ANIM_FADE_IN="Fade In"
MOD_MOKOJOOMHERO_ANIM_SLIDE_UP="Slide Up"
MOD_MOKOJOOMHERO_ANIM_SLIDE_LEFT="Slide from Right"
MOD_MOKOJOOMHERO_ANIM_SLIDE_RIGHT="Slide from Left"
; Card delay
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
; Parallax
MOD_MOKOJOOMHERO_PARALLAX_LABEL="Parallax Effect"
MOD_MOKOJOOMHERO_PARALLAX_DESC="Background moves at a slower rate than page content on scroll."
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
; A/B testing
MOD_MOKOJOOMHERO_FIELDSET_AB="A/B Testing"
MOD_MOKOJOOMHERO_AB_ENABLED_LABEL="Enable A/B Testing"
MOD_MOKOJOOMHERO_AB_ENABLED_DESC="Randomly show different content variations to visitors."
MOD_MOKOJOOMHERO_AB_VARIATIONS_LABEL="Variations"
MOD_MOKOJOOMHERO_AB_VARIATIONS_DESC="Define content variations with relative weights."
MOD_MOKOJOOMHERO_AB_VAR_LABEL="Label"
MOD_MOKOJOOMHERO_AB_VAR_CONTENT="Content"
MOD_MOKOJOOMHERO_AB_VAR_WEIGHT="Weight"
; Scheduling
MOD_MOKOJOOMHERO_FIELDSET_SCHEDULING="Scheduling"
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_LABEL="Enable Scheduling"
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_DESC="Only display the hero during a specific date/time range."
MOD_MOKOJOOMHERO_SCHEDULE_START_LABEL="Start Date/Time"
MOD_MOKOJOOMHERO_SCHEDULE_START_DESC="The hero will not display before this date and time."
MOD_MOKOJOOMHERO_SCHEDULE_END_LABEL="End Date/Time"
MOD_MOKOJOOMHERO_SCHEDULE_END_DESC="The hero will not display after this date and time."
; Video poster
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads."
; Scroll indicator
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_LABEL="Show Scroll Indicator"
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_DESC="Show an animated down-arrow at the bottom of the hero prompting users to scroll."
; Mute toggle
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)."
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
; Hero height (mobile)
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_LABEL="Mobile Hero Height"
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_DESC="Height of the hero on mobile devices. Leave empty for auto height."
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_HINT="e.g. 40vh or 300px (empty = auto)"
; Overlay fieldset
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay &amp; Text"
MOD_MOKOJOOMHERO_OVERLAY_TYPE_LABEL="Overlay Type"
MOD_MOKOJOOMHERO_OVERLAY_TYPE_DESC="How the overlay is applied. Solid fills evenly; gradient fades from transparent to opaque in the chosen direction."
MOD_MOKOJOOMHERO_OVERLAY_SOLID="Solid"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_BOTTOM="Gradient (dark at bottom)"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_TOP="Gradient (dark at top)"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_LEFT="Gradient (dark at left)"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_RIGHT="Gradient (dark at right)"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Colour"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background colour of the overlay on top of the hero image."
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
MOD_MOKOJOOMHERO_VALIGN_LABEL="Vertical Alignment"
MOD_MOKOJOOMHERO_VALIGN_DESC="Vertical position of the content within the hero."
MOD_MOKOJOOMHERO_VALIGN_TOP="Top"
MOD_MOKOJOOMHERO_VALIGN_CENTER="Centre"
MOD_MOKOJOOMHERO_VALIGN_BOTTOM="Bottom"
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Colour"
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Colour of the text displayed over the hero image."
; Horizontal alignment options
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
MOD_MOKOJOOMHERO_ALIGN_CENTER="Centre"
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
+172
View File
@@ -0,0 +1,172 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
;
; FILE INFORMATION
; DEFGROUP: MokoJoomHero.Module.Language
; INGROUP: MokoJoomHero.Module
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
; PATH: /src/language/en-US/mod_mokojoomhero.ini
; VERSION: 01.16.00
; BRIEF: Language strings for MokoJoomHero module (en-US, frontend + admin form fields)
MOD_MOKOJOOMHERO_NO_CONTENT="Add content to this module to display it over the hero image."
; Content fieldset
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
MOD_MOKOJOOMHERO_CONTENT_SOURCE_LABEL="Content Source"
MOD_MOKOJOOMHERO_CONTENT_SOURCE_DESC="Choose whether to enter content manually or pull from a Joomla article."
MOD_MOKOJOOMHERO_SOURCE_MANUAL="Manual Editor"
MOD_MOKOJOOMHERO_SOURCE_ARTICLE="Joomla Article"
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
MOD_MOKOJOOMHERO_ARTICLE_LABEL="Article"
MOD_MOKOJOOMHERO_ARTICLE_DESC="Select a published article to use as the hero content. The article introtext (or fulltext) is displayed."
MOD_MOKOJOOMHERO_ARTICLE_SELECT="- Select Article -"
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_LABEL="Use Article Title"
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_DESC="Replace the module title with the selected article's title."
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
; Hero mode
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
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"
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (1-5)."
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
; Per-slide content
MOD_MOKOJOOMHERO_SLIDE_CONTENT_LABEL="Slide Content"
MOD_MOKOJOOMHERO_SLIDE_CONTENT_DESC="Define individual slides with unique images and content. When populated, this overrides the random image folder. Leave empty to use the folder-based slideshow."
MOD_MOKOJOOMHERO_SLIDE_IMAGE_LABEL="Image"
MOD_MOKOJOOMHERO_SLIDE_HEADING_LABEL="Heading"
MOD_MOKOJOOMHERO_SLIDE_BODY_LABEL="Body Text"
MOD_MOKOJOOMHERO_SLIDE_LINK_LABEL="Link URL"
MOD_MOKOJOOMHERO_SLIDE_LINK_TEXT_LABEL="Link Text"
; Video settings (embedded)
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
; Local video settings
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
; 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)."
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
; Card delay
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
; Content animation
MOD_MOKOJOOMHERO_CONTENT_ANIM_LABEL="Content Animation"
MOD_MOKOJOOMHERO_CONTENT_ANIM_DESC="Entrance animation for the overlay content when the hero scrolls into view."
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_LABEL="Animation Delay (ms)"
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_DESC="Delay before the content animation starts."
MOD_MOKOJOOMHERO_ANIM_NONE="None"
MOD_MOKOJOOMHERO_ANIM_FADE_IN="Fade In"
MOD_MOKOJOOMHERO_ANIM_SLIDE_UP="Slide Up"
MOD_MOKOJOOMHERO_ANIM_SLIDE_LEFT="Slide from Right"
MOD_MOKOJOOMHERO_ANIM_SLIDE_RIGHT="Slide from Left"
; Parallax
MOD_MOKOJOOMHERO_PARALLAX_LABEL="Parallax Effect"
MOD_MOKOJOOMHERO_PARALLAX_DESC="Background moves at a slower rate than page content on scroll, creating a depth effect."
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
; A/B testing
MOD_MOKOJOOMHERO_FIELDSET_AB="A/B Testing"
MOD_MOKOJOOMHERO_AB_ENABLED_LABEL="Enable A/B Testing"
MOD_MOKOJOOMHERO_AB_ENABLED_DESC="Randomly show different content variations to visitors. Assignment is sticky per session."
MOD_MOKOJOOMHERO_AB_VARIATIONS_LABEL="Variations"
MOD_MOKOJOOMHERO_AB_VARIATIONS_DESC="Define content variations with relative weights. Higher weight = higher chance of being shown."
MOD_MOKOJOOMHERO_AB_VAR_LABEL="Label"
MOD_MOKOJOOMHERO_AB_VAR_CONTENT="Content"
MOD_MOKOJOOMHERO_AB_VAR_WEIGHT="Weight"
; Scheduling
MOD_MOKOJOOMHERO_FIELDSET_SCHEDULING="Scheduling"
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_LABEL="Enable Scheduling"
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_DESC="Only display the hero during a specific date/time range. Uses the site timezone."
MOD_MOKOJOOMHERO_SCHEDULE_START_LABEL="Start Date/Time"
MOD_MOKOJOOMHERO_SCHEDULE_START_DESC="The hero will not display before this date and time. Leave empty for no start restriction."
MOD_MOKOJOOMHERO_SCHEDULE_END_LABEL="End Date/Time"
MOD_MOKOJOOMHERO_SCHEDULE_END_DESC="The hero will not display after this date and time. Leave empty for no end restriction."
; Video poster
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads. Prevents a blank hero on slow connections."
; Scroll indicator
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_LABEL="Show Scroll Indicator"
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_DESC="Show an animated down-arrow at the bottom of the hero prompting users to scroll."
; Mute toggle
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)."
; Hero height (mobile)
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_LABEL="Mobile Hero Height"
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_DESC="Height of the hero on mobile devices. Leave empty for auto height. Uses the same units as Hero Height."
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_HINT="e.g. 40vh or 300px (empty = auto)"
; Overlay fieldset
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay &amp; Text"
MOD_MOKOJOOMHERO_OVERLAY_TYPE_LABEL="Overlay Type"
MOD_MOKOJOOMHERO_OVERLAY_TYPE_DESC="How the overlay is applied. Solid fills evenly; gradient fades from transparent to opaque in the chosen direction."
MOD_MOKOJOOMHERO_OVERLAY_SOLID="Solid"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_BOTTOM="Gradient (dark at bottom)"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_TOP="Gradient (dark at top)"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_LEFT="Gradient (dark at left)"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_RIGHT="Gradient (dark at right)"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Color"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background color of the overlay on top of the hero image."
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
MOD_MOKOJOOMHERO_VALIGN_LABEL="Vertical Alignment"
MOD_MOKOJOOMHERO_VALIGN_DESC="Vertical position of the content within the hero."
MOD_MOKOJOOMHERO_VALIGN_TOP="Top"
MOD_MOKOJOOMHERO_VALIGN_CENTER="Center"
MOD_MOKOJOOMHERO_VALIGN_BOTTOM="Bottom"
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Color"
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Color of the text displayed over the hero image."
; Horizontal alignment options
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
MOD_MOKOJOOMHERO_ALIGN_CENTER="Center"
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
+173
View File
@@ -0,0 +1,173 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
;
; FILE INFORMATION
; DEFGROUP: MokoJoomHero.Module.Language
; INGROUP: MokoJoomHero.Module
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
; PATH: /src/language/en-US/mod_mokojoomhero.sys.ini
; VERSION: 01.16.00
; BRIEF: System language strings — used in admin Extension Manager and Module Manager (en-US)
MOD_MOKOJOOMHERO="Module - MokoJoomHero"
MOD_MOKOJOOMHERO_DESCRIPTION="Displays a random hero image slideshow or background video with content overlaid. Designed for MokoOnyx template. By Moko Consulting."
; Content fieldset
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
MOD_MOKOJOOMHERO_CONTENT_SOURCE_LABEL="Content Source"
MOD_MOKOJOOMHERO_CONTENT_SOURCE_DESC="Choose whether to enter content manually or pull from a Joomla article."
MOD_MOKOJOOMHERO_SOURCE_MANUAL="Manual Editor"
MOD_MOKOJOOMHERO_SOURCE_ARTICLE="Joomla Article"
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
MOD_MOKOJOOMHERO_ARTICLE_LABEL="Article"
MOD_MOKOJOOMHERO_ARTICLE_DESC="Select a published article to use as the hero content."
MOD_MOKOJOOMHERO_ARTICLE_SELECT="- Select Article -"
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_LABEL="Use Article Title"
MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_DESC="Replace the module title with the selected article's title."
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
; Hero mode
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
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"
MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC="Path to folder containing hero images, relative to Joomla root (e.g. images/heroes)."
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (1-5)."
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
; Per-slide content
MOD_MOKOJOOMHERO_SLIDE_CONTENT_LABEL="Slide Content"
MOD_MOKOJOOMHERO_SLIDE_CONTENT_DESC="Define individual slides with unique images and content."
MOD_MOKOJOOMHERO_SLIDE_IMAGE_LABEL="Image"
MOD_MOKOJOOMHERO_SLIDE_HEADING_LABEL="Heading"
MOD_MOKOJOOMHERO_SLIDE_BODY_LABEL="Body Text"
MOD_MOKOJOOMHERO_SLIDE_LINK_LABEL="Link URL"
MOD_MOKOJOOMHERO_SLIDE_LINK_TEXT_LABEL="Link Text"
; Video settings (embedded)
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
; Local video settings
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
; Content animation
MOD_MOKOJOOMHERO_CONTENT_ANIM_LABEL="Content Animation"
MOD_MOKOJOOMHERO_CONTENT_ANIM_DESC="Entrance animation for the overlay content when the hero scrolls into view."
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_LABEL="Animation Delay (ms)"
MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_DESC="Delay before the content animation starts."
MOD_MOKOJOOMHERO_ANIM_NONE="None"
MOD_MOKOJOOMHERO_ANIM_FADE_IN="Fade In"
MOD_MOKOJOOMHERO_ANIM_SLIDE_UP="Slide Up"
MOD_MOKOJOOMHERO_ANIM_SLIDE_LEFT="Slide from Right"
MOD_MOKOJOOMHERO_ANIM_SLIDE_RIGHT="Slide from Left"
; Card delay
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
; Parallax
MOD_MOKOJOOMHERO_PARALLAX_LABEL="Parallax Effect"
MOD_MOKOJOOMHERO_PARALLAX_DESC="Background moves at a slower rate than page content on scroll."
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
; A/B testing
MOD_MOKOJOOMHERO_FIELDSET_AB="A/B Testing"
MOD_MOKOJOOMHERO_AB_ENABLED_LABEL="Enable A/B Testing"
MOD_MOKOJOOMHERO_AB_ENABLED_DESC="Randomly show different content variations to visitors."
MOD_MOKOJOOMHERO_AB_VARIATIONS_LABEL="Variations"
MOD_MOKOJOOMHERO_AB_VARIATIONS_DESC="Define content variations with relative weights."
MOD_MOKOJOOMHERO_AB_VAR_LABEL="Label"
MOD_MOKOJOOMHERO_AB_VAR_CONTENT="Content"
MOD_MOKOJOOMHERO_AB_VAR_WEIGHT="Weight"
; Scheduling
MOD_MOKOJOOMHERO_FIELDSET_SCHEDULING="Scheduling"
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_LABEL="Enable Scheduling"
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_DESC="Only display the hero during a specific date/time range."
MOD_MOKOJOOMHERO_SCHEDULE_START_LABEL="Start Date/Time"
MOD_MOKOJOOMHERO_SCHEDULE_START_DESC="The hero will not display before this date and time."
MOD_MOKOJOOMHERO_SCHEDULE_END_LABEL="End Date/Time"
MOD_MOKOJOOMHERO_SCHEDULE_END_DESC="The hero will not display after this date and time."
; Video poster
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads."
; Scroll indicator
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_LABEL="Show Scroll Indicator"
MOD_MOKOJOOMHERO_SCROLL_INDICATOR_DESC="Show an animated down-arrow at the bottom of the hero prompting users to scroll."
; Mute toggle
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)."
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
; Hero height (mobile)
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_LABEL="Mobile Hero Height"
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_DESC="Height of the hero on mobile devices. Leave empty for auto height."
MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_HINT="e.g. 40vh or 300px (empty = auto)"
; Overlay fieldset
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay &amp; Text"
MOD_MOKOJOOMHERO_OVERLAY_TYPE_LABEL="Overlay Type"
MOD_MOKOJOOMHERO_OVERLAY_TYPE_DESC="How the overlay is applied. Solid fills evenly; gradient fades from transparent to opaque in the chosen direction."
MOD_MOKOJOOMHERO_OVERLAY_SOLID="Solid"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_BOTTOM="Gradient (dark at bottom)"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_TOP="Gradient (dark at top)"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_LEFT="Gradient (dark at left)"
MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_RIGHT="Gradient (dark at right)"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Color"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background color of the overlay on top of the hero image."
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
MOD_MOKOJOOMHERO_VALIGN_LABEL="Vertical Alignment"
MOD_MOKOJOOMHERO_VALIGN_DESC="Vertical position of the content within the hero."
MOD_MOKOJOOMHERO_VALIGN_TOP="Top"
MOD_MOKOJOOMHERO_VALIGN_CENTER="Center"
MOD_MOKOJOOMHERO_VALIGN_BOTTOM="Bottom"
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Color"
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Color of the text displayed over the hero image."
; Horizontal alignment options
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
MOD_MOKOJOOMHERO_ALIGN_CENTER="Center"
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
+382
View File
@@ -0,0 +1,382 @@
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoJoomHero.Module.Assets
* INGROUP: MokoJoomHero.Module
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
* PATH: /src/media/css/mod_mokojoomhero.css
* VERSION: 01.16.00
* BRIEF: Hero module stylesheet — slideshow, video, colour/gradient, overlay, card, mute toggle, responsive
*/
/* ============================================================
Hero container
============================================================ */
.mokojoomhero {
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
/* ============================================================
Solid colour / gradient background
============================================================ */
.mokojoomhero__color {
position: absolute;
inset: 0;
}
/* ============================================================
Image slides — base
============================================================ */
.mokojoomhero__slide {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
opacity: 0;
}
.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
============================================================ */
/* Native <video> elements: object-fit works directly */
video.mokojoomhero__video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
border: 0;
pointer-events: none;
}
/* Embedded <iframe> (YouTube/Vimeo): object-fit doesn't apply to iframes,
so we oversize the iframe and centre-crop via the parent's overflow:hidden */
iframe.mokojoomhero__video {
position: absolute;
top: 50%;
left: 50%;
width: 100vw;
height: 56.25vw; /* 16:9 aspect ratio */
min-height: 100%;
min-width: 177.78vh; /* 100 × 16/9 — ensures cover in portrait viewports */
transform: translate(-50%, -50%);
border: 0;
pointer-events: none;
}
/* ============================================================
Overlay
============================================================ */
.mokojoomhero__overlay {
position: relative;
z-index: 1;
display: flex;
justify-content: center;
width: 100%;
height: 100%;
padding: 2rem;
}
/* ============================================================
Content
============================================================ */
.mokojoomhero__content {
max-width: 900px;
width: 100%;
}
.mokojoomhero__content h1,
.mokojoomhero__content h2,
.mokojoomhero__content h3 {
margin-top: 0;
color: inherit;
}
.mokojoomhero__content p:last-child {
margin-bottom: 0;
}
/* ============================================================
Card
============================================================ */
.mokojoomhero__card {
background: rgba(255, 255, 255, 0.95);
color: #333;
border-radius: 8px;
padding: 2rem 2.5rem;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
max-width: 700px;
margin: 0 auto;
}
.mokojoomhero__card h1,
.mokojoomhero__card h2,
.mokojoomhero__card h3 {
color: #222;
margin-top: 0;
}
.mokojoomhero__card a:not(.btn) {
color: inherit;
text-decoration: underline;
}
/* ============================================================
Card fade-in delay
============================================================ */
.mokojoomhero__card[data-card-delay] {
opacity: 0;
animation: mokojoomhero-fadein 0.6s ease forwards;
}
@keyframes mokojoomhero-fadein {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* ============================================================
Mute toggle
============================================================ */
.mokojoomhero__mute-toggle {
position: absolute;
bottom: 1rem;
right: 1rem;
z-index: 2;
background: rgba(0, 0, 0, 0.5);
color: #fff;
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
font-size: 1.2rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.mokojoomhero__mute-toggle:hover {
background: rgba(0, 0, 0, 0.7);
}
/* ============================================================
Video poster image
============================================================ */
.mokojoomhero__poster {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
/* ============================================================
Scroll-down indicator
============================================================ */
.mokojoomhero__scroll-indicator {
position: absolute;
bottom: 1.5rem;
left: 50%;
transform: translateX(-50%);
z-index: 2;
background: none;
border: none;
color: #fff;
cursor: pointer;
padding: 0;
opacity: 0.8;
transition: opacity 0.3s;
animation: mokojoomhero-bounce 2s infinite;
}
.mokojoomhero__scroll-indicator:hover {
opacity: 1;
}
.mokojoomhero__scroll-indicator--hidden {
display: none;
}
@keyframes mokojoomhero-bounce {
0%, 20%, 50%, 80%, 100% { transform: translateX(-50%) translateY(0); }
40% { transform: translateX(-50%) translateY(-8px); }
60% { transform: translateX(-50%) translateY(-4px); }
}
/* ============================================================
Content entrance animations
============================================================ */
.mokojoomhero__content[class*="mokojoomhero__content--anim-"] {
opacity: 0;
}
.mokojoomhero__content--anim-fade-in.mokojoomhero__content--visible {
animation: mokojoomhero-anim-fade-in 0.8s ease forwards;
}
.mokojoomhero__content--anim-slide-up.mokojoomhero__content--visible {
animation: mokojoomhero-anim-slide-up 0.8s ease forwards;
}
.mokojoomhero__content--anim-slide-left.mokojoomhero__content--visible {
animation: mokojoomhero-anim-slide-left 0.8s ease forwards;
}
.mokojoomhero__content--anim-slide-right.mokojoomhero__content--visible {
animation: mokojoomhero-anim-slide-right 0.8s ease forwards;
}
@keyframes mokojoomhero-anim-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes mokojoomhero-anim-slide-up {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes mokojoomhero-anim-slide-left {
from { opacity: 0; transform: translateX(30px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes mokojoomhero-anim-slide-right {
from { opacity: 0; transform: translateX(-30px); }
to { opacity: 1; transform: translateX(0); }
}
/* ============================================================
Parallax
============================================================ */
.mokojoomhero[data-parallax] .mokojoomhero__slide,
.mokojoomhero[data-parallax] .mokojoomhero__color,
.mokojoomhero[data-parallax] .mokojoomhero__poster,
.mokojoomhero[data-parallax] video.mokojoomhero__video,
.mokojoomhero[data-parallax] iframe.mokojoomhero__video {
will-change: transform;
}
/* ============================================================
Reduced motion — WCAG 2.1 AA (SC 2.3.3)
============================================================ */
@media (prefers-reduced-motion: reduce) {
.mokojoomhero__slide {
transition: none !important;
animation: none !important;
}
.mokojoomhero__card[data-card-delay] {
opacity: 1;
animation: none !important;
}
.mokojoomhero__scroll-indicator {
animation: none;
}
.mokojoomhero[data-transition="zoom"] .mokojoomhero__slide--active {
animation: none !important;
}
.mokojoomhero__content[class*="mokojoomhero__content--anim-"] {
opacity: 1;
animation: none !important;
}
.mokojoomhero[data-parallax] .mokojoomhero__slide,
.mokojoomhero[data-parallax] .mokojoomhero__color,
.mokojoomhero[data-parallax] .mokojoomhero__poster,
.mokojoomhero[data-parallax] video.mokojoomhero__video,
.mokojoomhero[data-parallax] iframe.mokojoomhero__video {
will-change: auto;
transform: none !important;
}
}
/* ============================================================
Responsive
============================================================ */
@media (max-width: 768px) {
.mokojoomhero {
height: var(--mokojoomhero-mobile-height, auto) !important;
}
.mokojoomhero__video,
.mokojoomhero__slide {
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;
}
.mokojoomhero__content {
font-size: 0.9rem;
}
.mokojoomhero__card {
padding: 1.5rem;
}
}
+298
View File
@@ -0,0 +1,298 @@
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoJoomHero.Module.Assets
* INGROUP: MokoJoomHero.Module
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
* PATH: /src/media/js/mod_mokojoomhero.js
* VERSION: 01.16.00
* BRIEF: Hero module JavaScript — slideshow crossfade, video viewport control, mute toggle
*/
'use strict';
document.addEventListener('DOMContentLoaded', function () {
// Skip slideshow on mobile — video/images are hidden by CSS
if (window.matchMedia('(max-width: 768px)').matches) {
return;
}
var prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// ── 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 transition = hero.dataset.transition || 'crossfade';
var current = 0;
// Per-slide content data
var slideContentData = null;
var contentEl = hero.querySelector('.mokojoomhero__content');
if (hero.dataset.slideContent) {
try {
slideContentData = JSON.parse(hero.dataset.slideContent);
} catch (e) {
console.warn('MokoJoomHero: Failed to parse slide content data:', e.message);
slideContentData = null;
}
}
if (slides.length < 2 || prefersReducedMotion) {
return;
}
function updateSlideContent(index) {
if (!slideContentData || !slideContentData[index] || !contentEl) {
return;
}
var data = slideContentData[index];
var card = contentEl.querySelector('.mokojoomhero__card');
var target = card || contentEl;
// Clear existing content safely
while (target.firstChild) {
target.removeChild(target.firstChild);
}
if (data.heading) {
var h2 = document.createElement('h2');
h2.className = 'mokojoomhero__title';
h2.textContent = data.heading;
target.appendChild(h2);
}
if (data.body) {
var p = document.createElement('p');
p.textContent = data.body;
target.appendChild(p);
}
if (data.link && data.linkText) {
var linkP = document.createElement('p');
var a = document.createElement('a');
a.href = data.link;
a.className = 'btn btn-primary';
a.textContent = data.linkText;
linkP.appendChild(a);
target.appendChild(linkP);
}
}
function advanceSlide() {
var prev = current;
current = (current + 1) % slides.length;
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');
}
updateSlideContent(current);
}
// Set initial slide content
updateSlideContent(0);
setInterval(advanceSlide, interval);
});
// ── Pause/resume videos when out of viewport ──
if (!('IntersectionObserver' in window)) {
return;
}
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
var hero = entry.target;
var video = hero.querySelector('video.mokojoomhero__video');
var iframe = hero.querySelector('iframe.mokojoomhero__video');
if (entry.isIntersecting) {
// Resume
if (video) {
var playPromise = video.play();
if (playPromise !== undefined) {
playPromise.catch(function () {
// Autoplay blocked by browser policy — not actionable
});
}
}
if (iframe && iframe.contentWindow) {
var src = iframe.src || '';
if (src.indexOf('youtube') !== -1) {
iframe.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
} else if (src.indexOf('vimeo') !== -1) {
iframe.contentWindow.postMessage('{"method":"play"}', '*');
}
}
} else {
// Pause
if (video) {
video.pause();
}
if (iframe && iframe.contentWindow) {
var src = iframe.src || '';
if (src.indexOf('youtube') !== -1) {
iframe.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
} else if (src.indexOf('vimeo') !== -1) {
iframe.contentWindow.postMessage('{"method":"pause"}', '*');
}
}
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.mokojoomhero').forEach(function (hero) {
observer.observe(hero);
});
// ── Content entrance animations ──
if (!prefersReducedMotion) {
var animObserver = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add('mokojoomhero__content--visible');
animObserver.unobserve(entry.target);
}
});
}, { threshold: 0.2 });
document.querySelectorAll('.mokojoomhero__content[class*="mokojoomhero__content--anim-"]').forEach(function (el) {
animObserver.observe(el);
});
}
// ── Parallax scroll ──
if (!prefersReducedMotion) {
var parallaxHeroes = document.querySelectorAll('.mokojoomhero[data-parallax]');
if (parallaxHeroes.length) {
var onScroll = function () {
parallaxHeroes.forEach(function (hero) {
var rect = hero.getBoundingClientRect();
var speed = parseFloat(hero.dataset.parallax) || 0.5;
if (rect.bottom > 0 && rect.top < window.innerHeight) {
var offset = Math.round(rect.top * speed * -1);
var bg = hero.querySelector('.mokojoomhero__slide, .mokojoomhero__color, .mokojoomhero__poster, video.mokojoomhero__video');
if (bg) {
bg.style.transform = 'translateY(' + offset + 'px)';
}
var iframe = hero.querySelector('iframe.mokojoomhero__video');
if (iframe) {
iframe.style.transform = 'translate(-50%, calc(-50% + ' + offset + 'px))';
}
}
});
};
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
}
}
// ── Scroll-down indicator ──
document.querySelectorAll('.mokojoomhero__scroll-indicator').forEach(function (btn) {
var hero = btn.closest('.mokojoomhero');
if (!hero) {
return;
}
btn.addEventListener('click', function () {
var nextEl = hero.nextElementSibling || hero.parentElement.nextElementSibling;
if (nextEl) {
nextEl.scrollIntoView({ behavior: prefersReducedMotion ? 'auto' : 'smooth' });
}
});
// Hide indicator once hero scrolls out of view
var scrollObserver = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) {
btn.classList.add('mokojoomhero__scroll-indicator--hidden');
} else {
btn.classList.remove('mokojoomhero__scroll-indicator--hidden');
}
});
}, { threshold: 0.1 });
scrollObserver.observe(hero);
});
// ── Mute/unmute toggle ──
document.querySelectorAll('.mokojoomhero__mute-toggle').forEach(function (btn) {
var hero = btn.closest('.mokojoomhero');
if (!hero) {
return;
}
var video = hero.querySelector('video.mokojoomhero__video');
var iframe = hero.querySelector('iframe.mokojoomhero__video');
var icon = btn.querySelector('.mokojoomhero__mute-icon');
btn.addEventListener('click', function () {
var muted = btn.getAttribute('data-muted') === 'true';
if (video) {
video.muted = !muted;
}
if (iframe && iframe.contentWindow) {
var src = iframe.src || '';
if (src.indexOf('youtube') !== -1) {
var func = muted ? 'unMute' : 'mute';
iframe.contentWindow.postMessage('{"event":"command","func":"' + func + '","args":""}', '*');
} else if (src.indexOf('vimeo') !== -1) {
var vol = muted ? 1 : 0;
iframe.contentWindow.postMessage('{"method":"setVolume","value":' + vol + '}', '*');
}
}
btn.setAttribute('data-muted', muted ? 'false' : 'true');
btn.setAttribute('aria-label', muted ? 'Mute video' : 'Unmute video');
if (icon) {
icon.textContent = muted ? '\u{1F50A}' : '\u{1F507}';
}
});
});
});
+314
View File
@@ -0,0 +1,314 @@
<?php
/**
* @package Joomla.Site
* @subpackage mod_mokojoomhero
*
* @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;
use Joomla\CMS\Helper\ModuleHelper;
use Joomla\CMS\Uri\Uri;
/** @var \Joomla\CMS\Application\SiteApplication $app */
/** @var \stdClass $module */
/** @var \Joomla\Registry\Registry $params */
// Load module assets via Web Asset Manager (registry in media/mod_mokojoomhero/)
$wa = $app->getDocument()->getWebAssetManager();
$wa->getRegistry()->addExtensionRegistryFile('mod_mokojoomhero');
$wa->usePreset('mod_mokojoomhero');
// Schedule check — skip rendering if outside the configured date range
$scheduleEnabled = (bool) $params->get('scheduleEnabled', 0);
if ($scheduleEnabled) {
$now = new \DateTime('now', new \DateTimeZone($app->get('offset', 'UTC')));
$scheduleStart = $params->get('scheduleStart', '');
$scheduleEnd = $params->get('scheduleEnd', '');
if ($scheduleStart) {
$start = new \DateTime($scheduleStart, new \DateTimeZone($app->get('offset', 'UTC')));
if ($now < $start) {
return;
}
}
if ($scheduleEnd) {
$end = new \DateTime($scheduleEnd, new \DateTimeZone($app->get('offset', 'UTC')));
if ($now > $end) {
return;
}
}
}
// A/B testing — weighted random variation, session-sticky per module instance
$abEnabled = (bool) $params->get('abEnabled', 0);
$abVariationContent = '';
if ($abEnabled) {
$abVariations = $params->get('abVariations', '');
$abData = is_string($abVariations) ? json_decode($abVariations, true) : (array) $abVariations;
if (is_array($abData) && count($abData) > 0) {
$session = \Joomla\CMS\Factory::getSession();
$sessionKey = 'mokojoomhero.ab.' . $module->id;
$picked = $session->get($sessionKey, null);
if ($picked === null || !isset($abData[$picked])) {
// Weighted random selection
$totalWeight = 0;
foreach ($abData as $v) {
$totalWeight += (int) (((array) $v)['weight'] ?? 50);
}
$rand = mt_rand(1, max($totalWeight, 1));
$cumulative = 0;
$picked = 0;
foreach ($abData as $i => $v) {
$cumulative += (int) (((array) $v)['weight'] ?? 50);
if ($rand <= $cumulative) {
$picked = $i;
break;
}
}
$session->set($sessionKey, $picked);
}
$variation = (array) ($abData[$picked] ?? []);
$abVariationContent = $variation['content'] ?? '';
}
}
// Module parameters
$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');
$heroHeightMobile = $params->get('heroHeightMobile', '');
$overlayColor = $params->get('overlayColor', '#000000');
$overlayType = $params->get('overlayType', 'solid');
$overlayOpacity = (float) $params->get('overlayOpacity', 0.5);
$textAlign = $params->get('textAlign', 'center');
$verticalAlign = $params->get('verticalAlign', 'center');
$textColor = $params->get('textColor', '#ffffff');
$contentSource = $params->get('contentSource', 'manual');
$articleId = (int) $params->get('articleId', 0);
$useArticleTitle = (bool) $params->get('useArticleTitle', 0);
$heroContent = $params->get('heroContent', '');
$slideContent = $params->get('slideContent', '');
$showCard = (bool) $params->get('showCard', 1);
$cardDelay = (int) $params->get('cardDelay', 0);
$contentAnimation = $params->get('contentAnimation', 'none');
$contentAnimationDelay = (int) $params->get('contentAnimationDelay', 0);
$parallaxEnabled = (bool) $params->get('parallaxEnabled', 0);
$parallaxSpeed = (float) $params->get('parallaxSpeed', 0.5);
$showMuteToggle = (bool) $params->get('showMuteToggle', 0);
$videoPoster = $params->get('videoPoster', '');
$showScrollIndicator = (bool) $params->get('showScrollIndicator', 0);
$localVideoFile = $params->get('localVideoFile', '');
$bgColor = $params->get('bgColor', '#003366');
$gradientStart = $params->get('gradientStart', '#003366');
$gradientEnd = $params->get('gradientEnd', '#006699');
$gradientAngle = (int) $params->get('gradientAngle', 135);
// Validate CSS height values to prevent injection
if (!preg_match('/^\d+(\.\d+)?(px|vh|vw|em|rem|%)$/', $heroHeight)) {
$heroHeight = '60vh';
}
if ($heroHeightMobile && !preg_match('/^\d+(\.\d+)?(px|vh|vw|em|rem|%)$/', $heroHeightMobile)) {
$heroHeightMobile = '';
}
// Validate hex colour values
$hexColorPattern = '/^#[0-9a-fA-F]{6}$/';
if (!preg_match($hexColorPattern, $overlayColor)) {
$overlayColor = '#000000';
}
if (!preg_match($hexColorPattern, $textColor)) {
$textColor = '#ffffff';
}
if (!preg_match($hexColorPattern, $bgColor)) {
$bgColor = '#003366';
}
if (!preg_match($hexColorPattern, $gradientStart)) {
$gradientStart = '#003366';
}
if (!preg_match($hexColorPattern, $gradientEnd)) {
$gradientEnd = '#006699';
}
// Validate allowlist values
$allowedTextAlign = ['left', 'center', 'right'];
if (!in_array($textAlign, $allowedTextAlign, true)) {
$textAlign = 'center';
}
$allowedFadeTypes = ['crossfade', 'slide', 'fade-black', 'zoom'];
if (!in_array($fadeType, $allowedFadeTypes, true)) {
$fadeType = 'crossfade';
}
$allowedOverlayTypes = ['solid', 'gradient-bottom', 'gradient-top', 'gradient-left', 'gradient-right'];
if (!in_array($overlayType, $allowedOverlayTypes, true)) {
$overlayType = 'solid';
}
$allowedContentAnimations = ['none', 'fade-in', 'slide-up', 'slide-left', 'slide-right'];
if (!in_array($contentAnimation, $allowedContentAnimations, true)) {
$contentAnimation = 'none';
}
$parallaxSpeed = max(0.1, min(0.9, $parallaxSpeed));
$gradientAngle = max(0, min(360, $gradientAngle));
// Apply A/B variation content if active
if ($abEnabled && $abVariationContent) {
$heroContent = $abVariationContent;
}
// Collect hero images
$heroImages = [];
if ($heroMode === 'images') {
$folderPath = JPATH_ROOT . '/' . ltrim($imageFolder, '/');
if (is_dir($folderPath)) {
try {
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'svg'];
$all = [];
foreach (new DirectoryIterator($folderPath) as $file) {
if ($file->isFile() && in_array(strtolower($file->getExtension()), $allowed, true)) {
$all[] = $file->getFilename();
}
}
if ($all) {
shuffle($all);
$picked = array_slice($all, 0, min($imageCount, 5));
foreach ($picked as $filename) {
$heroImages[] = Uri::root() . $imageFolder . '/' . $filename;
}
}
} catch (\UnexpectedValueException $e) {
\Joomla\CMS\Log\Log::add(
'MokoJoomHero: Cannot read image folder "' . $folderPath . '": ' . $e->getMessage(),
\Joomla\CMS\Log\Log::WARNING,
'mod_mokojoomhero'
);
}
}
}
// Build video URL — smartly detect YouTube, Vimeo, or local/direct file
$videoUrl = '';
$youtubeId = '';
$vimeoId = '';
if ($heroMode === 'localvideo' && $localVideoFile) {
$videoUrl = Uri::root() . ltrim($localVideoFile, '/');
} elseif ($heroMode === 'video' && $videoFile) {
// YouTube: watch, embed, shorts, youtu.be, with optional timestamps/params
if (preg_match('/(?:youtube\.com\/(?:watch\?.*v=|embed\/|shorts\/|v\/)|youtu\.be\/)([\w-]{11})/', $videoFile, $m)) {
$youtubeId = $m[1];
// Vimeo: vimeo.com/123456 or player.vimeo.com/video/123456
} elseif (preg_match('/vimeo\.com\/(?:video\/)?(\d+)/', $videoFile, $m)) {
$vimeoId = $m[1];
} else {
// Direct URL or local file path
$videoUrl = (strpos($videoFile, '://') !== false)
? $videoFile
: Uri::root() . ltrim($videoFile, '/');
}
}
// Load content from article if configured
$articleTitle = '';
if ($contentSource === 'article' && $articleId > 0) {
try {
$db = \Joomla\CMS\Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName(['title', 'introtext', 'fulltext']))
->from($db->quoteName('#__content'))
->where($db->quoteName('id') . ' = ' . $articleId)
->where($db->quoteName('state') . ' = 1');
$db->setQuery($query);
$article = $db->loadObject();
if ($article) {
$rawContent = $article->introtext ?: $article->fulltext;
$heroContent = \Joomla\CMS\HTML\HTMLHelper::_('content.prepare', $rawContent);
$articleTitle = $article->title;
}
} catch (\RuntimeException $e) {
\Joomla\CMS\Log\Log::add(
'MokoJoomHero: Failed to load article ID ' . $articleId . ': ' . $e->getMessage(),
\Joomla\CMS\Log\Log::WARNING,
'mod_mokojoomhero'
);
}
}
// Process per-slide content — overrides folder-based images when populated
$slides = [];
if ($heroMode === 'images' && !empty($slideContent)) {
$slideData = is_string($slideContent) ? json_decode($slideContent, true) : (array) $slideContent;
if ($slideData === null && json_last_error() !== JSON_ERROR_NONE) {
\Joomla\CMS\Log\Log::add(
'MokoJoomHero: Failed to decode slideContent JSON: ' . json_last_error_msg(),
\Joomla\CMS\Log\Log::WARNING,
'mod_mokojoomhero'
);
}
if (is_array($slideData)) {
foreach ($slideData as $item) {
$item = (array) $item;
if (!empty($item['image'])) {
$slides[] = [
'image' => Uri::root() . ltrim($item['image'], '/'),
'heading' => $item['heading'] ?? '',
'body' => $item['body'] ?? '',
'link' => $item['link'] ?? '',
'linkText' => $item['linkText'] ?? 'Learn More',
];
}
}
}
// Per-slide content overrides folder-based random images
if ($slides) {
$heroImages = array_column($slides, 'image');
}
}
require ModuleHelper::getLayoutPath('mod_mokojoomhero', $params->get('layout', 'default'));
+524
View File
@@ -0,0 +1,524 @@
<?xml version="1.0" encoding="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: MokoJoomHero.Module
INGROUP: MokoJoomHero
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
PATH: /src/mod_mokojoomhero.xml
VERSION: 01.00.20
BRIEF: Joomla module manifest — random hero image with content overlay
-->
<extension type="module" client="site" method="upgrade">
<name>Module - MokoJoomHero</name>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GPL-3.0-or-later</license>
<version>01.16.00</version>
<description>Displays a random hero image slideshow or background video with content overlaid. Designed for MokoOnyx template. By Moko Consulting.</description>
<scriptfile>script.php</scriptfile>
<files>
<filename module="mod_mokojoomhero">mod_mokojoomhero.php</filename>
<filename>mod_mokojoomhero.xml</filename>
<filename>script.php</filename>
<folder>tmpl</folder>
<folder>language</folder>
</files>
<media destination="mod_mokojoomhero" folder="media">
<filename>joomla.asset.json</filename>
<folder>css</folder>
<folder>js</folder>
</media>
<languages folder="language">
<language tag="en-GB">en-GB/mod_mokojoomhero.ini</language>
<language tag="en-GB">en-GB/mod_mokojoomhero.sys.ini</language>
<language tag="en-US">en-US/mod_mokojoomhero.ini</language>
<language tag="en-US">en-US/mod_mokojoomhero.sys.ini</language>
</languages>
<updateservers>
<server type="extension" name="Module - MokoJoomHero">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/updates.xml</server>
</updateservers>
<config>
<fields name="params">
<fieldset name="basic">
<field
name="heroMode"
type="list"
label="MOD_MOKOJOOMHERO_MODE_LABEL"
description="MOD_MOKOJOOMHERO_MODE_DESC"
default="images"
>
<option value="images">MOD_MOKOJOOMHERO_MODE_IMAGES</option>
<option value="video">MOD_MOKOJOOMHERO_MODE_VIDEO</option>
<option value="localvideo">MOD_MOKOJOOMHERO_MODE_LOCALVIDEO</option>
<option value="color">MOD_MOKOJOOMHERO_MODE_COLOR</option>
<option value="gradient">MOD_MOKOJOOMHERO_MODE_GRADIENT</option>
</field>
<field
name="bgColor"
type="color"
label="MOD_MOKOJOOMHERO_BG_COLOR_LABEL"
description="MOD_MOKOJOOMHERO_BG_COLOR_DESC"
default="#003366"
showon="heroMode:color"
/>
<field
name="gradientStart"
type="color"
label="MOD_MOKOJOOMHERO_GRADIENT_START_LABEL"
description="MOD_MOKOJOOMHERO_GRADIENT_START_DESC"
default="#003366"
showon="heroMode:gradient"
/>
<field
name="gradientEnd"
type="color"
label="MOD_MOKOJOOMHERO_GRADIENT_END_LABEL"
description="MOD_MOKOJOOMHERO_GRADIENT_END_DESC"
default="#006699"
showon="heroMode:gradient"
/>
<field
name="gradientAngle"
type="number"
label="MOD_MOKOJOOMHERO_GRADIENT_ANGLE_LABEL"
description="MOD_MOKOJOOMHERO_GRADIENT_ANGLE_DESC"
default="135"
min="0"
max="360"
step="15"
showon="heroMode:gradient"
/>
<field
name="fadeType"
type="list"
label="MOD_MOKOJOOMHERO_FADE_TYPE_LABEL"
description="MOD_MOKOJOOMHERO_FADE_TYPE_DESC"
default="crossfade"
showon="heroMode:images"
>
<option value="crossfade">MOD_MOKOJOOMHERO_FADE_CROSSFADE</option>
<option value="slide">MOD_MOKOJOOMHERO_FADE_SLIDE</option>
<option value="fade-black">MOD_MOKOJOOMHERO_FADE_BLACK</option>
<option value="zoom">MOD_MOKOJOOMHERO_FADE_ZOOM</option>
</field>
<field
name="imageFolder"
type="text"
label="MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL"
description="MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC"
default="images/heroes"
filter="path"
showon="heroMode:images"
/>
<field
name="imageCount"
type="number"
label="MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL"
description="MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC"
default="5"
min="1"
max="5"
showon="heroMode:images"
/>
<field
name="slideInterval"
type="number"
label="MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL"
description="MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC"
default="5000"
min="1000"
step="500"
showon="heroMode:images"
/>
<field
name="slideContent"
type="subform"
label="MOD_MOKOJOOMHERO_SLIDE_CONTENT_LABEL"
description="MOD_MOKOJOOMHERO_SLIDE_CONTENT_DESC"
multiple="true"
layout="joomla.form.field.subform.repeatable-table"
showon="heroMode:images"
max="5"
>
<form>
<field
name="image"
type="media"
label="MOD_MOKOJOOMHERO_SLIDE_IMAGE_LABEL"
types="images"
/>
<field
name="heading"
type="text"
label="MOD_MOKOJOOMHERO_SLIDE_HEADING_LABEL"
filter="string"
/>
<field
name="body"
type="textarea"
label="MOD_MOKOJOOMHERO_SLIDE_BODY_LABEL"
filter="safehtml"
rows="3"
/>
<field
name="link"
type="url"
label="MOD_MOKOJOOMHERO_SLIDE_LINK_LABEL"
filter="url"
/>
<field
name="linkText"
type="text"
label="MOD_MOKOJOOMHERO_SLIDE_LINK_TEXT_LABEL"
filter="string"
default="Learn More"
/>
</form>
</field>
<field
name="videoFile"
type="text"
label="MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL"
description="MOD_MOKOJOOMHERO_VIDEO_FILE_DESC"
filter="string"
showon="heroMode:video"
/>
<field
name="localVideoFile"
type="media"
label="MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL"
description="MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC"
types="videos"
showon="heroMode:localvideo"
/>
<field
name="heroHeight"
type="text"
label="MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL"
description="MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC"
hint="MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT"
default="60vh"
filter="string"
/>
<field
name="heroHeightMobile"
type="text"
label="MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_LABEL"
description="MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_DESC"
hint="MOD_MOKOJOOMHERO_HERO_HEIGHT_MOBILE_HINT"
default=""
filter="string"
/>
<field
name="videoPoster"
type="media"
label="MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL"
description="MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC"
types="images"
showon="heroMode:video,localvideo"
/>
<field
name="showScrollIndicator"
type="radio"
layout="joomla.form.field.radio.switcher"
label="MOD_MOKOJOOMHERO_SCROLL_INDICATOR_LABEL"
description="MOD_MOKOJOOMHERO_SCROLL_INDICATOR_DESC"
default="0"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field
name="parallaxEnabled"
type="radio"
layout="joomla.form.field.radio.switcher"
label="MOD_MOKOJOOMHERO_PARALLAX_LABEL"
description="MOD_MOKOJOOMHERO_PARALLAX_DESC"
default="0"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field
name="parallaxSpeed"
type="range"
label="MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL"
description="MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC"
default="0.5"
min="0.1"
max="0.9"
step="0.1"
showon="parallaxEnabled:1"
/>
<field
name="showMuteToggle"
type="radio"
layout="joomla.form.field.radio.switcher"
label="MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL"
description="MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC"
default="0"
showon="heroMode:video,localvideo"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
</fieldset>
<fieldset name="abtesting"
label="MOD_MOKOJOOMHERO_FIELDSET_AB"
>
<field
name="abEnabled"
type="radio"
layout="joomla.form.field.radio.switcher"
label="MOD_MOKOJOOMHERO_AB_ENABLED_LABEL"
description="MOD_MOKOJOOMHERO_AB_ENABLED_DESC"
default="0"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field
name="abVariations"
type="subform"
label="MOD_MOKOJOOMHERO_AB_VARIATIONS_LABEL"
description="MOD_MOKOJOOMHERO_AB_VARIATIONS_DESC"
multiple="true"
layout="joomla.form.field.subform.repeatable-table"
showon="abEnabled:1"
max="4"
>
<form>
<field
name="label"
type="text"
label="MOD_MOKOJOOMHERO_AB_VAR_LABEL"
filter="string"
default="Variation"
/>
<field
name="content"
type="textarea"
label="MOD_MOKOJOOMHERO_AB_VAR_CONTENT"
filter="safehtml"
rows="3"
/>
<field
name="weight"
type="number"
label="MOD_MOKOJOOMHERO_AB_VAR_WEIGHT"
default="50"
min="1"
max="100"
/>
</form>
</field>
</fieldset>
<fieldset name="scheduling"
label="MOD_MOKOJOOMHERO_FIELDSET_SCHEDULING"
>
<field
name="scheduleEnabled"
type="radio"
layout="joomla.form.field.radio.switcher"
label="MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_LABEL"
description="MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_DESC"
default="0"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field
name="scheduleStart"
type="calendar"
label="MOD_MOKOJOOMHERO_SCHEDULE_START_LABEL"
description="MOD_MOKOJOOMHERO_SCHEDULE_START_DESC"
format="%Y-%m-%d %H:%M"
showtime="true"
showon="scheduleEnabled:1"
/>
<field
name="scheduleEnd"
type="calendar"
label="MOD_MOKOJOOMHERO_SCHEDULE_END_LABEL"
description="MOD_MOKOJOOMHERO_SCHEDULE_END_DESC"
format="%Y-%m-%d %H:%M"
showtime="true"
showon="scheduleEnabled:1"
/>
</fieldset>
<fieldset name="content"
label="MOD_MOKOJOOMHERO_FIELDSET_CONTENT"
>
<field
name="contentSource"
type="list"
label="MOD_MOKOJOOMHERO_CONTENT_SOURCE_LABEL"
description="MOD_MOKOJOOMHERO_CONTENT_SOURCE_DESC"
default="manual"
>
<option value="manual">MOD_MOKOJOOMHERO_SOURCE_MANUAL</option>
<option value="article">MOD_MOKOJOOMHERO_SOURCE_ARTICLE</option>
</field>
<field
name="heroContent"
type="editor"
label="MOD_MOKOJOOMHERO_CONTENT_LABEL"
description="MOD_MOKOJOOMHERO_CONTENT_DESC"
filter="safehtml"
buttons="true"
hide="readmore,pagebreak"
showon="contentSource:manual"
/>
<field
name="articleId"
type="sql"
label="MOD_MOKOJOOMHERO_ARTICLE_LABEL"
description="MOD_MOKOJOOMHERO_ARTICLE_DESC"
query="SELECT id, title FROM #__content WHERE state = 1 ORDER BY title ASC"
key_field="id"
value_field="title"
header="MOD_MOKOJOOMHERO_ARTICLE_SELECT"
showon="contentSource:article"
/>
<field
name="useArticleTitle"
type="radio"
layout="joomla.form.field.radio.switcher"
label="MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_LABEL"
description="MOD_MOKOJOOMHERO_USE_ARTICLE_TITLE_DESC"
default="0"
showon="contentSource:article"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field
name="showCard"
type="radio"
layout="joomla.form.field.radio.switcher"
label="MOD_MOKOJOOMHERO_SHOW_CARD_LABEL"
description="MOD_MOKOJOOMHERO_SHOW_CARD_DESC"
default="1"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field
name="contentAnimation"
type="list"
label="MOD_MOKOJOOMHERO_CONTENT_ANIM_LABEL"
description="MOD_MOKOJOOMHERO_CONTENT_ANIM_DESC"
default="none"
>
<option value="none">MOD_MOKOJOOMHERO_ANIM_NONE</option>
<option value="fade-in">MOD_MOKOJOOMHERO_ANIM_FADE_IN</option>
<option value="slide-up">MOD_MOKOJOOMHERO_ANIM_SLIDE_UP</option>
<option value="slide-left">MOD_MOKOJOOMHERO_ANIM_SLIDE_LEFT</option>
<option value="slide-right">MOD_MOKOJOOMHERO_ANIM_SLIDE_RIGHT</option>
</field>
<field
name="contentAnimationDelay"
type="number"
label="MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_LABEL"
description="MOD_MOKOJOOMHERO_CONTENT_ANIM_DELAY_DESC"
default="0"
min="0"
max="3000"
step="100"
showon="contentAnimation!:none"
/>
<field
name="cardDelay"
type="number"
label="MOD_MOKOJOOMHERO_CARD_DELAY_LABEL"
description="MOD_MOKOJOOMHERO_CARD_DELAY_DESC"
default="0"
min="0"
max="5000"
step="250"
showon="showCard:1"
/>
</fieldset>
<fieldset name="advanced"
label="MOD_MOKOJOOMHERO_FIELDSET_OVERLAY"
>
<field
name="overlayColor"
type="color"
label="MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL"
description="MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC"
default="#000000"
/>
<field
name="overlayType"
type="list"
label="MOD_MOKOJOOMHERO_OVERLAY_TYPE_LABEL"
description="MOD_MOKOJOOMHERO_OVERLAY_TYPE_DESC"
default="solid"
>
<option value="solid">MOD_MOKOJOOMHERO_OVERLAY_SOLID</option>
<option value="gradient-bottom">MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_BOTTOM</option>
<option value="gradient-top">MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_TOP</option>
<option value="gradient-left">MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_LEFT</option>
<option value="gradient-right">MOD_MOKOJOOMHERO_OVERLAY_GRADIENT_RIGHT</option>
</field>
<field
name="overlayOpacity"
type="range"
label="MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL"
description="MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC"
default="0.5"
min="0"
max="1"
step="0.1"
/>
<field
name="textAlign"
type="list"
label="MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL"
description="MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC"
default="center"
>
<option value="left">MOD_MOKOJOOMHERO_ALIGN_LEFT</option>
<option value="center">MOD_MOKOJOOMHERO_ALIGN_CENTER</option>
<option value="right">MOD_MOKOJOOMHERO_ALIGN_RIGHT</option>
</field>
<field
name="verticalAlign"
type="list"
label="MOD_MOKOJOOMHERO_VALIGN_LABEL"
description="MOD_MOKOJOOMHERO_VALIGN_DESC"
default="center"
>
<option value="top">MOD_MOKOJOOMHERO_VALIGN_TOP</option>
<option value="center">MOD_MOKOJOOMHERO_VALIGN_CENTER</option>
<option value="bottom">MOD_MOKOJOOMHERO_VALIGN_BOTTOM</option>
</field>
<field
name="textColor"
type="color"
label="MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL"
description="MOD_MOKOJOOMHERO_TEXT_COLOR_DESC"
default="#ffffff"
/>
</fieldset>
</fields>
</config>
</extension>
@@ -1,85 +0,0 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
;
; FILE INFORMATION
; DEFGROUP: MokoJoomHero.Module.Language
; INGROUP: MokoJoomHero.Module
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
; PATH: /src/language/en-GB/mod_mokojoomhero.ini
; VERSION: 01.04.01
; BRIEF: Language strings for MokoJoomHero module (frontend + admin form fields)
MOD_MOKOJOOMHERO_NO_CONTENT="Add content to this module to display it over the hero image."
; Content fieldset
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
; Hero mode
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
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"
; 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)."
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (15)."
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
; Video settings (embedded)
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
; Local video settings
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
; Card delay
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
; Mute toggle
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)."
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
; Overlay fieldset
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay &amp; Text"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Colour"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background colour of the overlay on top of the hero image."
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Colour"
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Colour of the text displayed over the hero image."
; Alignment options
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
MOD_MOKOJOOMHERO_ALIGN_CENTER="Centre"
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
@@ -1,72 +0,0 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
;
; FILE INFORMATION
; DEFGROUP: MokoJoomHero.Module.Language
; INGROUP: MokoJoomHero.Module
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
; PATH: /src/language/en-GB/mod_mokojoomhero.sys.ini
; VERSION: 01.04.01
; BRIEF: System language strings — used in admin Extension Manager and Module Manager
MOD_MOKOJOOMHERO="Module - MokoJoomHero"
MOD_MOKOJOOMHERO_DESCRIPTION="Displays a random hero image slideshow or background video with content overlaid. Designed for MokoOnyx template. By Moko Consulting."
; Content fieldset
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
; 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_IMAGES="Images"
MOD_MOKOJOOMHERO_MODE_VIDEO="Video (YouTube/Vimeo)"
MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
; 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)."
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (15)."
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
; Video settings (embedded)
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
; Local video settings
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
; Card delay
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
; Mute toggle
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)."
; 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)."
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
; Overlay fieldset
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay &amp; Text"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Colour"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background colour of the overlay on top of the hero image."
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Colour"
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Colour of the text displayed over the hero image."
; Alignment options
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
MOD_MOKOJOOMHERO_ALIGN_CENTER="Centre"
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
@@ -1,85 +0,0 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
;
; FILE INFORMATION
; DEFGROUP: MokoJoomHero.Module.Language
; INGROUP: MokoJoomHero.Module
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
; PATH: /src/language/en-US/mod_mokojoomhero.ini
; VERSION: 01.04.01
; BRIEF: Language strings for MokoJoomHero module (en-US, frontend + admin form fields)
MOD_MOKOJOOMHERO_NO_CONTENT="Add content to this module to display it over the hero image."
; Content fieldset
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
; Hero mode
MOD_MOKOJOOMHERO_MODE_LABEL="Hero Mode"
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"
; 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)."
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (1-5)."
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
; Video settings (embedded)
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
; Local video settings
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
; 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)."
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
; Card delay
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
; Mute toggle
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)."
; Overlay fieldset
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay &amp; Text"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Color"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background color of the overlay on top of the hero image."
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Color"
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Color of the text displayed over the hero image."
; Alignment options
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
MOD_MOKOJOOMHERO_ALIGN_CENTER="Center"
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
@@ -1,72 +0,0 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
;
; FILE INFORMATION
; DEFGROUP: MokoJoomHero.Module.Language
; INGROUP: MokoJoomHero.Module
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
; PATH: /src/language/en-US/mod_mokojoomhero.sys.ini
; VERSION: 01.04.01
; BRIEF: System language strings — used in admin Extension Manager and Module Manager (en-US)
MOD_MOKOJOOMHERO="Module - MokoJoomHero"
MOD_MOKOJOOMHERO_DESCRIPTION="Displays a random hero image slideshow or background video with content overlaid. Designed for MokoOnyx template. By Moko Consulting."
; Content fieldset
MOD_MOKOJOOMHERO_FIELDSET_CONTENT="Hero Content"
MOD_MOKOJOOMHERO_CONTENT_LABEL="Content"
MOD_MOKOJOOMHERO_CONTENT_DESC="HTML content displayed on the hero. Use the editor to add headings, text, buttons, or any HTML."
MOD_MOKOJOOMHERO_SHOW_CARD_LABEL="Show Card"
MOD_MOKOJOOMHERO_SHOW_CARD_DESC="Wrap the content in a card with a white background and shadow."
; 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_IMAGES="Images"
MOD_MOKOJOOMHERO_MODE_VIDEO="Video (YouTube/Vimeo)"
MOD_MOKOJOOMHERO_MODE_LOCALVIDEO="Local Video"
; 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)."
MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL="Number of Images"
MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC="How many random images to include in the slideshow (1-5)."
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL="Slide Interval (ms)"
MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC="Time between slides in milliseconds (e.g. 5000 = 5 seconds)."
; Video settings (embedded)
MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL="Video URL"
MOD_MOKOJOOMHERO_VIDEO_FILE_DESC="YouTube or Vimeo URL. The module auto-detects the source."
; Local video settings
MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL="Video File"
MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC="Select a video file from the Media Manager (mp4, webm, ogg)."
; Card delay
MOD_MOKOJOOMHERO_CARD_DELAY_LABEL="Card Fade-in Delay (ms)"
MOD_MOKOJOOMHERO_CARD_DELAY_DESC="Delay in milliseconds before the content card fades in. Set to 0 for no delay."
; Mute toggle
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)."
; 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)."
MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT="e.g. 60vh or 400px"
; Overlay fieldset
MOD_MOKOJOOMHERO_FIELDSET_OVERLAY="Overlay &amp; Text"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL="Overlay Color"
MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC="Background color of the overlay on top of the hero image."
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL="Overlay Opacity"
MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC="Transparency of the overlay (0 = fully transparent, 1 = fully opaque)."
MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL="Text Alignment"
MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC="Horizontal alignment of the overlay text."
MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL="Text Color"
MOD_MOKOJOOMHERO_TEXT_COLOR_DESC="Color of the text displayed over the hero image."
; Alignment options
MOD_MOKOJOOMHERO_ALIGN_LEFT="Left"
MOD_MOKOJOOMHERO_ALIGN_CENTER="Center"
MOD_MOKOJOOMHERO_ALIGN_RIGHT="Right"
@@ -1,201 +0,0 @@
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoJoomHero.Module.Assets
* INGROUP: MokoJoomHero.Module
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
* PATH: /src/css/template.css
* VERSION: 01.04.01
* BRIEF: Hero module stylesheet — slideshow, video background, overlay
*/
/* ============================================================
Hero container
============================================================ */
.mokojoomhero {
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
/* ============================================================
Solid colour / gradient background
============================================================ */
.mokojoomhero__color {
position: absolute;
inset: 0;
}
/* ============================================================
Image slides
============================================================ */
.mokojoomhero__slide {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
opacity: 0;
transition: opacity 1s ease;
}
.mokojoomhero__slide--active {
opacity: 1;
}
/* ============================================================
Video background
============================================================ */
/* Native <video> elements: object-fit works directly */
video.mokojoomhero__video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
border: 0;
pointer-events: none;
}
/* Embedded <iframe> (YouTube/Vimeo): object-fit doesn't apply to iframes,
so we oversize the iframe and centre-crop via the parent's overflow:hidden */
iframe.mokojoomhero__video {
position: absolute;
top: 50%;
left: 50%;
width: 100vw;
height: 56.25vw; /* 16:9 aspect ratio */
min-height: 100%;
min-width: 177.78vh; /* 100 × 16/9 — ensures cover in portrait viewports */
transform: translate(-50%, -50%);
border: 0;
pointer-events: none;
}
/* ============================================================
Overlay
============================================================ */
.mokojoomhero__overlay {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 2rem;
}
/* ============================================================
Content
============================================================ */
.mokojoomhero__content {
max-width: 900px;
width: 100%;
}
.mokojoomhero__content h1,
.mokojoomhero__content h2,
.mokojoomhero__content h3 {
margin-top: 0;
color: inherit;
}
.mokojoomhero__content p:last-child {
margin-bottom: 0;
}
/* ============================================================
Card
============================================================ */
.mokojoomhero__card {
background: rgba(255, 255, 255, 0.95);
color: #333;
border-radius: 8px;
padding: 2rem 2.5rem;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
max-width: 700px;
margin: 0 auto;
}
.mokojoomhero__card h1,
.mokojoomhero__card h2,
.mokojoomhero__card h3 {
color: #222;
margin-top: 0;
}
.mokojoomhero__card a:not(.btn) {
color: inherit;
text-decoration: underline;
}
/* ============================================================
Card fade-in delay
============================================================ */
.mokojoomhero__card[data-card-delay] {
opacity: 0;
animation: mokojoomhero-fadein 0.6s ease forwards;
}
@keyframes mokojoomhero-fadein {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* ============================================================
Mute toggle
============================================================ */
.mokojoomhero__mute-toggle {
position: absolute;
bottom: 1rem;
right: 1rem;
z-index: 2;
background: rgba(0, 0, 0, 0.5);
color: #fff;
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
font-size: 1.2rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.mokojoomhero__mute-toggle:hover {
background: rgba(0, 0, 0, 0.7);
}
/* ============================================================
Responsive
============================================================ */
@media (max-width: 768px) {
.mokojoomhero {
height: auto !important;
}
.mokojoomhero__video,
.mokojoomhero__slide {
display: none;
}
.mokojoomhero__overlay {
padding: 1rem;
background-color: transparent !important;
}
.mokojoomhero__content {
font-size: 0.9rem;
}
.mokojoomhero__card {
padding: 1.5rem;
}
}
@@ -1 +0,0 @@
<html><body bgcolor="#FFFFFF"></body></html>
@@ -1 +0,0 @@
<html><body bgcolor="#FFFFFF"></body></html>
@@ -1,118 +0,0 @@
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoJoomHero.Module.Assets
* INGROUP: MokoJoomHero.Module
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
* PATH: /src/js/template.js
* VERSION: 01.04.01
* BRIEF: Hero module JavaScript — image slideshow crossfade
*/
'use strict';
document.addEventListener('DOMContentLoaded', function () {
// Skip slideshow on mobile — video/images are hidden by CSS
if (window.matchMedia('(max-width: 768px)').matches) {
return;
}
// ── 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;
if (slides.length < 2) {
return;
}
setInterval(function () {
slides[current].classList.remove('mokojoomhero__slide--active');
slides[current].setAttribute('aria-hidden', 'true');
current = (current + 1) % slides.length;
slides[current].classList.add('mokojoomhero__slide--active');
slides[current].setAttribute('aria-hidden', 'false');
}, interval);
});
// ── Pause/resume videos when out of viewport ──
if (!('IntersectionObserver' in window)) {
return;
}
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
var hero = entry.target;
var video = hero.querySelector('video.mokojoomhero__video');
var iframe = hero.querySelector('iframe.mokojoomhero__video');
if (entry.isIntersecting) {
// Resume
if (video) {
video.play();
}
if (iframe) {
var src = iframe.src || '';
if (src.indexOf('youtube') !== -1) {
iframe.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
} else if (src.indexOf('vimeo') !== -1) {
iframe.contentWindow.postMessage('{"method":"play"}', '*');
}
}
} else {
// Pause
if (video) {
video.pause();
}
if (iframe) {
var src = iframe.src || '';
if (src.indexOf('youtube') !== -1) {
iframe.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
} else if (src.indexOf('vimeo') !== -1) {
iframe.contentWindow.postMessage('{"method":"pause"}', '*');
}
}
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.mokojoomhero').forEach(function (hero) {
observer.observe(hero);
});
// ── Mute/unmute toggle ──
document.querySelectorAll('.mokojoomhero__mute-toggle').forEach(function (btn) {
var hero = btn.closest('.mokojoomhero');
var video = hero.querySelector('video.mokojoomhero__video');
var iframe = hero.querySelector('iframe.mokojoomhero__video');
var icon = btn.querySelector('.mokojoomhero__mute-icon');
btn.addEventListener('click', function () {
var muted = btn.getAttribute('data-muted') === 'true';
if (video) {
video.muted = !muted;
}
if (iframe) {
var src = iframe.src || '';
if (src.indexOf('youtube') !== -1) {
var func = muted ? 'unMute' : 'mute';
iframe.contentWindow.postMessage('{"event":"command","func":"' + func + '","args":""}', '*');
} else if (src.indexOf('vimeo') !== -1) {
var vol = muted ? 1 : 0;
iframe.contentWindow.postMessage('{"method":"setVolume","value":' + vol + '}', '*');
}
}
btn.setAttribute('data-muted', muted ? 'false' : 'true');
btn.setAttribute('aria-label', muted ? 'Mute video' : 'Unmute video');
icon.textContent = muted ? '\u{1F50A}' : '\u{1F507}';
});
});
});
@@ -1,104 +0,0 @@
<?php
/**
* @package Joomla.Site
* @subpackage mod_mokojoomhero
*
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Helper\ModuleHelper;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Uri\Uri;
// Require the system plugin to be installed and enabled
if (!PluginHelper::isEnabled('system', 'mokojoomhero')) {
return;
}
/** @var \Joomla\CMS\Application\SiteApplication $app */
/** @var \stdClass $module */
/** @var \Joomla\Registry\Registry $params */
// Load module assets via Web Asset Manager (registry in media/mod_mokojoomhero/)
$wa = $app->getDocument()->getWebAssetManager();
$wa->getRegistry()->addExtensionRegistryFile('mod_mokojoomhero');
$wa->usePreset('mod_mokojoomhero');
// Module parameters
$heroMode = $params->get('heroMode', 'images');
$imageFolder = $params->get('imageFolder', 'images/heroes');
$imageCount = (int) $params->get('imageCount', 5);
$slideInterval = (int) $params->get('slideInterval', 5000);
$videoFile = $params->get('videoFile', '');
$heroHeight = $params->get('heroHeight', '60vh');
$overlayColor = $params->get('overlayColor', '#000000');
$overlayOpacity = (float) $params->get('overlayOpacity', 0.5);
$textAlign = $params->get('textAlign', 'center');
$textColor = $params->get('textColor', '#ffffff');
$heroContent = $params->get('heroContent', '');
$showCard = (bool) $params->get('showCard', 1);
$cardDelay = (int) $params->get('cardDelay', 0);
$showMuteToggle = (bool) $params->get('showMuteToggle', 0);
$localVideoFile = $params->get('localVideoFile', '');
$bgColor = $params->get('bgColor', '#003366');
$gradientStart = $params->get('gradientStart', '#003366');
$gradientEnd = $params->get('gradientEnd', '#006699');
$gradientAngle = (int) $params->get('gradientAngle', 135);
// Collect hero images
$heroImages = [];
if ($heroMode === 'images') {
$folderPath = JPATH_ROOT . '/' . ltrim($imageFolder, '/');
if (is_dir($folderPath)) {
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'svg'];
$all = [];
foreach (new DirectoryIterator($folderPath) as $file) {
if ($file->isFile() && in_array(strtolower($file->getExtension()), $allowed, true)) {
$all[] = $file->getFilename();
}
}
if ($all) {
shuffle($all);
$picked = array_slice($all, 0, min($imageCount, 5));
foreach ($picked as $filename) {
$heroImages[] = Uri::root() . $imageFolder . '/' . $filename;
}
}
}
}
// Build video URL — smartly detect YouTube, Vimeo, or local/direct file
$videoUrl = '';
$youtubeId = '';
$vimeoId = '';
if ($heroMode === 'localvideo' && $localVideoFile) {
$videoUrl = Uri::root() . ltrim($localVideoFile, '/');
} elseif ($heroMode === 'video' && $videoFile) {
// YouTube: watch, embed, shorts, youtu.be, with optional timestamps/params
if (preg_match('/(?:youtube\.com\/(?:watch\?.*v=|embed\/|shorts\/|v\/)|youtu\.be\/)([\w-]{11})/', $videoFile, $m)) {
$youtubeId = $m[1];
// Vimeo: vimeo.com/123456 or player.vimeo.com/video/123456
} elseif (preg_match('/vimeo\.com\/(?:video\/)?(\d+)/', $videoFile, $m)) {
$vimeoId = $m[1];
} else {
// Direct URL or local file path
$videoUrl = (strpos($videoFile, '://') !== false)
? $videoFile
: Uri::root() . ltrim($videoFile, '/');
}
}
// Module content from the editor (overlay text)
$content = $module->content ?? '';
require ModuleHelper::getLayoutPath('mod_mokojoomhero', $params->get('layout', 'default'));
@@ -1,247 +0,0 @@
<?xml version="1.0" encoding="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: MokoJoomHero.Module
INGROUP: MokoJoomHero
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
PATH: /src/mod_mokojoomhero.xml
VERSION: 01.00.20
BRIEF: Joomla module manifest — random hero image with content overlay
-->
<extension type="module" client="site" method="upgrade">
<name>Module - MokoJoomHero</name>
<creationDate>2026-05</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GPL-3.0-or-later</license>
<version>01.04.01-dev</version>
<description>Displays a random hero image slideshow or background video with content overlaid. Designed for MokoOnyx template. By Moko Consulting.</description>
<scriptfile>script.php</scriptfile>
<files>
<filename module="mod_mokojoomhero">mod_mokojoomhero.php</filename>
<filename>mod_mokojoomhero.xml</filename>
<filename>script.php</filename>
<folder>tmpl</folder>
<folder>language</folder>
</files>
<media destination="mod_mokojoomhero" folder="media">
<filename>joomla.asset.json</filename>
<folder>css</folder>
<folder>js</folder>
</media>
<languages folder="language">
<language tag="en-GB">en-GB/mod_mokojoomhero.ini</language>
<language tag="en-GB">en-GB/mod_mokojoomhero.sys.ini</language>
<language tag="en-US">en-US/mod_mokojoomhero.ini</language>
<language tag="en-US">en-US/mod_mokojoomhero.sys.ini</language>
</languages>
<config>
<fields name="params">
<fieldset name="basic">
<field
name="heroMode"
type="list"
label="MOD_MOKOJOOMHERO_MODE_LABEL"
description="MOD_MOKOJOOMHERO_MODE_DESC"
default="images"
>
<option value="images">MOD_MOKOJOOMHERO_MODE_IMAGES</option>
<option value="video">MOD_MOKOJOOMHERO_MODE_VIDEO</option>
<option value="localvideo">MOD_MOKOJOOMHERO_MODE_LOCALVIDEO</option>
<option value="color">MOD_MOKOJOOMHERO_MODE_COLOR</option>
<option value="gradient">MOD_MOKOJOOMHERO_MODE_GRADIENT</option>
</field>
<field
name="bgColor"
type="color"
label="MOD_MOKOJOOMHERO_BG_COLOR_LABEL"
description="MOD_MOKOJOOMHERO_BG_COLOR_DESC"
default="#003366"
showon="heroMode:color"
/>
<field
name="gradientStart"
type="color"
label="MOD_MOKOJOOMHERO_GRADIENT_START_LABEL"
description="MOD_MOKOJOOMHERO_GRADIENT_START_DESC"
default="#003366"
showon="heroMode:gradient"
/>
<field
name="gradientEnd"
type="color"
label="MOD_MOKOJOOMHERO_GRADIENT_END_LABEL"
description="MOD_MOKOJOOMHERO_GRADIENT_END_DESC"
default="#006699"
showon="heroMode:gradient"
/>
<field
name="gradientAngle"
type="number"
label="MOD_MOKOJOOMHERO_GRADIENT_ANGLE_LABEL"
description="MOD_MOKOJOOMHERO_GRADIENT_ANGLE_DESC"
default="135"
min="0"
max="360"
step="15"
showon="heroMode:gradient"
/>
<field
name="imageFolder"
type="text"
label="MOD_MOKOJOOMHERO_IMAGE_FOLDER_LABEL"
description="MOD_MOKOJOOMHERO_IMAGE_FOLDER_DESC"
default="images/heroes"
filter="path"
showon="heroMode:images"
/>
<field
name="imageCount"
type="number"
label="MOD_MOKOJOOMHERO_IMAGE_COUNT_LABEL"
description="MOD_MOKOJOOMHERO_IMAGE_COUNT_DESC"
default="5"
min="1"
max="5"
showon="heroMode:images"
/>
<field
name="slideInterval"
type="number"
label="MOD_MOKOJOOMHERO_SLIDE_INTERVAL_LABEL"
description="MOD_MOKOJOOMHERO_SLIDE_INTERVAL_DESC"
default="5000"
min="1000"
step="500"
showon="heroMode:images"
/>
<field
name="videoFile"
type="text"
label="MOD_MOKOJOOMHERO_VIDEO_FILE_LABEL"
description="MOD_MOKOJOOMHERO_VIDEO_FILE_DESC"
filter="string"
showon="heroMode:video"
/>
<field
name="localVideoFile"
type="media"
label="MOD_MOKOJOOMHERO_LOCAL_VIDEO_LABEL"
description="MOD_MOKOJOOMHERO_LOCAL_VIDEO_DESC"
types="videos"
showon="heroMode:localvideo"
/>
<field
name="heroHeight"
type="text"
label="MOD_MOKOJOOMHERO_HERO_HEIGHT_LABEL"
description="MOD_MOKOJOOMHERO_HERO_HEIGHT_DESC"
hint="MOD_MOKOJOOMHERO_HERO_HEIGHT_HINT"
default="60vh"
filter="string"
/>
<field
name="showMuteToggle"
type="radio"
layout="joomla.form.field.radio.switcher"
label="MOD_MOKOJOOMHERO_MUTE_TOGGLE_LABEL"
description="MOD_MOKOJOOMHERO_MUTE_TOGGLE_DESC"
default="0"
showon="heroMode:video,localvideo"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
</fieldset>
<fieldset name="content"
label="MOD_MOKOJOOMHERO_FIELDSET_CONTENT"
>
<field
name="heroContent"
type="editor"
label="MOD_MOKOJOOMHERO_CONTENT_LABEL"
description="MOD_MOKOJOOMHERO_CONTENT_DESC"
filter="safehtml"
buttons="true"
hide="readmore,pagebreak"
/>
<field
name="showCard"
type="radio"
layout="joomla.form.field.radio.switcher"
label="MOD_MOKOJOOMHERO_SHOW_CARD_LABEL"
description="MOD_MOKOJOOMHERO_SHOW_CARD_DESC"
default="1"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field
name="cardDelay"
type="number"
label="MOD_MOKOJOOMHERO_CARD_DELAY_LABEL"
description="MOD_MOKOJOOMHERO_CARD_DELAY_DESC"
default="0"
min="0"
max="5000"
step="250"
showon="showCard:1"
/>
</fieldset>
<fieldset name="advanced"
label="MOD_MOKOJOOMHERO_FIELDSET_OVERLAY"
>
<field
name="overlayColor"
type="color"
label="MOD_MOKOJOOMHERO_OVERLAY_COLOR_LABEL"
description="MOD_MOKOJOOMHERO_OVERLAY_COLOR_DESC"
default="#000000"
/>
<field
name="overlayOpacity"
type="range"
label="MOD_MOKOJOOMHERO_OVERLAY_OPACITY_LABEL"
description="MOD_MOKOJOOMHERO_OVERLAY_OPACITY_DESC"
default="0.5"
min="0"
max="1"
step="0.1"
/>
<field
name="textAlign"
type="list"
label="MOD_MOKOJOOMHERO_TEXT_ALIGN_LABEL"
description="MOD_MOKOJOOMHERO_TEXT_ALIGN_DESC"
default="center"
>
<option value="left">MOD_MOKOJOOMHERO_ALIGN_LEFT</option>
<option value="center">MOD_MOKOJOOMHERO_ALIGN_CENTER</option>
<option value="right">MOD_MOKOJOOMHERO_ALIGN_RIGHT</option>
</field>
<field
name="textColor"
type="color"
label="MOD_MOKOJOOMHERO_TEXT_COLOR_LABEL"
description="MOD_MOKOJOOMHERO_TEXT_COLOR_DESC"
default="#ffffff"
/>
</fieldset>
</fields>
</config>
</extension>
-26
View File
@@ -1,26 +0,0 @@
<?php
/**
* @package Joomla.Site
* @subpackage mod_mokojoomhero
*
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Installer\InstallerAdapter;
class Mod_MokojoomheroInstallerScript
{
public function postflight(string $type, InstallerAdapter $adapter): void
{
$heroesPath = JPATH_ROOT . '/images/heroes';
if (!is_dir($heroesPath)) {
Folder::create($heroesPath);
}
}
}
@@ -1 +0,0 @@
<html><body bgcolor="#FFFFFF"></body></html>
@@ -1 +0,0 @@
<html><body bgcolor="#FFFFFF"></body></html>
@@ -1,2 +0,0 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
@@ -1,5 +0,0 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
PLG_SYSTEM_MOKOJOOMHERO="System - MokoJoomHero"
PLG_SYSTEM_MOKOJOOMHERO_DESCRIPTION="System plugin for MokoJoomHero — license key validation"
@@ -1 +0,0 @@
<html><body bgcolor="#FFFFFF"></body></html>
@@ -1,2 +0,0 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
@@ -1,5 +0,0 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
PLG_SYSTEM_MOKOJOOMHERO="System - MokoJoomHero"
PLG_SYSTEM_MOKOJOOMHERO_DESCRIPTION="System plugin for MokoJoomHero — license key validation"
@@ -1 +0,0 @@
<html><body bgcolor="#FFFFFF"></body></html>
@@ -1,12 +0,0 @@
<?php
/**
* @package MokoJoomHero
* @subpackage plg_system_mokojoomhero
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
* @package MokoJoomHero
* @subpackage plg_system_mokojoomhero
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
-->
<extension type="plugin" group="system" method="upgrade">
<name>plg_system_mokojoomhero</name>
<version>01.04.01-dev</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GPL-3.0-or-later</license>
<description>PLG_SYSTEM_MOKOJOOMHERO_DESCRIPTION</description>
<namespace path="src">Joomla\Plugin\System\MokoJoomHero</namespace>
<files>
<filename plugin="mokojoomhero">mokojoomhero.php</filename>
<folder>services</folder>
<folder>src</folder>
</files>
<languages>
<language tag="en-GB">language/en-GB/plg_system_mokojoomhero.ini</language>
<language tag="en-GB">language/en-GB/plg_system_mokojoomhero.sys.ini</language>
<language tag="en-US">language/en-US/plg_system_mokojoomhero.ini</language>
<language tag="en-US">language/en-US/plg_system_mokojoomhero.sys.ini</language>
</languages>
</extension>
@@ -1 +0,0 @@
<html><body bgcolor="#FFFFFF"></body></html>
@@ -1,39 +0,0 @@
<?php
/**
* @package MokoJoomHero
* @subpackage plg_system_mokojoomhero
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Plugin\System\MokoJoomHero\Extension\MokoJoomHero;
return new class implements ServiceProviderInterface {
public function register(Container $container): void
{
$container->set(
PluginInterface::class,
function (Container $container) {
$plugin = new MokoJoomHero(
$container->get(DispatcherInterface::class),
(array) PluginHelper::getPlugin('system', 'mokojoomhero')
);
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};
@@ -1,90 +0,0 @@
<?php
/**
* @package MokoJoomHero
* @subpackage plg_system_mokojoomhero
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace Joomla\Plugin\System\MokoJoomHero\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
class MokoJoomHero extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'onAfterRoute' => 'onAfterRoute',
];
}
public function onAfterRoute(): void
{
$app = $this->getApplication();
if ($app->isClient('administrator')) {
$this->warnMissingLicenseKey();
}
}
/**
* Warn administrators once per session when no license key is configured.
*
* @return void
*/
private function warnMissingLicenseKey(): void
{
$session = Factory::getSession();
if ($session->get('mokojoomhero.license_warned', false)) {
return;
}
$user = Factory::getUser();
if ($user->guest || !$user->authorise('core.manage')) {
return;
}
$session->set('mokojoomhero.license_warned', true);
try {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('extra_query'))
->from($db->quoteName('#__update_sites'))
->where($db->quoteName('name') . ' = ' . $db->quote('MokoJoomHero Updates'))
->setLimit(1);
$db->setQuery($query);
$extraQuery = (string) $db->loadResult();
if (!empty($extraQuery)) {
parse_str($extraQuery, $parsed);
if (!empty($parsed['dlid']) && preg_match('/^MOKO-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/', $parsed['dlid'])) {
return;
}
}
$this->getApplication()->enqueueMessage(
'<strong>Moko Consulting License Key Required</strong> — '
. 'No download key is configured. Updates will not be available until a valid license key is entered. '
. 'Go to <a href="index.php?option=com_installer&view=updatesites">System → Update Sites</a> '
. 'and enter your license key (<code>MOKO-XXXX-XXXX-XXXX-XXXX</code>) in the Download Key field '
. 'for the MokoJoomHero update site.',
'warning'
);
} catch (\Throwable $e) {
// Don't break admin over a license check
}
}
}
@@ -1 +0,0 @@
<html><body bgcolor="#FFFFFF"></body></html>
@@ -1 +0,0 @@
<html><body bgcolor="#FFFFFF"></body></html>
-30
View File
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
* @package MokoJoomHero
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
-->
<extension type="package" method="upgrade">
<name>Package - MokoJoomHero</name>
<packagename>mokojoomhero</packagename>
<version>01.04.01-dev</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GPL-3.0-or-later</license>
<description>PKG_MOKOJOOMHERO_DESCRIPTION</description>
<scriptfile>pkg_script.php</scriptfile>
<files folder="packages">
<file type="module" id="mod_mokojoomhero" client="site">mod_mokojoomhero.zip</file>
<file type="plugin" id="mokojoomhero" group="system">plg_system_mokojoomhero.zip</file>
</files>
<updateservers>
<server type="extension" name="MokoJoomHero Updates">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/updates.xml</server>
</updateservers>
</extension>
-43
View File
@@ -1,43 +0,0 @@
<?php
/**
* @package MokoJoomHero
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Installer\InstallerAdapter;
class Pkg_MokoJoomHeroInstallerScript
{
/**
* Called after install/update.
*
* @param string $type Action type
* @param InstallerAdapter $parent Installer adapter
*
* @return void
*/
public function postflight(string $type, InstallerAdapter $parent): void
{
if ($type === 'install') {
$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'));
$db->setQuery($query);
$db->execute();
}
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
/**
* @package Joomla.Site
* @subpackage mod_mokojoomhero
*
* @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;
use Joomla\CMS\Factory;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Installer\InstallerAdapter;
class Mod_MokojoomheroInstallerScript
{
public function postflight(string $type, InstallerAdapter $adapter): void
{
// Create default image folder on fresh install
$heroesPath = JPATH_ROOT . '/images/heroes';
if (!is_dir($heroesPath)) {
Folder::create($heroesPath);
}
// Remove deprecated system plugin (was part of the old package extension)
$this->removeDeprecatedPlugin();
}
/**
* Uninstall plg_system_mokojoomhero if it exists — no longer needed.
*/
private function removeDeprecatedPlugin(): void
{
try {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('extension_id'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokojoomhero'));
$db->setQuery($query);
$extensionId = (int) $db->loadResult();
if ($extensionId) {
$installer = new \Joomla\CMS\Installer\Installer();
$installer->uninstall('plugin', $extensionId);
}
} catch (\Exception $e) {
// Non-critical — plugin may already be gone
}
}
}
@@ -6,47 +6,89 @@
*
* @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;
use Joomla\CMS\Language\Text;
/** @var string $heroMode */
/** @var array $heroImages */
/** @var int $slideInterval */
/** @var string $fadeType */
/** @var string $videoUrl */
/** @var string $youtubeId */
/** @var string $vimeoId */
/** @var string $heroHeight */
/** @var string $heroHeightMobile */
/** @var string $overlayColor */
/** @var string $overlayType */
/** @var float $overlayOpacity */
/** @var string $textAlign */
/** @var string $verticalAlign */
/** @var string $textColor */
/** @var string $contentSource */
/** @var int $articleId */
/** @var bool $useArticleTitle */
/** @var string $heroContent */
/** @var string $articleTitle */
/** @var array $slides */
/** @var bool $showCard */
/** @var int $cardDelay */
/** @var string $contentAnimation */
/** @var int $contentAnimationDelay */
/** @var bool $parallaxEnabled */
/** @var float $parallaxSpeed */
/** @var bool $showMuteToggle */
/** @var string $videoPoster */
/** @var bool $showScrollIndicator */
/** @var string $bgColor */
/** @var string $gradientStart */
/** @var string $gradientEnd */
/** @var int $gradientAngle */
/** @var string $content */
$moduleId = 'mod-mokojoomhero-' . $module->id;
// Convert hex overlay colour to rgba
$r = hexdec(substr($overlayColor, 1, 2));
$g = hexdec(substr($overlayColor, 3, 2));
$b = hexdec(substr($overlayColor, 5, 2));
$rgba = "rgba($r, $g, $b, $overlayOpacity)";
$rgbaOpaque = "rgba($r, $g, $b, $overlayOpacity)";
$rgbaTransparent = "rgba($r, $g, $b, 0)";
// Build overlay background based on type
$overlayDirections = [
'gradient-bottom' => 'to bottom',
'gradient-top' => 'to top',
'gradient-left' => 'to left',
'gradient-right' => 'to right',
];
if ($overlayType !== 'solid' && isset($overlayDirections[$overlayType])) {
$dir = $overlayDirections[$overlayType];
$overlayBg = "background: linear-gradient($dir, $rgbaTransparent, $rgbaOpaque);";
} else {
$overlayBg = "background-color: $rgbaOpaque;";
}
// Map vertical alignment to CSS align-items
$valignMap = ['top' => 'flex-start', 'center' => 'center', 'bottom' => 'flex-end'];
$valignCss = $valignMap[$verticalAlign] ?? 'center';
$heightAttr = htmlspecialchars($heroHeight, ENT_QUOTES, 'UTF-8');
?>
<?php if ($heroHeightMobile) : ?>
<style>#<?php echo $moduleId; ?> { --mokojoomhero-mobile-height: <?php echo htmlspecialchars($heroHeightMobile, ENT_QUOTES, 'UTF-8'); ?>; }</style>
<?php endif; ?>
<div id="<?php echo $moduleId; ?>" class="mokojoomhero" style="height: <?php echo $heightAttr; ?>;"
<?php if ($parallaxEnabled) : ?>
data-parallax="<?php echo $parallaxSpeed; ?>"
<?php endif; ?>
<?php if ($heroMode === 'images' && count($heroImages) > 1) : ?>
data-slides="<?php echo htmlspecialchars(json_encode($heroImages), ENT_QUOTES, 'UTF-8'); ?>"
data-interval="<?php echo $slideInterval; ?>"
data-transition="<?php echo htmlspecialchars($fadeType, ENT_QUOTES, 'UTF-8'); ?>"
<?php if ($slides) : ?>
data-slide-content="<?php echo htmlspecialchars(json_encode($slides), ENT_QUOTES, 'UTF-8'); ?>"
<?php endif; ?>
<?php endif; ?>
>
<?php // Background layer — solid colour, single image, slideshow, or video ?>
@@ -55,11 +97,20 @@ $heightAttr = htmlspecialchars($heroHeight, ENT_QUOTES, 'UTF-8');
<?php elseif ($heroMode === 'gradient') : ?>
<div class="mokojoomhero__color" style="background: linear-gradient(<?php echo $gradientAngle; ?>deg, <?php echo htmlspecialchars($gradientStart, ENT_QUOTES, 'UTF-8'); ?>, <?php echo htmlspecialchars($gradientEnd, ENT_QUOTES, 'UTF-8'); ?>);"></div>
<?php elseif ($heroMode === 'video' && $youtubeId) : ?>
<?php if ($videoPoster) : ?>
<div class="mokojoomhero__poster" style="background-image: url('<?php echo htmlspecialchars(\Joomla\CMS\Uri\Uri::root() . ltrim($videoPoster, '/'), ENT_QUOTES, 'UTF-8'); ?>');"></div>
<?php endif; ?>
<iframe class="mokojoomhero__video" src="https://www.youtube-nocookie.com/embed/<?php echo htmlspecialchars($youtubeId, ENT_QUOTES, 'UTF-8'); ?>?autoplay=1&mute=1&loop=1&playlist=<?php echo htmlspecialchars($youtubeId, ENT_QUOTES, 'UTF-8'); ?>&controls=0&showinfo=0&rel=0&modestbranding=1&playsinline=1&enablejsapi=1&origin=<?php echo htmlspecialchars(\Joomla\CMS\Uri\Uri::root(), ENT_QUOTES, 'UTF-8'); ?>" allow="autoplay; encrypted-media" allowfullscreen></iframe>
<?php elseif ($heroMode === 'video' && $vimeoId) : ?>
<?php if ($videoPoster) : ?>
<div class="mokojoomhero__poster" style="background-image: url('<?php echo htmlspecialchars(\Joomla\CMS\Uri\Uri::root() . ltrim($videoPoster, '/'), ENT_QUOTES, 'UTF-8'); ?>');"></div>
<?php endif; ?>
<iframe class="mokojoomhero__video" src="https://player.vimeo.com/video/<?php echo htmlspecialchars($vimeoId, ENT_QUOTES, 'UTF-8'); ?>?autoplay=1&muted=1&loop=1&background=1" allow="autoplay" allowfullscreen></iframe>
<?php elseif (($heroMode === 'video' || $heroMode === 'localvideo') && $videoUrl) : ?>
<video class="mokojoomhero__video" autoplay muted loop playsinline>
<?php if ($videoPoster) : ?>
<div class="mokojoomhero__poster" style="background-image: url('<?php echo htmlspecialchars(\Joomla\CMS\Uri\Uri::root() . ltrim($videoPoster, '/'), ENT_QUOTES, 'UTF-8'); ?>');"></div>
<?php endif; ?>
<video class="mokojoomhero__video" autoplay muted loop playsinline<?php if ($videoPoster) : ?> poster="<?php echo htmlspecialchars(\Joomla\CMS\Uri\Uri::root() . ltrim($videoPoster, '/'), ENT_QUOTES, 'UTF-8'); ?>"<?php endif; ?>>
<source src="<?php echo htmlspecialchars($videoUrl, ENT_QUOTES, 'UTF-8'); ?>">
</video>
<?php elseif ($heroImages) : ?>
@@ -77,20 +128,32 @@ $heightAttr = htmlspecialchars($heroHeight, ENT_QUOTES, 'UTF-8');
</button>
<?php endif; ?>
<?php if ($showScrollIndicator) : ?>
<button class="mokojoomhero__scroll-indicator" type="button" aria-label="Scroll down">
<svg class="mokojoomhero__scroll-chevron" viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
</button>
<?php endif; ?>
<?php // Overlay + content ?>
<div class="mokojoomhero__overlay" style="background-color: <?php echo $rgba; ?>;">
<div class="mokojoomhero__content" style="text-align: <?php echo htmlspecialchars($textAlign, ENT_QUOTES, 'UTF-8'); ?>; color: <?php echo htmlspecialchars($textColor, ENT_QUOTES, 'UTF-8'); ?>;">
<?php if ($heroContent || $module->showtitle) : ?>
<div class="mokojoomhero__overlay" style="<?php echo $overlayBg; ?> align-items: <?php echo $valignCss; ?>;">
<div class="mokojoomhero__content<?php if ($contentAnimation !== 'none') : ?> mokojoomhero__content--anim-<?php echo htmlspecialchars($contentAnimation, ENT_QUOTES, 'UTF-8'); ?><?php endif; ?>" style="text-align: <?php echo htmlspecialchars($textAlign, ENT_QUOTES, 'UTF-8'); ?>; color: <?php echo htmlspecialchars($textColor, ENT_QUOTES, 'UTF-8'); ?>;<?php if ($contentAnimationDelay) : ?> animation-delay: <?php echo $contentAnimationDelay; ?>ms;<?php endif; ?>">
<?php
$displayTitle = ($contentSource === 'article' && $useArticleTitle && $articleTitle)
? $articleTitle
: $module->title;
$showTitle = ($contentSource === 'article' && $useArticleTitle && $articleTitle) || $module->showtitle;
?>
<?php if ($heroContent || $showTitle) : ?>
<?php if ($showCard) : ?>
<div class="mokojoomhero__card"<?php if ($cardDelay) : ?> style="animation-delay: <?php echo $cardDelay; ?>ms;" data-card-delay="<?php echo $cardDelay; ?>"<?php endif; ?>>
<?php if ($module->showtitle) : ?>
<h2 class="mokojoomhero__title"><?php echo htmlspecialchars($module->title, ENT_QUOTES, 'UTF-8'); ?></h2>
<?php if ($showTitle) : ?>
<h2 class="mokojoomhero__title"><?php echo htmlspecialchars($displayTitle, ENT_QUOTES, 'UTF-8'); ?></h2>
<?php endif; ?>
<?php echo $heroContent; ?>
</div>
<?php else : ?>
<?php if ($module->showtitle) : ?>
<h2 class="mokojoomhero__title"><?php echo htmlspecialchars($module->title, ENT_QUOTES, 'UTF-8'); ?></h2>
<?php if ($showTitle) : ?>
<h2 class="mokojoomhero__title"><?php echo htmlspecialchars($displayTitle, ENT_QUOTES, 'UTF-8'); ?></h2>
<?php endif; ?>
<?php echo $heroContent; ?>
<?php endif; ?>
-35
View File
@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Joomla Extension Update Server XML
See: https://docs.joomla.org/Deploying_an_Update_Server
This file is the update server manifest for mod_mokojoomhero.
The Joomla installer polls this URL to check for new versions.
The manifest in this repository must reference this file:
<updateservers>
<server type="extension" priority="1" name="MokoJoomHero Updates">
https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/updates.xml
</server>
</updateservers>
When a new release is made, run `make release` or the release workflow to
prepend a new <update> entry to this file automatically.
-->
<updates>
<update>
<name>Module - MokoJoomHero</name>
<description>MokoJoomHero — A Joomla hero image module by Moko Consulting</description>
<element>mod_mokojoomhero</element>
<type>module</type>
<client>site</client>
<version>{{VERSION}}</version>
<downloads>
<downloadurl type="full" format="zip">
https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/v{{VERSION}}/mod_mokojoomhero.zip
</downloadurl>
</downloads>
<targetplatform name="joomla" version="(5|6).*" />
<php_minimum>8.1</php_minimum>
</update>
</updates>
-123
View File
@@ -1,123 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
VERSION: 01.07.00
-->
<updates>
<!-- Package update (new installs) -->
<update>
<name>Package - MokoJoomHero</name>
<description>Package - MokoJoomHero development build.</description>
<element>pkg_mokojoomhero</element>
<type>package</type>
<version>01.07.00-dev</version>
<creationDate>2026-06-02</creationDate>
<infourl title="Package - MokoJoomHero">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/tag/development</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/development/pkg_mokojoomhero-01.07.00-dev.zip</downloadurl>
</downloads>
<sha256></sha256>
<tags><tag>dev</tag></tags>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="joomla" version="(5|6)\..*"/>
</update>
<!-- Legacy module updates (kept for one major version cycle to allow migration) -->
<update>
<name>Module - MokoJoomHero</name>
<description>Module - MokoJoomHero dev build.</description>
<element>mod_mokojoomhero</element>
<type>module</type>
<client>site</client>
<version>01.07.00-dev</version>
<creationDate>2026-05-30</creationDate>
<infourl title="Module - MokoJoomHero">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/tag/development</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/development/mod_mokojoomhero-01.07.00-dev.zip</downloadurl>
</downloads>
<sha256>56ae99ad18e12ee52c60298adef5983aef788fe867d3e3a36957b314ad7eb386</sha256>
<tags><tag>dev</tag></tags>
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/CHANGELOG.md</changelogurl>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="joomla" version="(5|6)\..*"/>
</update>
<update>
<name>Module - MokoJoomHero</name>
<description>Module - MokoJoomHero alpha build.</description>
<element>mod_mokojoomhero</element>
<type>module</type>
<client>site</client>
<version>01.07.00-alpha</version>
<creationDate>2026-05-30</creationDate>
<infourl title="Module - MokoJoomHero">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/tag/alpha</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/alpha/mod_mokojoomhero-01.07.00-alpha.zip</downloadurl>
</downloads>
<sha256>56ae99ad18e12ee52c60298adef5983aef788fe867d3e3a36957b314ad7eb386</sha256>
<tags><tag>alpha</tag></tags>
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/CHANGELOG.md</changelogurl>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="joomla" version="(5|6)\..*"/>
</update>
<update>
<name>Module - MokoJoomHero</name>
<description>Module - MokoJoomHero beta build.</description>
<element>mod_mokojoomhero</element>
<type>module</type>
<client>site</client>
<version>01.07.00-beta</version>
<creationDate>2026-05-30</creationDate>
<infourl title="Module - MokoJoomHero">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/tag/beta</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/beta/mod_mokojoomhero-01.07.00-beta.zip</downloadurl>
</downloads>
<sha256>56ae99ad18e12ee52c60298adef5983aef788fe867d3e3a36957b314ad7eb386</sha256>
<tags><tag>beta</tag></tags>
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/CHANGELOG.md</changelogurl>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="joomla" version="(5|6)\..*"/>
</update>
<update>
<name>Module - MokoJoomHero</name>
<description>Module - MokoJoomHero rc build.</description>
<element>mod_mokojoomhero</element>
<type>module</type>
<client>site</client>
<version>01.07.00-rc</version>
<creationDate>2026-05-30</creationDate>
<infourl title="Module - MokoJoomHero">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/tag/release-candidate</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/release-candidate/mod_mokojoomhero-01.07.00-rc.zip</downloadurl>
</downloads>
<sha256>56ae99ad18e12ee52c60298adef5983aef788fe867d3e3a36957b314ad7eb386</sha256>
<tags><tag>rc</tag></tags>
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/CHANGELOG.md</changelogurl>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="joomla" version="(5|6)\..*"/>
</update>
<update>
<name>Module - MokoJoomHero</name>
<description>Module - MokoJoomHero stable build.</description>
<element>mod_mokojoomhero</element>
<type>module</type>
<client>site</client>
<version>01.07.00</version>
<creationDate>2026-05-30</creationDate>
<infourl title='Module - MokoJoomHero'>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/tag/stable</infourl>
<downloads>
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/stable/mod_mokojoomhero-01.07.00.zip</downloadurl>
</downloads>
<sha256>56ae99ad18e12ee52c60298adef5983aef788fe867d3e3a36957b314ad7eb386</sha256>
<tags><tag>stable</tag></tags>
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/CHANGELOG.md</changelogurl>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="joomla" version="(5|6)\..*" />
</update>
</updates>