Components
53
Accordion Items Animated Gallery Basic Hero Basic Map Case Studies Feed Case Studies Hero Case Study Hero Case Study Slider Column Content Content Grid Content Image Content Slider Cta Cta Blocks Embed Block Enriched Accordion Error Event Feed Example Features Slider Full Width Image Hero Full Width Media Image And Form Image Content Image Split Content Image Stats Logo Slider Map Post Feed Pricing Publications Feed Simple Accordion Simple Header Simple Hero Solutions Animated Block Split Content Split Content Carousel Split Content Content Split Content Post Split Content Pricing Split Content Video Split Image Hero Stats Slider Team Carousel Team Hero Team Listing Testimonial Case Study Timeline Trivia Bar Two Column Rich Text Vacancies Table Video Video Expand

Timeline

View example

How does it work?

Consultation & Customisation

We begin by consulting with you to understand your needs and package our reporting solutions into something that works best for . Over 6-8 weeks, we develop a customised reporting framework and actionable plan.

Data Collection

Next, we facilitate data entry and upload into our system, engage stakeholders through notifications for data submission, and ensure compliance with reporting standards.

Data Aggregation

The platform then aggregates the collected data by various criteria, such as site, supplier, and sustainability theme, to provide a comprehensive overview.

Data Analysis & Measurement

Analyse your performance against targets, utilising performance dashboards with red, amber, green KPIs that highlight performance levels.

Reporting

Generate detailed reports, export PDFs, and create standard action plans to communicate sustainability performance and metrics to key stakeholders.

Management & Improvement

Use these insights to make data-driven decision, implement actions and continuously improve your processes with support from our dedicated experts.

Collaborate on targets
Collect data
Analyse & aggregate
Assess
Report, repeat annually
Monitor and manage progress
There are no ACF fields assigned to this component.

				
@import "../../resources/scss/util/variables";
@import "../../resources/scss/util/mixins";

.block-timeline {
	padding-top: clamp(40px, 8vw, 70px);
	padding-bottom: clamp(40px, 8vw, 70px);

    .container {
        max-width: 98vw;
    }

    .row {
        background-color: var(--primary);
        color: var(--white);
        border-radius: 50px;
        height: 90vh;

        @include bp($md, true) {
            border-radius: 30px;
        }
    }

    &--blue {
        .row {
            background-color: var(--accentBlue);
        }
    }

    &__heading {
        position: absolute;
        top: 1rem;
        padding-top: 2rem;
        padding-left: 1rem;
        font-family: Montserrat;
        z-index: 1;

        @include bp($lg, true) {
            padding-top: 1rem;
        }

        .heading {
            color: var(--white);
            font-size: 34px;
            font-weight: 600;
            line-height: 1;

            @include bp($lg, true) {
                font-size: 22px;
            }
        }

        @include bp($lg) {
            padding-left: 5%;
            width: 100%;
            left: 0;
        }
    }

    &__navigation {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        gap: 1rem;
        z-index: 1;

        @include bp($lg) {
            flex-direction: column;
        }

        @include bp($lg, true) {
            top: 50%;
        }

        &-dot {
            display: inline-block;
            text-decoration: none;
            width: 2rem;
            height: 2rem;
            border-radius: 50%;
            position: relative;
            background-color: var(--white);

            @include bp($lg, true) {
                width: 1rem;
                height: 1rem;
            }

            &:after {
                content: '';
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background-color: var(--secondary);
                width: 1.25rem;
                height: 1.25rem;
                border-radius: 50%;
                opacity: 0;
                transition: opacity 0.3s;

                @include bp($lg, true) {
                    width: 0.66rem;
                    height: 0.66rem;
                }
            }

            &.active {
                &:after {
                    opacity: 1;
                }
            }
        }
    }

    &__content {
        position: relative;

        @include bp($lg, true) {
            height: 45vh;
        }

        &-wrapper {
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            position: absolute;
            display: flex;
            align-items: center;
            justify-content: center;
            --text-color: var(--white);

            @include bp($lg) {
                padding-left: 0;
            }

            &-inner {
                border-left: 0.5rem solid var(--secondary);
                padding-left: 2rem;

                @include bp($lg, true) {
                    width: calc(100% - 4rem);
                    padding-left: 0;
                    border-left: none;
                }

                .heading {
                    font-family: Montserrat;
                    font-size: clamp(20px, 3vw, 34px);
                    font-weight: 600;
                    line-height: 1;
                }

                p.content {
                    margin-top: 1rem;
                    margin-bottom: 0;
                    font-family: Montserrat;
                    font-size: clamp(16px, 2vw, 22px);
                    font-weight: 300;
                    max-width: rem-calc(750);
                    @include bp($lg) {
                        padding-right: 3rem;
                    }
                }
            }
        }
    }

    &__content-wrapper-inner-content {
        @include bp($sm, true) {
            height: 22vh;
        }

        @include bp($lg, true) {
            height: 25vh;
            overflow-y: scroll;
            /* custom scrollbar */
			&::-webkit-scrollbar {
				width: 10px;
				height: 90%;
			}

			&::-webkit-scrollbar-track {
				background-color: transparent;
				height: 90%;
			}

			&::-webkit-scrollbar-thumb {
				background-color: var(--secondary);
				border-radius: 10px;
				border: 3px solid transparent;
				background-clip: content-box;
			}

			&::-webkit-scrollbar-thumb:hover {
                background-color: #a8bbbf;
            }
        }
    }

    &__image {
        position: relative;

        @include bp($lg, true) {
            height: 45vh;
        }

        img {
            @include bp($lg) {
                border-top-right-radius: 50px;
                border-bottom-right-radius: 50px;
                border-bottom-left-radius: 50px;
            }

            @include bp($lg, true) {
                border-radius: 50px;
            }
        }
    }

    &__card {
        position: absolute;
        right: 1rem;
        bottom: 1rem;
        background-color: var(--white);
        z-index: 1;
        padding: 1rem;
        border-radius: 40px;
        text-decoration: none;
        max-width: rem-calc(375);
        transition: all 0.3s;

        @include bp($lg) {
            padding: 2rem;
        }

        @include bp($lg, true) {
            left: 0.5rem;
            right: 0.5rem;
            max-width: unset;
            bottom: 0.5rem;
        }

        .heading {
            font-family: Montserrat;
            font-size: rem-calc(22);
            font-weight: 600;
            line-height: 26.4px;
            color: var(--primary);
            max-width: calc(100% - 3rem);
            white-space: wrap;

            @include bp($lg, true) {
                margin-bottom: 0;
            }
        }

        &:hover {
            transform: translateY(-0.5rem);
            .block-timeline {
                &__permalink {
                    span {
                        border: 2px solid var(--white);
                        background-color: var(--primary);
                        background-image: url("data:image/svg+xml,%3Csvg width='18' height='27' viewBox='0 0 18 27' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.870117 19.6006L8.87012 26.0006L16.8701 19.6006' stroke='%23fff'/%3E%3Cpath d='M8.87011 26L8.87012 0' stroke='%23fff'/%3E%3C/svg%3E%0A");
                    }
                }
            }
        }
    }

    &__permalink {
        text-align: right;

        @include bp($lg, true) {
            position: absolute;
            bottom: 0.5rem;
            right: 1rem;
        }

        span {
            transition: all 0.3s;
            display: inline-block;
            color: transparent;
            overflow: hidden;
            overflow: clip;
            width: rem-calc(44);
            height: rem-calc(44);
            border: 2px solid var(--primary);
            border-radius: 50%;
            background-image: url("data:image/svg+xml,%3Csvg width='18' height='27' viewBox='0 0 18 27' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.870117 19.6006L8.87012 26.0006L16.8701 19.6006' stroke='%23383551'/%3E%3Cpath d='M8.87011 26L8.87012 0' stroke='%23383551'/%3E%3C/svg%3E%0A");
            background-size: 65% 65%;
            background-position: center;
            background-repeat: no-repeat;
            transform: rotate(-90deg);
        }
    }
}
class Timeline {
	block;

	constructor(block) {
		this.block = block;
		this.init();
	}

	init() {
		gsap.registerPlugin(ScrollTrigger);

		const images = this.block.querySelectorAll('.block-timeline__image-wrapper');
		const contentWrappers = this.block.querySelectorAll('.block-timeline__content-wrapper');
		const cards = this.block.querySelectorAll('.block-timeline__card');
		const navigationDots = this.block.querySelectorAll('.block-timeline__navigation-dot');
		const block = this.block;

		let tl = gsap.timeline({
			scrollTrigger: {
				trigger: block,
				pin: true,
				start: 'top top',
				end: '+=400%',
				scrub: 1,
				duration: 20,
			}
		})

		contentWrappers.forEach((contentWrapper, index) => {
			// console.log('index', index);
			tl.to({}, 1, {}).addLabel('step-'+index).to({}, 1, {});

			if(index < 1) {
				tl.add(() => {
					// remove the active class from all the navigation dots and add it to the current one
					navigationDots.forEach((dot) => {
						dot.classList.remove('active');
					});
					navigationDots[index].classList.add('active');
				})
			} else {
				// fade out previous content while also fading in new content
				tl.fromTo(contentWrappers[index], {
					opacity: 0,
					pointerEvents: 'none',
				}, {
					opacity: 1,
					duration: 1,
					pointerEvents: 'auto',
				}, '<').fromTo(images[index], {
					opacity: 0,
					pointerEvents: 'none',
				}, {
					opacity: 1,
					duration: 1,
					pointerEvents: 'auto',
				}, '<').fromTo(cards[index], {
					opacity: 0,
					pointerEvents: 'none',
				}, {
					opacity: 1,
					duration: 1,
					pointerEvents: 'auto',
				}, '<').fromTo(contentWrappers[index-1], {
					opacity: 1,
					pointerEvents: 'auto',
				}, {
					opacity: 0,
					duration: 1,
					pointerEvents: 'none',
				}, '<').fromTo(images[index-1], {
					opacity: 1,
					pointerEvents: 'auto',
				}, {
					opacity: 0,
					duration: 1,
					pointerEvents: 'none',
				}, '<').fromTo(cards[index-1], {
					opacity: 1,
					pointerEvents: 'auto',
				}, {
					opacity: 0,
					duration: 1,
					pointerEvents: 'none',
				}, '<').add(() => {
					// remove the active class from all the navigation dots and add it to the current one
					navigationDots.forEach((dot) => {
						dot.classList.remove('active');
					});
					navigationDots[index].classList.add('active');
				}).to({}, 1, {});
			}
		});

		navigationDots.forEach((dot, index) => {
			dot.addEventListener('click', (e) => {
				e.preventDefault();
				// console.log(tl.scrollTrigger.labelToScroll('step-'+index));

				window.scrollTo({
					top: tl.scrollTrigger.labelToScroll('step-'+index) + 200,
					behavior: 'smooth',
				});

			});
		});
	}
}

document.addEventListener('DOMContentLoaded', () => {
	document.querySelectorAll('.block-timeline').forEach((block) => {
		new Timeline(block);
	})
});
{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 2,
    "name": "strategiq/timeline",
    "title": "Timeline",
    "description": "Example block to be used as a template",
    "category": "strategiq",
    "icon": "strategiq",
    "acf": {
        "mode": "preview",
        "renderTemplate": "block-timeline.php"
    },
    "supports": {
        "anchor": true,
        "align": false,
        "color": {
            "background": true,
            "text": false,
            "gradients": true
        },
        "spacing": {
            "padding": [
                "top",
                "bottom"
            ],
            "margin": [
                "top",
                "bottom"
            ]
        }
    },
    "example": {
        "attributes": {
            "mode": "preview",
            "data": {
                "heading_type": "h2",
                "heading_text": "Example - Timeline",
                "content": "This is some example content to represent what the content will look like"
            }
        }
    },
    "style": "file:../../assets/css/timeline/block-timeline.css",
    "viewScript": ["gsap","scrolltrigger", "timeline"]
}
There are is no readme file with this component.