diff --git a/network-api/networkapi/wagtailpages/templates/wagtailpages/pages/youtube-regrets-2021/fragments/carousel.html b/network-api/networkapi/wagtailpages/templates/wagtailpages/pages/youtube-regrets-2021/fragments/carousel.html new file mode 100644 index 00000000000..1de0940f3c2 --- /dev/null +++ b/network-api/networkapi/wagtailpages/templates/wagtailpages/pages/youtube-regrets-2021/fragments/carousel.html @@ -0,0 +1,100 @@ +{% load static i18n %} + + diff --git a/network-api/networkapi/wagtailpages/templates/wagtailpages/pages/youtube-regrets-2021/youtube_regrets_accordion.html b/network-api/networkapi/wagtailpages/templates/wagtailpages/pages/youtube-regrets-2021/youtube_regrets_accordion.html index f135f81a787..92a6da83742 100644 --- a/network-api/networkapi/wagtailpages/templates/wagtailpages/pages/youtube-regrets-2021/youtube_regrets_accordion.html +++ b/network-api/networkapi/wagtailpages/templates/wagtailpages/pages/youtube-regrets-2021/youtube_regrets_accordion.html @@ -107,13 +107,15 @@

{% trans "YouTube Regrets can alter people’s lives forev
-

YouTube Regrets are primarily a result of the recommendation algorithm, meaning videos that YouTube chooses to amplify, rather than videos that people sought out.

-

YouTube Regrets are primarily a result of the recommendation algorithm, meaning videos that YouTube chooses to amplify, rather than videos that people sought out.

-

YouTube Regrets are primarily a result of the recommendation algorithm, meaning videos that YouTube chooses to amplify, rather than videos that people sought out.

-

YouTube Regrets are primarily a result of the recommendation algorithm, meaning videos that YouTube chooses to amplify, rather than videos that people sought out.

-

YouTube Regrets are primarily a result of the recommendation algorithm, meaning videos that YouTube chooses to amplify, rather than videos that people sought out.

-

YouTube Regrets are primarily a result of the recommendation algorithm, meaning videos that YouTube chooses to amplify, rather than videos that people sought out.

-

YouTube Regrets are primarily a result of the recommendation algorithm, meaning videos that YouTube chooses to amplify, rather than videos that people sought out.

+

Our past work has shown that these video recommendations can have significant impacts on people’s lives.

+ + {# Carousel #} +
+
+ {% include "../youtube-regrets-2021/fragments/carousel.html" %} +
+
+
diff --git a/network-api/networkapi/wagtailpages/templates/wagtailpages/pages/youtube_regrets_base.html b/network-api/networkapi/wagtailpages/templates/wagtailpages/pages/youtube_regrets_base.html index 5f08acc2d56..7ab0bfd89f3 100644 --- a/network-api/networkapi/wagtailpages/templates/wagtailpages/pages/youtube_regrets_base.html +++ b/network-api/networkapi/wagtailpages/templates/wagtailpages/pages/youtube_regrets_base.html @@ -5,7 +5,7 @@ {% block bodyclass %}has-regrets-intro{% endblock %} {% block extended_head %} - + {% endblock %} {# Empty header; Do not add
to the page #} diff --git a/package-lock.json b/package-lock.json index d9728a41153..a9c811c1a7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -417,6 +417,11 @@ } } }, + "@glidejs/glide": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@glidejs/glide/-/glide-3.4.1.tgz", + "integrity": "sha512-C34AEcK1HjSyxilRToUL54I6KAoodojUbeRlXoruobZuG0eGm8xfDL+3kgkWj7AJK4EZtunSOYfoqMp70eDtwg==" + }, "@hapi/hoek": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.1.1.tgz", diff --git a/package.json b/package.json index 1f6b87f571c..05bc022c8f1 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "cypress-axe": "^0.12.2", "esbuild": "^0.8.57", "event-stream": "3.3.4", + "@glidejs/glide": "^3.4.1", "gsap": "^3.7.0", "js-cookie": "2.2.1", "moment": "^2.29.1", diff --git a/source/images/youtube-regrets/carousel/quote-1.png b/source/images/youtube-regrets/carousel/quote-1.png new file mode 100644 index 00000000000..692c1a14444 Binary files /dev/null and b/source/images/youtube-regrets/carousel/quote-1.png differ diff --git a/source/images/youtube-regrets/carousel/quote-2.png b/source/images/youtube-regrets/carousel/quote-2.png new file mode 100644 index 00000000000..19acb67ecf0 Binary files /dev/null and b/source/images/youtube-regrets/carousel/quote-2.png differ diff --git a/source/images/youtube-regrets/carousel/quote-3.png b/source/images/youtube-regrets/carousel/quote-3.png new file mode 100644 index 00000000000..0a3fd8c39a8 Binary files /dev/null and b/source/images/youtube-regrets/carousel/quote-3.png differ diff --git a/source/images/youtube-regrets/carousel/quote-4.png b/source/images/youtube-regrets/carousel/quote-4.png new file mode 100644 index 00000000000..253958ba2e6 Binary files /dev/null and b/source/images/youtube-regrets/carousel/quote-4.png differ diff --git a/source/images/youtube-regrets/carousel/quote-5.png b/source/images/youtube-regrets/carousel/quote-5.png new file mode 100644 index 00000000000..25e3e5cd065 Binary files /dev/null and b/source/images/youtube-regrets/carousel/quote-5.png differ diff --git a/source/images/youtube-regrets/speech-bubble-sad.svg b/source/images/youtube-regrets/speech-bubble-sad.svg new file mode 100644 index 00000000000..0a4f46f0f4f --- /dev/null +++ b/source/images/youtube-regrets/speech-bubble-sad.svg @@ -0,0 +1 @@ + diff --git a/source/js/foundation/pages/youtube-regrets/carousel-arrow-disabler.js b/source/js/foundation/pages/youtube-regrets/carousel-arrow-disabler.js new file mode 100644 index 00000000000..0753fed1a81 --- /dev/null +++ b/source/js/foundation/pages/youtube-regrets/carousel-arrow-disabler.js @@ -0,0 +1,50 @@ +function ArrowDisabler(Glide, Components) { + return { + mount() { + // Only in effect when rewinding is disabled + if (Glide.settings.rewind) { + return; + } + + Glide.on(['mount.after', 'run'], () => { + // Filter out arrows_control + Components.Controls.items.forEach((controlItem) => { + const left = controlItem.querySelector( + '[data-glide-dir="<"]', + ); + + const right = controlItem.querySelector( + '[data-glide-dir=">"]', + ); + + // Set left arrow state + if (left) { + if (Glide.index === 0) { + left.setAttribute('disabled', ''); // Disable on first slide + } else { + left.removeAttribute('disabled'); // Enable on other slides + } + } + + // Set right arrow state + if (right) { + // Glide.index is based on the active slide + // For bound: true, there will be no empty space & the last slide will never become active + // Hence add perView to correctly calculate the last slide + const lastSlideIndex = Glide.settings.bound + ? Glide.index + (Glide.settings.perView - 1) + : Glide.index; + + if (lastSlideIndex === Components.Sizes.length - 1) { + right.setAttribute('disabled', ''); // Disable on last slide + } else { + right.removeAttribute('disabled'); // Disable on other slides + } + } + }); + }); + }, + }; +} + +export default ArrowDisabler; diff --git a/source/js/foundation/pages/youtube-regrets/carousel.js b/source/js/foundation/pages/youtube-regrets/carousel.js new file mode 100644 index 00000000000..83edd56fc5b --- /dev/null +++ b/source/js/foundation/pages/youtube-regrets/carousel.js @@ -0,0 +1,88 @@ +import Glide from '@glidejs/glide'; +import ArrowDisabler from './carousel-arrow-disabler'; + +class Carousel { + static selector() { + return '[data-carousel]'; + } + + constructor(node) { + this.node = node; + this.createSlideshow(); + this.slideTotal = this.node.dataset.slidetotal; + this.slideshow.mount({ ArrowDisabler }); + this.bindEvents(); + this.setLiveRegion(); + } + + bindEvents() { + this.updateAriaRoles(); + + // Rerun after each slide move + this.slideshow.on('run.after', () => { + this.updateAriaRoles(); + this.updateLiveRegion(); + }); + } + + createSlideshow() { + this.slideshow = new Glide(this.node, { + type: 'slider', + startAt: 0, + gap: 0, + keyboard: true, + perTouch: 1, + touchRatio: 0.5, + perView: 1, + rewind: true, + autoplay: false, + + // Swipe animation on mobile but + // fade animation on desktop. + // They require different animation durations + animationDuration: window.innerWidth > 992 ? 0 : 300 + }); + } + + updateAriaRoles() { + for (const slide of this.node.querySelectorAll( + '.glide__slide:not(.glide__slide--active)', + )) { + const inactiveSlideAnchors = slide.querySelectorAll('a'); + slide.setAttribute('aria-hidden', 'true'); + inactiveSlideAnchors.forEach(function inactiveAnchor(el) { + el.setAttribute('tabindex', -1); + }); + } + const activeSlide = this.node.querySelector('.glide__slide--active'); + const activeSlideAnchors = activeSlide.querySelectorAll('a'); + activeSlide.removeAttribute('aria-hidden'); + activeSlideAnchors.forEach(function activeAnchor(el) { + el.removeAttribute('tabindex'); + }); + } + + // Sets a live region. This will announce which slide is showing to screen readers when previous / next buttons clicked + setLiveRegion() { + const liveRegion = this.node.querySelector('[data-liveregion]'); + const inner = document.createElement('div'); + inner.setAttribute('aria-live', 'polite'); + inner.setAttribute('aria-atomic', 'true'); + inner.setAttribute('data-liveregion', true); + liveRegion.appendChild(inner); + } + + // Update the live region that announces the next slide. + updateLiveRegion() { + this.node.querySelector( + '[data-liveregion]', + ).innerHTML = `0${this.slideshow.index + 1} /0${this.slideTotal}`; + } +} + +export const initYoutubeRegretsCarousel = () => { + const carousels = [...document.querySelectorAll("#yt-regrets-carousel")]; + carousels.map((carousel) => new Carousel(carousel)); +}; + +export default Carousel; diff --git a/source/js/main.js b/source/js/main.js index d0e82695c22..5f668c03034 100644 --- a/source/js/main.js +++ b/source/js/main.js @@ -26,6 +26,7 @@ import { initYoutubeRegretsReadMoreCategories } from "./foundation/pages/youtube import { initYoutubeRegretsResearchCountUp } from "./foundation/pages/youtube-regrets/count-up"; import { initYoutubeRegretsAccordions } from "./foundation/pages/youtube-regrets/accordion"; import { initYouTubeRegretsRecommendationsPieChart } from "./foundation/pages/youtube-regrets/recommendations-pie-chart"; +import { initYoutubeRegretsCarousel } from "./foundation/pages/youtube-regrets/carousel"; // Initializing component a11y browser console logging if ( @@ -121,6 +122,7 @@ let main = { initYoutubeRegretsResearchCountUp(); initYoutubeRegretsAccordions(); initYouTubeRegretsRecommendationsPieChart(); + initYoutubeRegretsCarousel(); } // YouTube Regrets Reporter page diff --git a/source/sass/views/youtube-regrets-2021-carousel.scss b/source/sass/views/youtube-regrets-2021-carousel.scss new file mode 100644 index 00000000000..fe4a4df7bdd --- /dev/null +++ b/source/sass/views/youtube-regrets-2021-carousel.scss @@ -0,0 +1,197 @@ +@import '../../../node_modules/@glidejs/glide/src/assets/sass/glide.core'; + +.carousel { + $root: &; + position: relative; + margin: 20px 0; + + @media (min-width: $bp-lg) { + transform: translateY(-70px); + margin: 60px 0; + } + + // Slides wrapper + &__slides { + list-style: none; + margin-bottom: 0; + margin-top: 70px; + + @media (min-width: $bp-lg) { + margin-top: 0; + } + } + + // Slide + &__item { + display: grid; + grid-row-gap: 20px; + + @media (min-width: $bp-lg) { + grid-template-columns: 450px 1fr; + grid-row-gap: 0; + grid-column-gap: 30px; + } + } + + &__item-content { + @media (min-width: $bp-sm) { + padding-right: 40px; + } + + @media (min-width: $bp-xl) { + padding-right: 0; + } + } + + &__item-text { + @include set-text-size(28px, 30px); + font-family: $font-zilla; + font-weight: 500; + + span { + color: #d63126; + } + } + + &__image { + @media (min-width: $bp-lg) { + display: flex; + justify-content: flex-end; + } + + img { + width: 100%; + + @media (min-width: $bp-lg) { + max-height: 300px; + object-fit: cover; + } + } + } + + // Next/prev + &__controls { + display: none; + + @media (min-width: $bp-lg) { + display: grid; + grid-template-columns: 450px 1fr; + grid-column-gap: 30px; + height: 70px; + transform: translateY(70px); + position: relative; + z-index: 1; + padding: 0 41px; + } + + @media (min-width: $bp-lg) { + padding: 0; + } + } + + &__controls-inner { + display: grid; + grid-column: 2; + + @media (min-width: $bp-lg) { + padding-right: 41px; + } + + @media (min-width: $bp-xl) { + padding-right: 0; + } + } + + &__button { + border: 0; + padding: 0; + background: transparent; + + svg { + fill: $white; + transition: fill 0.3s; + + .arrow { + transition: stroke 0.3s; + } + } + + &:hover { + svg { + fill: $black; + } + + .arrow { + stroke: $white; + } + } + } + + // Slide counter + &__count { + position: absolute; + top: 0; + left: 0; + right: 0; + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-column-gap: 30px; + margin-top: 10px; + + @media (min-width: $bp-lg) { + grid-template-columns: 450px 1fr; + top: calc(300px + 70px); + } + } + + &__count-first { + @include set-text-size(36px, 50px); + font-weight: 300; + margin-right: 10px; + } + + &__count-second { + @include set-text-size(18px, 24px); + font-weight: 300; + } + + // Sadface icons + &__speech-bubble-mobile { + width: 58px; + height: 49px; + + @media (min-width: $bp-lg) { + display: none; + } + } + + &__speech-bubble-desktop { + display: none; + + @media (min-width: $bp-lg) { + display: block; + } + } +} + +// Override to force fade transition on desktop +// Added this way for now while we test/decide +// https://github.com/glidejs/glide/issues/279#issuecomment-556933381 +@media (min-width: $bp-lg) { + .glide__slides { + transform:none !important; + width:auto !important; + display: grid; + grid-template-areas: 'slide'; //create a 1x1 grid where the single cell is called slide + } + .glide__slide { + position: relative; + opacity: 0; + transition: opacity 0.5s ease; + grid-area: slide; //assign all child slides to the cell + } + .glide__slide--active { + z-index:1; + opacity:1; + } +} diff --git a/source/sass/views/youtube-regrets-2021-utils.scss b/source/sass/views/youtube-regrets-2021-utils.scss new file mode 100644 index 00000000000..70a3edd6428 --- /dev/null +++ b/source/sass/views/youtube-regrets-2021-utils.scss @@ -0,0 +1,12 @@ +.full-width-container { + @media (min-width: $bp-lg) { + margin: 0 calc(50% - 50vw); + } + + &__inner { + @media (min-width: $bp-lg) { + max-width: 1100px; + margin: 0 auto; + } + } +} diff --git a/source/sass/views/youtube-regrets-2021.scss b/source/sass/views/youtube-regrets-2021.scss index d03218cafe6..e7b2845299f 100644 --- a/source/sass/views/youtube-regrets-2021.scss +++ b/source/sass/views/youtube-regrets-2021.scss @@ -1,6 +1,7 @@ .youtube-regrets-2021 { $font-changa: "Changa", sans-serif; $font-nunito: "Nunito Sans", sans-serif; + $font-zilla: "Zilla Slab", sans-serif; .fw-bold { font-weight: 700; @@ -366,4 +367,7 @@ } } } + + @import "./youtube-regrets-2021-utils"; + @import "./youtube-regrets-2021-carousel"; }