diff --git a/docs/front-end/breakpoints.md b/docs/front-end/breakpoints.md
index a316f4200..78b6d3a07 100644
--- a/docs/front-end/breakpoints.md
+++ b/docs/front-end/breakpoints.md
@@ -13,7 +13,9 @@ $breakpoints: (
'x-large' '(min-width: 1280px)',
// secondary breakpoints - use sparingly
'small' '(min-width: 410px)',
- 'xx-large' '(min-width: 1800px)'
+ 'menu' '(min-width: 800px)',
+ 'xx-large' '(min-width: 1440px)',
+ 'xxx-large' '(min-width: 1800px)'
);
```
diff --git a/package-lock.json b/package-lock.json
index 6e0997fd6..78572bcfb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,8 @@
"license": "UNLICENSED",
"dependencies": {
"js-cookie": "^3.0.5",
- "lite-youtube-embed": "^0.3.2"
+ "lite-youtube-embed": "^0.3.2",
+ "swiper": "^11.2.1"
},
"devDependencies": {
"@types/jest": "^29.5.6",
@@ -12434,6 +12435,24 @@
"url": "https://opencollective.com/svgo"
}
},
+ "node_modules/swiper": {
+ "version": "11.2.1",
+ "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.1.tgz",
+ "integrity": "sha512-62G69+iQRIfUqTmJkWpZDcX891Ra8O9050ckt1/JI2H+0483g+gq0m7gINecDqMtDh2zt5dK+uzBRxGhGOOvQA==",
+ "funding": [
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/swiperjs"
+ },
+ {
+ "type": "open_collective",
+ "url": "http://opencollective.com/swiper"
+ }
+ ],
+ "engines": {
+ "node": ">= 4.7.0"
+ }
+ },
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
diff --git a/package.json b/package.json
index d06c1da4f..a5d21b808 100644
--- a/package.json
+++ b/package.json
@@ -69,6 +69,7 @@
},
"dependencies": {
"js-cookie": "^3.0.5",
- "lite-youtube-embed": "^0.3.2"
+ "lite-youtube-embed": "^0.3.2",
+ "swiper": "^11.2.1"
}
}
diff --git a/tbx/project_styleguide/templates/patterns/atoms/sprites/sprites.html b/tbx/project_styleguide/templates/patterns/atoms/sprites/sprites.html
index 1d83ea528..3268ce73c 100644
--- a/tbx/project_styleguide/templates/patterns/atoms/sprites/sprites.html
+++ b/tbx/project_styleguide/templates/patterns/atoms/sprites/sprites.html
@@ -39,6 +39,14 @@
+
+
+
+
+
+
+
+
diff --git a/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/dynamic_hero_block.html b/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/dynamic_hero_block.html
index 60ff7d776..459b358cb 100644
--- a/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/dynamic_hero_block.html
+++ b/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/dynamic_hero_block.html
@@ -1,17 +1,28 @@
-
- {% if value.static_text %}
{{ value.static_text }} {% endif %}
+
+ {% if value.static_text %}
{{ value.static_text }} {% endif %}
{% if value.dynamic_text %}
{% if value.dynamic_text|length > 1 %}
- {# If there's more than one dynamic text, show the controls for the loop. #}
-
- {% for text in value.dynamic_text %}
- {{ text }}
- {% endfor %}
-
+ {# If there's more than one dynamic text, show Swiper carousel structure and controls for the loop. #}
+
+
+ {% for text in value.dynamic_text %}
+
+ {# Fixed px values required to control the font size for all browsers and prevent slide overflow#}
+ {{ text }}
+
+ {% endfor %}
+
+
+
+ {% include "patterns/atoms/icons/icon.html" with name="chevron" classname="dynamic-hero__control--prev" %}
+ {% include "patterns/atoms/icons/icon.html" with name="pause" classname="dynamic-hero__control--pause" %}
+ {% include "patterns/atoms/icons/icon.html" with name="play" classname="dynamic-hero__control--play" %}
+ {% include "patterns/atoms/icons/icon.html" with name="chevron" classname="dynamic-hero__control--next" %}
+
{% else %}
- {# If there's only one dynamic text, don't show the controls for the loop. #}
+ {# If there's only one dynamic text, don't show Swiper carousel structure and controls for the loop. #}
{% for text in value.dynamic_text %}
-
{{ text }}
+
{{ text }}
{% endfor %}
{% endif %}
{% endif %}
diff --git a/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/dynamic_hero_block.yaml b/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/dynamic_hero_block.yaml
index bd89cbbb3..8c3da66ed 100644
--- a/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/dynamic_hero_block.yaml
+++ b/tbx/project_styleguide/templates/patterns/molecules/streamfield/blocks/dynamic_hero_block.yaml
@@ -2,6 +2,8 @@ context:
value:
static_text: We help charities and nonprofits
dynamic_text:
- - 'future-proof your funding streams.'
- - 'transform lives through digital innovation.'
- 'increase supporter acquisition and retention.'
+ - 'deliver life-changing digital services.'
+ - 'scale social impact through technology.'
+ - 'transform lives through digital innovation.'
+ - 'future-proof your funding streams.'
diff --git a/tbx/project_styleguide/templates/patterns/molecules/streamfield/stream_block_division.html b/tbx/project_styleguide/templates/patterns/molecules/streamfield/stream_block_division.html
deleted file mode 100644
index 2d497788d..000000000
--- a/tbx/project_styleguide/templates/patterns/molecules/streamfield/stream_block_division.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% load wagtailcore_tags %}
-
-{% if value %}
- {% for block in value %}
- {% include_block block with unique_id=block.id %}
- {% endfor %}
-{% endif %}
diff --git a/tbx/project_styleguide/templates/patterns/molecules/streamfield/stream_block_division.yaml b/tbx/project_styleguide/templates/patterns/molecules/streamfield/stream_block_division.yaml
deleted file mode 100644
index a84df542a..000000000
--- a/tbx/project_styleguide/templates/patterns/molecules/streamfield/stream_block_division.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-context:
- value:
- - dummy
-
-tags:
- include_block:
- block with unique_id=block.id:
- template_name: 'patterns/_pattern_library_only/streamfield/division_story_container.html'
diff --git a/tbx/project_styleguide/templates/patterns/pages/divisions/division_page.html b/tbx/project_styleguide/templates/patterns/pages/divisions/division_page.html
index 510baa7ea..ead81114b 100644
--- a/tbx/project_styleguide/templates/patterns/pages/divisions/division_page.html
+++ b/tbx/project_styleguide/templates/patterns/pages/divisions/division_page.html
@@ -4,13 +4,9 @@
{% block content %}
-
-
{{ page.title }}
+
{{ page.title }}
-
{{ page.label }}
-
-
-
{% include_block page.hero %}
+
{% include_block page.hero %}
{% include_block page.body %}
diff --git a/tbx/project_styleguide/templates/patterns/styleguide/components/components.html b/tbx/project_styleguide/templates/patterns/styleguide/components/components.html
index 3473469d2..c46af4656 100644
--- a/tbx/project_styleguide/templates/patterns/styleguide/components/components.html
+++ b/tbx/project_styleguide/templates/patterns/styleguide/components/components.html
@@ -174,6 +174,11 @@
Home page hero
{% include "patterns/molecules/home-page-hero/home-page-hero.html" %}
+
+
Dynamic hero
+ {% include "patterns/molecules/streamfield/blocks/dynamic_hero_block.html" %}
+
+
Values block
{% include "patterns/molecules/streamfield/blocks/values_block.html" %}
diff --git a/tbx/static_src/javascript/components/dynamic-hero.js b/tbx/static_src/javascript/components/dynamic-hero.js
new file mode 100644
index 000000000..872de97b9
--- /dev/null
+++ b/tbx/static_src/javascript/components/dynamic-hero.js
@@ -0,0 +1,72 @@
+import Swiper from 'swiper';
+// eslint-disable-next-line import/no-unresolved
+import { Autoplay } from 'swiper/modules';
+
+export default class DynamicHero {
+ static selector() {
+ return '[data-dynamic-hero]';
+ }
+
+ constructor(node) {
+ this.node = node;
+ this.swiperContainer = this.node.querySelector('.swiper');
+ this.slides = this.node.querySelectorAll('.swiper-slide');
+ this.nextButton = this.node.querySelector('[data-dynamic-hero-next]');
+ this.prevButton = this.node.querySelector('[data-dynamic-hero-prev]');
+ this.pauseButton = this.node.querySelector('[data-dynamic-hero-pause]');
+ this.playButton = this.node.querySelector('[data-dynamic-hero-play]');
+ this.bindEvents();
+ }
+
+ bindEvents() {
+ if (!this.swiperContainer) {
+ return;
+ }
+
+ // Check if the user prefers reduced motion - don't autoplay if they do
+ const isReduced =
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches ===
+ true;
+
+ this.swiper = new Swiper(this.swiperContainer, {
+ modules: [Autoplay],
+ direction: 'vertical',
+ slidesPerView: 1,
+ speed: 1000,
+ loop: true,
+ autoplay: {
+ delay: 5000,
+ enabled: !isReduced,
+ },
+ });
+
+ if (this.nextButton) {
+ this.nextButton.addEventListener('click', () =>
+ this.swiper.slideNext(),
+ );
+ }
+
+ if (this.prevButton) {
+ this.prevButton.addEventListener('click', () =>
+ this.swiper.slidePrev(),
+ );
+ }
+
+ if (this.pauseButton) {
+ this.pauseButton.addEventListener('click', () => {
+ this.swiper.autoplay.stop();
+ this.pauseButton.classList.add('hidden');
+ this.playButton.classList.remove('hidden');
+ });
+ }
+
+ if (this.playButton) {
+ this.playButton.addEventListener('click', () => {
+ this.swiper.autoplay.start();
+ this.swiper.slideNext();
+ this.playButton.classList.add('hidden');
+ this.pauseButton.classList.remove('hidden');
+ });
+ }
+ }
+}
diff --git a/tbx/static_src/javascript/main.js b/tbx/static_src/javascript/main.js
index 8636b23d1..d6111e12e 100755
--- a/tbx/static_src/javascript/main.js
+++ b/tbx/static_src/javascript/main.js
@@ -8,6 +8,7 @@ import YouTubeConsentManager from './components/youtube-embed';
import Tabs from './components/tabs';
import TableHint from './components/table-hint';
import ModeSwitcher from './components/mode-switcher';
+import DynamicHero from './components/dynamic-hero';
// IE11 polyfills
import foreachPolyfill from './polyfills/foreach-polyfill';
@@ -37,5 +38,6 @@ document.addEventListener('DOMContentLoaded', () => {
initComponent(Tabs);
initComponent(TableHint);
initComponent(ModeSwitcher);
+ initComponent(DynamicHero);
new DesktopCloseMenus();
});
diff --git a/tbx/static_src/sass/components/_dynamic-hero.scss b/tbx/static_src/sass/components/_dynamic-hero.scss
new file mode 100644
index 000000000..fe67f58b8
--- /dev/null
+++ b/tbx/static_src/sass/components/_dynamic-hero.scss
@@ -0,0 +1,74 @@
+@use 'config' as *;
+
+// Set fixed height to prevent Swiper's infinite height issue https://swiperjs.com/get-started#swiper-css-stylessize
+.dynamic-hero {
+ .swiper {
+ max-width: 100%;
+ height: 380px;
+
+ @include media-query(medium) {
+ height: 240px;
+ }
+
+ @include media-query(menu) {
+ height: 160px;
+ }
+
+ @include media-query(large) {
+ height: 320px;
+ }
+
+ @include media-query(xx-large) {
+ height: 250px;
+ }
+ }
+
+ .swiper-slide {
+ padding-bottom: 20px;
+ }
+
+ &__controls {
+ display: flex;
+ }
+
+ &__control {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 40px;
+ width: 40px;
+ opacity: 1;
+ visibility: visible;
+ transition: $transition;
+
+ &:hover,
+ &:focus {
+ color: var(--color--link-interaction);
+ }
+
+ &:focus {
+ @include focus-style();
+ }
+
+ &--prev {
+ transform: rotate(90deg);
+ }
+
+ &--next {
+ transform: rotate(-90deg);
+ }
+
+ &--pause,
+ &--play {
+ height: 40px;
+ width: 40px;
+ }
+
+ &.hidden {
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ position: absolute;
+ }
+ }
+}
diff --git a/tbx/static_src/sass/components/_grid.scss b/tbx/static_src/sass/components/_grid.scss
index 14da784e2..33edbc667 100644
--- a/tbx/static_src/sass/components/_grid.scss
+++ b/tbx/static_src/sass/components/_grid.scss
@@ -42,6 +42,15 @@
}
}
+ &__dynamic-hero {
+ grid-column: 2 / span 4;
+ margin-bottom: $spacer-medium;
+
+ @include media-query(large) {
+ grid-column: 2 / span 12;
+ }
+ }
+
&__intro {
margin-bottom: $spacer-medium;
grid-column: 2 / span 4;
diff --git a/tbx/static_src/sass/components/_image.scss b/tbx/static_src/sass/components/_image.scss
index 5185ae0d7..2ea8831fe 100644
--- a/tbx/static_src/sass/components/_image.scss
+++ b/tbx/static_src/sass/components/_image.scss
@@ -44,7 +44,7 @@
margin-right: $spacer-medium;
}
- @include media-query(xx-large) {
+ @include media-query(xxx-large) {
margin-right: 0;
}
}
diff --git a/tbx/static_src/sass/config/_variables.scss b/tbx/static_src/sass/config/_variables.scss
index 09fc23188..e2a68a8d9 100755
--- a/tbx/static_src/sass/config/_variables.scss
+++ b/tbx/static_src/sass/config/_variables.scss
@@ -287,8 +287,9 @@ $breakpoints: (
'x-large' '(min-width: 1280px)',
// secondary breakpoints - use sparingly
'small' '(min-width: 410px)',
- 'xx-large' '(min-width: 1800px)',
- 'menu' '(min-width: 800px)'
+ 'menu' '(min-width: 800px)',
+ 'xx-large' '(min-width: 1440px)',
+ 'xxx-large' '(min-width: 1800px)'
);
// Layout
diff --git a/tbx/static_src/sass/main.scss b/tbx/static_src/sass/main.scss
index fc56c53e1..20b3d85c4 100755
--- a/tbx/static_src/sass/main.scss
+++ b/tbx/static_src/sass/main.scss
@@ -1,5 +1,6 @@
// CSS from external vendors (available as npm)
@use '../../../node_modules/lite-youtube-embed/src/lite-yt-embed.css';
+@use '../../../node_modules/swiper/swiper.scss';
// Base
@use 'base/base';
@@ -19,6 +20,7 @@
@use 'components/contact-cta';
@use 'components/cookie-message';
@use 'components/division-signpost';
+@use 'components/dynamic-hero';
@use 'components/employee-owned-icon';
@use 'components/event-block';
@use 'components/featured-case-study';