feat: replace hero video fallback shortcode with dynamic Swiper slider and enable observer settings for swiper instances

This commit is contained in:
2026-07-30 09:52:08 +07:00
parent f4d566a56d
commit c3b0248df8
8 changed files with 216 additions and 107 deletions
@@ -14,18 +14,29 @@
" "
/> />
<div class="f--ambient-video" id="hero-ambient-video"> <div class="f--ambient-video" id="hero-ambient-video">
<video role="presentation" tabindex="-1" loop="" autoplay="" playsinline="" muted="" <video
id="hero-video" onerror="handleVideoError()"> role="presentation"
tabindex="-1"
loop=""
autoplay=""
playsinline=""
muted=""
id="hero-video"
onerror="handleVideoError()"
>
<source <source
src="http://localhost:8080/uploads/2026/07/9375f45b-027a-4fb4-bd18-66889309280d.mp4" src="http://localhost:8080/uploads/2026/07/9375f45b-027a-4fb4-bd18-66889309280d.mp4"
type="video/mp4" onerror="handleVideoError()" /> type="video/mp4"
onerror="handleVideoError()"
/>
</video> </video>
</div> </div>
<div id="hero-video-fallback" <div
style="display: none; position: absolute; top:0; left:0; width:100%; height:100%; z-index: 2;"> id="hero-video-fallback"
<th:block style="display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 2"
th:utext="${@hookManager.applyFilters('the_content', '[plugin:swiper-slider slug=''video-fallback'']')}"> >
<th:block th:utext="${@hookManager.applyFilters('the_content', '[plugin:swiper-slider slug=''video-fallback-slider'']')}">
</th:block> </th:block>
</div> </div>
@@ -78,40 +89,50 @@
<div class="text-container centered"> <div class="text-container centered">
<div class="slide-text-container-inner"> <div class="slide-text-container-inner">
<h3> <h3>
<a href="https://www.umass.edu/gateway/why-umass">Stroke International School <a href="https://www.umass.edu/gateway/why-umass">Stroke International School 2026.</a>
2026.</a>
</h3> </h3>
<div class="f--field f--button"> <div class="f--field f--button">
<a href="https://www.umass.edu/gateway/why-umass" <a
href="https://www.umass.edu/gateway/why-umass"
class="button-secondary button-context-dark" class="button-secondary button-context-dark"
aria-label="Learn More Learn more about UMass Amherst." aria-label="Learn More Learn more about UMass Amherst."
data-component-id="umass_base:button"> data-component-id="umass_base:button"
>
<span class="button-text"> Learn More </span> <span class="button-text"> Learn More </span>
</a> </a>
</div> </div>
<nav class="mc--menu mc--info-menu" <nav class="mc--menu mc--info-menu" th:if="${infoMenu != null and not #lists.isEmpty(infoMenu.items)}">
th:if="${infoMenu != null and not #lists.isEmpty(infoMenu.items)}">
<ul class="menu m--menu m--info-menu"> <ul class="menu m--menu m--info-menu">
<li class="menu-item menu-item--expanded"> <li class="menu-item menu-item--expanded">
<details class="utility-button-wrapper mc--info-menu-container"> <details class="utility-button-wrapper mc--info-menu-container">
<summary type="button" <summary
type="button"
aria-label="Display submenu for Information menu" aria-label="Display submenu for Information menu"
aria-expanded="false" aria-haspopup="true" aria-expanded="false"
class="utility-button arrow-toggle information-for-toggle"> aria-haspopup="true"
class="utility-button arrow-toggle information-for-toggle"
>
<span th:text="${infoMenu.name}">Info For</span> <span th:text="${infoMenu.name}">Info For</span>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" <svg
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" version="1.1"
viewBox="0 0 16 7" enable-background="new 0 0 16 7" xmlns="http://www.w3.org/2000/svg"
xml:space="preserve" width="16" height="7" class="arrow"> xmlns:xlink="http://www.w3.org/1999/xlink"
<polygon fill-rule="evenodd" clip-rule="evenodd" x="0px"
fill="#ffffff" points="7,7 0,0 16,0 "></polygon> y="0px"
viewBox="0 0 16 7"
enable-background="new 0 0 16 7"
xml:space="preserve"
width="16"
height="7"
class="arrow"
>
<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#ffffff" points="7,7 0,0 16,0 "></polygon>
</svg> </svg>
</summary> </summary>
<div class="submenu-wrapper"> <div class="submenu-wrapper">
<ul class="submenu"> <ul class="submenu">
<li class="menu-item" th:each="item : ${infoMenu.items}"> <li class="menu-item" th:each="item : ${infoMenu.items}">
<a th:href="${item.url}" <a th:href="${item.url}" th:text="${item.label}">Prospective Students</a>
th:text="${item.label}">Prospective Students</a>
</li> </li>
</ul> </ul>
</div> </div>
@@ -119,8 +140,7 @@
<div class="submenu-wrapper-desktop"> <div class="submenu-wrapper-desktop">
<ul class="submenu"> <ul class="submenu">
<li class="menu-item" th:each="item : ${infoMenu.items}"> <li class="menu-item" th:each="item : ${infoMenu.items}">
<a th:href="${item.url}" th:text="${item.label}">Prospective <a th:href="${item.url}" th:text="${item.label}">Prospective Students</a>
Students</a>
</li> </li>
</ul> </ul>
</div> </div>
@@ -9,7 +9,7 @@
<article class="relative p-3 lg:p-4 bg-white rounded-lg border border-gray-100 group flex flex-col justify-between text-left w-full h-full"> <article class="relative p-3 lg:p-4 bg-white rounded-lg border border-gray-100 group flex flex-col justify-between text-left w-full h-full">
<div class="flex flex-col gap-3.5"> <div class="flex flex-col gap-3.5">
<a class="relative overflow-hidden rounded block aspect-[9/5]" href="{{linkUrl}}" th:href="@{${item.linkUrl}}"> <a class="relative overflow-hidden rounded block aspect-[9/5]" href="{{linkUrl}}" th:href="@{${item.linkUrl}}">
<img alt="{{title}}" th:alt="${item.title}" class="object-cover h-full w-full" src="{{imageUrl}}" th:src="@{${item.imageUrl}}"/> <img alt="{{title}}" th:alt="${item.title}" class="object-cover h-full w-full" data-src="{{imageUrl}}" th:src="@{${item.imageUrl}}"/>
<div class="absolute inset-0 w-full h-full bg-black/40 opacity-0 lg:group-hover:opacity-100 duration-300 ease-in-out"></div> <div class="absolute inset-0 w-full h-full bg-black/40 opacity-0 lg:group-hover:opacity-100 duration-300 ease-in-out"></div>
</a> </a>
<div class="px-1.5 w-full flex flex-col gap-3.5"> <div class="px-1.5 w-full flex flex-col gap-3.5">
File diff suppressed because one or more lines are too long
@@ -367,6 +367,13 @@ document.addEventListener("DOMContentLoaded", function() {
new Swiper('.swiper-{{slug}}', { new Swiper('.swiper-{{slug}}', {
slidesPerView: 'auto', slidesPerView: 'auto',
spaceBetween: 16, spaceBetween: 16,
observer: true,
observeParents: true,
autoplay: {
delay: 3500,
disableOnInteraction: false,
pauseOnMouseEnter: true,
},
navigation: { navigation: {
nextEl: '.btn-navigation-{{slug}}-next', nextEl: '.btn-navigation-{{slug}}-next',
prevEl: '.btn-navigation-{{slug}}-prev', prevEl: '.btn-navigation-{{slug}}-prev',
@@ -1431,3 +1431,7 @@ figure.table table tr:hover {
color: #9b1b1b !important; color: #9b1b1b !important;
transform: scale(1.08); transform: scale(1.08);
} }
.h-full {
height: 100%;
}
@@ -9,6 +9,8 @@ document.addEventListener('DOMContentLoaded', function () {
const courseSwiper = new Swiper('.courseSwiper', { const courseSwiper = new Swiper('.courseSwiper', {
loop: true, loop: true,
spaceBetween: 20, // Khoảng cách giữa các card spaceBetween: 20, // Khoảng cách giữa các card
observer: true,
observeParents: true,
autoplay: { autoplay: {
delay: 3500, // Tự động trượt sau 3.5 giây delay: 3500, // Tự động trượt sau 3.5 giây
disableOnInteraction: false, disableOnInteraction: false,
@@ -20,6 +20,14 @@
} }
/* Desktop/Tablet: Video most, Slide second, Image last */ /* Desktop/Tablet: Video most, Slide second, Image last */
@media (min-width: 769px) { @media (min-width: 769px) {
.image-video-container {
display: grid;
grid-template-rows: 100%;
}
.image-video-container > * {
grid-column: 1;
grid-row: 1;
}
.image-video-container > img { .image-video-container > img {
z-index: 1 !important; z-index: 1 !important;
width: 100%; width: 100%;
@@ -29,12 +37,9 @@
#hero-video-fallback { #hero-video-fallback {
z-index: 2 !important; z-index: 2 !important;
} }
#hero-ambient-video { /* #hero-ambient-video {
z-index: 3 !important; z-index: 3 !important;
} } */
.content-top .text-container.centered {
z-index: 4 !important;
}
video#hero-video { video#hero-video {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -69,8 +74,29 @@
id="hero-video-fallback" id="hero-video-fallback"
style="display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 2" style="display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 2"
> >
<th:block th:utext="${hookManager.applyFilters('the_content', '[plugin:swiper-slider slug=''video-fallback'']')}"> <div class="swiper hero-fallback-swiper w-full h-full">
</th:block> <div class="swiper-wrapper">
<!-- Dynamic Slides from Plugin Database -->
<th:block th:utext="${hookManager.applyFilters('swiper_slider_items', 'video-fallback-slider')}"></th:block>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
if (typeof Swiper !== 'undefined') {
new Swiper('.hero-fallback-swiper', {
effect: 'fade', // Optional: smooth fade transition for hero
loop: true,
observer: true,
observeParents: true,
autoplay: {
delay: 4000,
disableOnInteraction: false,
},
});
}
});
</script>
</div> </div>
<script> <script>
@@ -73,8 +73,29 @@
id="hero-video-fallback" id="hero-video-fallback"
style="display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 2" style="display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 2"
> >
<th:block th:utext="${hookManager.applyFilters('the_content', '[plugin:swiper-slider slug=''video-fallback'']')}"> <div class="swiper hero-fallback-swiper w-full h-full">
</th:block> <div class="swiper-wrapper">
<!-- Dynamic Slides from Plugin Database -->
<th:block th:utext="${hookManager.applyFilters('swiper_slider_items', 'video-fallback-slider')}"></th:block>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
if (typeof Swiper !== 'undefined') {
new Swiper('.hero-fallback-swiper', {
effect: 'fade', // Optional: smooth fade transition for hero
loop: true,
observer: true,
observeParents: true,
autoplay: {
delay: 4000,
disableOnInteraction: false,
},
});
}
});
</script>
</div> </div>
<script> <script>