From a35ba3a597f0c54ef77d9792c4b073f0a3ad1803 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Tue, 24 Feb 2026 14:31:41 +0100 Subject: [PATCH] Dashboard: Replace automatic banner rotation with random initial display and manual arrows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the automatic timed banner rotation with a random initial banner selection and small, manually operated arrow buttons for navigation. Previous behavior can be restored by setting BannerRotation.automaticRotationEnabled to true. The original slides used position:absolute + opacity for transitions. This was changed to display:none/block because absolutely positioned children don't contribute to their parent container's dimensions, causing the banner containers to collapse to zero width/height. The arrow buttons (position:absolute, top:50%) need a parent with real dimensions to center correctly. With display:none/block, the active slide determines the container's natural size. The fixed height:60px on .gold-banner was removed for the same reason — it clipped banner images that are taller than 60px. An explicit height was added to .header-banner to ensure the arrow buttons can vertically center via top:50% within the platinum banner area. --- src/serena/resources/dashboard/dashboard.css | 53 ++++++++++--- src/serena/resources/dashboard/dashboard.js | 80 ++++++++++++++++++-- src/serena/resources/dashboard/index.html | 4 + 3 files changed, 118 insertions(+), 19 deletions(-) diff --git a/src/serena/resources/dashboard/dashboard.css b/src/serena/resources/dashboard/dashboard.css index 1c0a8a47..662fed57 100644 --- a/src/serena/resources/dashboard/dashboard.css +++ b/src/serena/resources/dashboard/dashboard.css @@ -207,6 +207,7 @@ body { top: 0; left: 0; order: 2; + height: var(--header-height); max-height: var(--header-height); } @@ -311,17 +312,13 @@ body { } .platinum-banner-slide { - position: absolute; - top: 0; - left: 0; + display: none; pointer-events: none; height: 100%; - opacity: 0; - transition: opacity 0.5s ease-in-out; } .platinum-banner-slide.active { - opacity: 1; + display: block; pointer-events: auto; } @@ -351,19 +348,16 @@ body { .gold-banner { position: relative; width: 100%; - height: 60px; overflow: hidden; } .gold-banner-slide { - position: absolute; + display: none; pointer-events: none; - opacity: 0; - transition: opacity 0.5s ease-in-out; } .gold-banner-slide.active { - opacity: 1; + display: block; pointer-events: auto; } @@ -372,6 +366,43 @@ body { object-fit: contain; } +/* Banner Arrow Navigation */ +.banner-arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + z-index: 10; + background: rgba(128, 128, 128, 0.15); + color: var(--text-muted); + border: none; + font-size: 18px; + line-height: 1; + width: 24px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 4px; + opacity: 0.3; + transition: opacity 0.2s ease, background-color 0.2s ease, color 0.2s ease; + padding: 0; +} + +.banner-arrow:hover { + opacity: 0.8; + background: rgba(128, 128, 128, 0.4); + color: var(--text-primary); +} + +.banner-arrow-left { + left: 0; +} + +.banner-arrow-right { + right: 0; +} + .page-view { /*max-width: 1600px;*/ margin: 0 auto; diff --git a/src/serena/resources/dashboard/dashboard.js b/src/serena/resources/dashboard/dashboard.js index 2b4af7fa..f59dbb6a 100644 --- a/src/serena/resources/dashboard/dashboard.js +++ b/src/serena/resources/dashboard/dashboard.js @@ -52,8 +52,17 @@ function updateThemeAwareImage($img, theme=null) { } } +/** + * Manages banner loading, display, and navigation. + * + * When automaticRotationEnabled is true, banners rotate on a timer and arrow + * buttons are hidden. When false (the current default), a random initial + * banner is shown and the user navigates manually via arrow buttons. + */ class BannerRotation { constructor() { + this.automaticRotationEnabled = false; + this.platinumIndex = 0; this.goldIndex = 0; this.platinumTimer = null; @@ -67,8 +76,18 @@ class BannerRotation { init() { let self = this; this.loadBanners(function() { - self.startPlatinumRotation(); - self.startGoldRotation(); + self.randomizeInitialBanner('platinum'); + self.randomizeInitialBanner('gold'); + + if (self.automaticRotationEnabled) { + self.startPlatinumRotation(); + self.startGoldRotation(); + // Hide arrows entirely when rotation is automatic + $('.banner-arrow').hide(); + } else { + self.hideArrowsIfSingle(); + self.bindArrowButtons(); + } }); } @@ -119,6 +138,23 @@ class BannerRotation { }, this.platinumInterval); } + randomizeInitialBanner(type) { + const slideClass = type === 'platinum' ? '.platinum-banner-slide' : '.gold-banner-slide'; + const $slides = $(slideClass); + const total = $slides.length; + + if (total === 0) return; + + const randomIndex = Math.floor(Math.random() * total); + if (type === 'platinum') { + this.platinumIndex = randomIndex; + } else { + this.goldIndex = randomIndex; + } + $slides.removeClass('active'); + $slides.eq(randomIndex).addClass('active'); + } + startGoldRotation() { const self = this; this.goldTimer = setInterval(() => { @@ -126,6 +162,30 @@ class BannerRotation { }, this.goldInterval); } + hideArrowsIfSingle() { + if ($('.platinum-banner-slide').length <= 1) { + $('#platinum-banners .banner-arrow').hide(); + } + if ($('.gold-banner-slide').length <= 1) { + $('#gold-banners .banner-arrow').hide(); + } + } + + bindArrowButtons() { + let self = this; + $('.banner-arrow').on('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + const target = $(this).data('target'); + const direction = $(this).hasClass('banner-arrow-right') ? 'next' : 'prev'; + if (target === 'platinum') { + self.rotatePlatinum(direction); + } else { + self.rotateGold(direction); + } + }); + } + rotatePlatinum(direction) { const $slides = $('.platinum-banner-slide'); const total = $slides.length; @@ -145,9 +205,11 @@ class BannerRotation { // Add active class to new slide $slides.eq(this.platinumIndex).addClass('active'); - // Reset timer - clearInterval(this.platinumTimer); - this.startPlatinumRotation(); + // Reset timer when in automatic rotation mode + if (this.automaticRotationEnabled) { + clearInterval(this.platinumTimer); + this.startPlatinumRotation(); + } } rotateGold(direction) { @@ -169,9 +231,11 @@ class BannerRotation { // Add active class to new group $groups.eq(this.goldIndex).addClass('active'); - // Reset timer - clearInterval(this.goldTimer); - this.startGoldRotation(); + // Reset timer when in automatic rotation mode + if (this.automaticRotationEnabled) { + clearInterval(this.goldTimer); + this.startGoldRotation(); + } } } diff --git a/src/serena/resources/dashboard/index.html b/src/serena/resources/dashboard/index.html index 1fbf0a45..c8bf75d7 100644 --- a/src/serena/resources/dashboard/index.html +++ b/src/serena/resources/dashboard/index.html @@ -26,6 +26,8 @@
+ +
@@ -147,6 +149,8 @@
+ +