From c04981290f8488215f26e7b3fa424bc58fb134d3 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Wed, 18 Mar 2026 10:15:38 -0500 Subject: [PATCH 1/3] Enhance carousel and button styles: add disabled state for carousel buttons, improve text decoration for content detail icons, and update card carousel template to include badge count. --- static/css/v3/carousel-buttons.css | 9 +++-- static/css/v3/content.css | 6 ++++ static/css/v3/post-cards.css | 15 ++++++++ static/js/carousel.js | 35 +++++++++++++++++-- templates/v3/includes/_cards_carousel_v3.html | 2 +- 5 files changed, 62 insertions(+), 5 deletions(-) diff --git a/static/css/v3/carousel-buttons.css b/static/css/v3/carousel-buttons.css index 4ece3c982..79ebc1e19 100644 --- a/static/css/v3/carousel-buttons.css +++ b/static/css/v3/carousel-buttons.css @@ -35,10 +35,15 @@ height: 12px; } -.carousel-buttons .btn-carousel:hover { +.carousel-buttons .btn-carousel:hover:not(:disabled) { color: var(--color-secondary-dark-blue, #0077B8); } -.carousel-buttons .btn-carousel[data-hover] { +.carousel-buttons .btn-carousel[data-hover]:not(:disabled) { color: var(--color-secondary-dark-blue, #0077B8); } + +.carousel-buttons .btn-carousel:disabled { + opacity: 0.5; + cursor: not-allowed; +} diff --git a/static/css/v3/content.css b/static/css/v3/content.css index fbbfc4a61..43c649971 100644 --- a/static/css/v3/content.css +++ b/static/css/v3/content.css @@ -87,6 +87,12 @@ a:hover .content-detail-icon:not(.content-detail-icon--contained) { .content-detail-icon__cta:hover { text-decoration: underline; } +.content-detail-icon__cta, +.content-detail-icon__cta:focus, +.content-detail-icon__cta:focus-visible { + text-decoration: underline; + text-decoration-skip-ink: none; +} .content-detail-icon--contained { background: transparent; diff --git a/static/css/v3/post-cards.css b/static/css/v3/post-cards.css index 3c8c56edb..d7e7fd4d6 100644 --- a/static/css/v3/post-cards.css +++ b/static/css/v3/post-cards.css @@ -2,6 +2,21 @@ text-decoration: none; } +.post-cards .content-detail-icon__cta, +.post-cards .content-card__cta { + text-decoration: underline; + text-decoration-skip-ink: none; +} + +.post-cards .content-detail-icon__cta:hover, +.post-cards .content-detail-icon__cta:focus, +.post-cards .content-detail-icon__cta:focus-visible, +.post-cards .content-card__cta:hover, +.post-cards .content-card__cta:focus, +.post-cards .content-card__cta:focus-visible { + text-decoration: underline; +} + .post-cards { font-family: var(--font-sans); color: var(--color-text-primary); diff --git a/static/js/carousel.js b/static/js/carousel.js index eefdf5d85..925c64ab2 100644 --- a/static/js/carousel.js +++ b/static/js/carousel.js @@ -3,9 +3,24 @@ const CAROUSEL_STEP_PX_FALLBACK = 320; const SCROLL_RESET_EPSILON = 2; + const SCROLL_END_EPSILON = 1; const DEFAULT_AUTOPLAY_MS = 4000; const CAROUSEL_ITEM_SELECTOR = '[data-carousel-item]'; + function updateArrowState(track, prevBtn, nextBtn) { + if (!track || !prevBtn || !nextBtn) return; + const maxScroll = track.scrollWidth - track.clientWidth; + if (maxScroll <= 0) { + prevBtn.disabled = true; + nextBtn.disabled = true; + return; + } + const atStart = track.scrollLeft <= 0; + const atEnd = track.scrollLeft >= maxScroll - SCROLL_END_EPSILON; + prevBtn.disabled = atStart; + nextBtn.disabled = atEnd; + } + function getStepPx(track) { const first = track.querySelector(CAROUSEL_ITEM_SELECTOR); if (first) { @@ -138,10 +153,26 @@ const prevBtn = controls.querySelector('[data-carousel-prev]'); const nextBtn = controls.querySelector('[data-carousel-next]'); if (prevBtn) { - prevBtn.addEventListener('click', function () { scrollCarousel(track, 'prev', true); }); + prevBtn.addEventListener('click', function () { + if (prevBtn.disabled) return; + scrollCarousel(track, 'prev', true); + }); } if (nextBtn) { - nextBtn.addEventListener('click', function () { scrollCarousel(track, 'next', true); }); + nextBtn.addEventListener('click', function () { + if (nextBtn.disabled) return; + scrollCarousel(track, 'next', true); + }); + } + + const syncArrows = function () { + updateArrowState(track, prevBtn, nextBtn); + }; + requestAnimationFrame(syncArrows); + track.addEventListener('scroll', syncArrows, { passive: true }); + if (typeof ResizeObserver !== 'undefined') { + const ro = new ResizeObserver(syncArrows); + ro.observe(track); } const autoplayDelay = root.getAttribute('data-carousel-autoplay'); diff --git a/templates/v3/includes/_cards_carousel_v3.html b/templates/v3/includes/_cards_carousel_v3.html index 343626862..8f68f68e1 100644 --- a/templates/v3/includes/_cards_carousel_v3.html +++ b/templates/v3/includes/_cards_carousel_v3.html @@ -39,7 +39,7 @@