@import "../../resources/scss/util/variables";
@import "../../resources/scss/util/mixins";
@import "../../resources/scss/vendor/bootstrap/vendor/rfs";
.block-industry-insights {
@include padding-top(rem-calc(60));
@include padding-bottom(rem-calc(60));
position: relative;
&.loading {
pointer-events: none;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
z-index: 100;
pointer-events: all;
}
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50px;
height: 50px;
border: 4px solid rgba(0, 0, 0, 0.1);
border-top-color: var(--primary, #000);
border-radius: 50%;
animation: spin 0.8s linear infinite;
z-index: 101;
}
}
&__filters-title {
text-align: center;
margin-bottom: rem-calc(16);
font-size: rem-calc(16);
font-weight: 500;
@include bp($md) {
font-size: rem-calc(18);
margin-bottom: rem-calc(20);
}
}
&__filters {
display: flex;
flex-wrap: wrap;
gap: rem-calc(6);
margin-bottom: rem-calc(40);
justify-content: center;
@include bp($md) {
margin-bottom: rem-calc(60);
gap: rem-calc(8);
}
}
&__filter {
display: inline-block;
padding: rem-calc(10 20);
border-radius: rem-calc(30);
border: 1px solid rgba(0, 0, 0, 0.2);
background-color: $white;
color: rgba(0, 0, 0, 0.7);
text-decoration: none;
font-size: rem-calc(14);
font-weight: 500;
transition: all 0.3s ease;
cursor: pointer;
font-family: inherit;
appearance: none;
-webkit-appearance: none;
@include bp($md) {
padding: rem-calc(6 12);
font-size: rem-calc(12);
}
&:hover {
background-color: rgba(0, 0, 0, 0.05);
border-color: rgba(0, 0, 0, 0.3);
}
&:focus {
outline: 2px solid var(--primary, #000);
outline-offset: 2px;
}
&--active {
background-color: var(--primary, #000);
color: $white;
border-color: var(--primary, #000);
&:hover {
background-color: rgba(0, 0, 0, 0.8);
}
}
}
&__timeline {
position: relative;
padding: rem-calc(40) 0;
@include bp($md) {
padding: rem-calc(60) 0;
}
}
&__timeline-line {
position: absolute;
left: rem-calc(20);
top: 0;
bottom: 0;
width: 4px;
background-color: rgba(0, 0, 0, 0.1);
z-index: 1;
@include bp($md) {
left: 50%;
transform: translateX(-50%);
}
&::before {
content: '';
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 12px;
height: 12px;
background-color: var(--primary, #000);
border-radius: 50%;
z-index: 2;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 12px;
height: 12px;
background-color: var(--primary, #000);
border-radius: 50%;
z-index: 2;
}
}
&__timeline-items {
position: relative;
z-index: 2;
}
&__timeline-item {
position: relative;
margin-bottom: rem-calc(40);
padding-left: rem-calc(50);
@include bp($md) {
margin-bottom: rem-calc(60);
padding-left: 0;
width: calc(50% - 30px);
}
&:last-child {
margin-bottom: 0;
}
&--left {
@include bp($md) {
margin-right: auto;
}
}
&--right {
@include bp($md) {
margin-left: auto;
}
}
}
&__timeline-marker {
position: absolute;
left: rem-calc(12);
top: rem-calc(20);
width: 16px;
height: 16px;
background-color: var(--primary, #000);
border: 4px solid $white;
border-radius: 50%;
z-index: 3;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
@include bp($md) {
left: auto;
right: calc(-30px - 8px);
}
.block-industry-insights__timeline-item--right & {
@include bp($md) {
right: auto;
left: calc(-30px - 8px);
}
}
// Category-specific marker styles
// Add custom colors for each category as needed
&--google-seo, &--google {
background-color: #34a853;
}
&--affiliates {
background-color: #9badac;
}
&--pr {
background-color: #e0e5e9;
}
&--digital {
background-color: #95e1d3;
}
&--ppc {
background-color: #83c3dc;
}
&--social-media {
background-color: #e12309;
}
&--analytics {
background-color: #f2a600;
}
&--meta {
background-color: #0081fb;
}
&--facebook {
background-color: #1877f2;
}
&--tiktok {
background-color: #ff0050;
}
&--bing {
background-color: #008373;
}
&--microsoft {
background-color: #00a4ef;
}
&--tools {
background-color: #6c757d;
}
&--instagram {
background-color: #c13584;
}
&--artificial-intelligence {
background-color: #6366f1;
}
&--linkedin {
background-color: #0077b5;
}
&--pinterest {
background-color: #bd081c;
}
}
&__timeline-content {
background-color: $white;
border-radius: rem-calc(12);
padding: rem-calc(24);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
transition: box-shadow 0.3s ease, transform 0.3s ease;
&:hover {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}
@include bp($md) {
padding: rem-calc(32);
}
}
&__timeline-image {
margin-bottom: rem-calc(20);
border-radius: rem-calc(8);
overflow: hidden;
img {
width: 100%;
height: auto;
display: block;
}
}
&__timeline-text {
position: relative;
iframe {
max-width: 100%;
}
}
&__timeline-date {
font-size: rem-calc(14);
color: rgba(0, 0, 0, 0.6);
margin-bottom: rem-calc(12);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
&__timeline-categories {
display: flex;
flex-wrap: wrap;
gap: rem-calc(6);
margin-bottom: rem-calc(12);
}
&__timeline-category {
display: inline-block;
padding: rem-calc(4 12);
border-radius: rem-calc(20);
font-size: rem-calc(12);
font-weight: 500;
color: $white;
background-color: var(--primary, #000);
text-transform: uppercase;
letter-spacing: 0.5px;
// Category-specific background colors matching marker styles
&--google-seo, &--google {
background-color: #34a853;
}
&--affiliates {
background-color: #9badac;
}
&--pr {
background-color: #e0e5e9;
}
&--digital {
background-color: #95e1d3;
}
&--ppc {
background-color: #83c3dc;
}
&--social-media {
background-color: #e12309;
}
&--analytics {
background-color: #f2a600;
}
&--meta {
background-color: #0081fb;
}
&--facebook {
background-color: #1877f2;
}
&--tiktok {
background-color: #ff0050;
}
&--bing {
background-color: #008373;
}
&--microsoft {
background-color: #00a4ef;
}
&--tools {
background-color: #6c757d;
}
&--instagram {
background-color: #c13584;
}
&--artificial-intelligence {
background-color: #6366f1;
}
&--linkedin {
background-color: #0077b5;
}
&--pinterest {
background-color: #bd081c;
}
}
&__timeline-title {
margin-bottom: rem-calc(12);
font-size: rem-calc(20);
line-height: 1.3;
@include bp($md) {
font-size: rem-calc(24);
}
a {
color: inherit;
text-decoration: none;
transition: color 0.3s ease;
}
}
&__timeline-excerpt {
font-size: rem-calc(16);
line-height: 1.6;
color: rgba(0, 0, 0, 0.7);
margin-bottom: rem-calc(16);
}
&__timeline-link {
display: inline-block;
font-size: rem-calc(14);
font-weight: 600;
color: var(--primary, #000);
text-decoration: none;
text-transform: uppercase;
letter-spacing: 0.5px;
transition: color 0.3s ease;
font-size: rem-calc(12)!important;
white-space: break-spaces;
@media (max-width: 768px) {
text-align: center;
}
&:hover {
color: #fff
}
}
&__no-results {
text-align: center;
padding: rem-calc(60) 0;
p {
font-size: rem-calc(18);
color: rgba(0, 0, 0, 0.6);
}
}
&__load-more-wrapper {
text-align: center;
margin-top: rem-calc(40);
padding-top: rem-calc(40);
width: 100%;
display: flex;
justify-content: center;
align-items: center;
clear: both;
position: relative;
z-index: 9;
@include bp($md) {
margin-top: rem-calc(60);
padding-top: rem-calc(60);
}
}
&__load-more {
min-width: rem-calc(200);
margin: 0;
display: inline-block;
background: #FFF;
&:hover {
background: var(--primary, #000);
color: #FFF;
}
}
.background-gradient {
position: fixed;
height: 100vh;
}
&__back-to-top {
position: fixed;
bottom: rem-calc(30);
right: rem-calc(30);
width: rem-calc(50);
height: rem-calc(50);
border-radius: 50%;
background-color: var(--primary, #000);
color: #fff;
border: 2px solid var(--primary, #000);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
visibility: hidden;
transform: translateY(rem-calc(20));
transition: all 0.3s ease;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
svg {
width: rem-calc(20);
height: rem-calc(20);
}
&:hover {
background-color: #fff;
color: var(--primary, #000);
transform: translateY(0);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
}
&--visible {
opacity: 1 !important;
visibility: visible !important;
transform: translateY(0) !important;
}
@include bp($md) {
bottom: rem-calc(40);
right: rem-calc(40);
width: rem-calc(60);
height: rem-calc(60);
svg {
width: rem-calc(24);
height: rem-calc(24);
}
}
}
}
@keyframes spin {
from {
transform: translate(-50%, -50%) rotate(0deg);
}
to {
transform: translate(-50%, -50%) rotate(360deg);
}
}
class IndustryInsightsBlock {
/**
* Create and initialise objects of this class
* @param {HTMLElement} block
*/
constructor(block) {
this.block = block;
this.filters = this.block.querySelectorAll('.block-industry-insights__filter');
this.timelineContainer = this.block.querySelector('.block-industry-insights__timeline-items');
this.loadMoreBtn = this.block.querySelector('.block-industry-insights__load-more');
this.taxonomy = this.block.dataset.taxonomy || 'industry_updates_categories';
this.postType = this.block.dataset.postType || 'industry_updates';
this.selectedCategory = '';
this.currentPage = 1;
this.postsPerPage = 20;
this.startIndex = 0;
this.cache = {}; // Cache for AJAX responses
this.init();
}
init() {
if (!this.block) return;
// Get initial values from data attributes
if (this.timelineContainer) {
this.postsPerPage = parseInt(this.timelineContainer.dataset.postsPerPage) || 20;
this.currentPage = parseInt(this.timelineContainer.dataset.currentPage) || 1;
// Calculate initial start index based on current items
const currentItems = this.timelineContainer.querySelectorAll('.block-industry-insights__timeline-item');
this.startIndex = currentItems.length;
}
// Detect category from URL on page load (for SEO-friendly URLs)
this.detectCategoryFromURL();
this.setupFilters();
this.setupLoadMore();
this.setupPopstateListener();
this.setupBackToTop();
}
detectCategoryFromURL() {
// Check if we're on a category archive page
const path = window.location.pathname;
const categoryMatch = path.match(/\/industry-updates\/([^\/]+)\/?$/);
let categorySlug = '';
if (categoryMatch) {
categorySlug = categoryMatch[1];
}
// Find the matching filter button
const matchingFilter = Array.from(this.filters).find(filter => {
return filter.dataset.category === categorySlug || (categorySlug === '' && filter.dataset.category === 'all');
});
if (matchingFilter) {
this.selectedCategory = categorySlug === '' ? '' : categorySlug;
// Ensure the correct filter is marked as active
this.filters.forEach((f) => f.classList.remove('block-industry-insights__filter--active'));
matchingFilter.classList.add('block-industry-insights__filter--active');
return true;
}
return false;
}
setupPopstateListener() {
// Listen for browser back/forward navigation
window.addEventListener('popstate', (e) => {
// Detect category from the restored URL
const hadCategory = this.detectCategoryFromURL();
// Reset pagination
this.currentPage = 1;
this.startIndex = 0;
// Fetch results to restore the filtered state
this.fetchResults(true);
});
}
buildFilterUrl(category) {
// Get base URL - try to find it from a link filter or construct it
let baseUrl = window.location.origin + '/industry-updates/';
// Try to find base URL from an "all" filter link
const allFilter = Array.from(this.filters).find(f => f.dataset.category === 'all');
if (allFilter && allFilter.tagName === 'A' && allFilter.href) {
const url = new URL(allFilter.href);
baseUrl = url.origin + url.pathname;
} else {
// Fallback: construct from current URL
const pathMatch = window.location.pathname.match(/^(\/industry-updates\/)/);
if (pathMatch) {
baseUrl = window.location.origin + pathMatch[1];
}
}
// Build URL based on category
if (category === 'all' || category === '') {
return baseUrl;
} else {
return baseUrl + category + '/';
}
}
setupFilters() {
if (!this.filters || this.filters.length === 0) return;
this.filters.forEach((filter) => {
filter.addEventListener('click', (e) => {
const isLink = filter.tagName === 'A';
const category = filter.dataset.category || '';
// For links, allow navigation for SEO but enhance with AJAX if possible
if (isLink && filter.href && !filter.href.match(/^javascript:|^#/)) {
// Check if we can use AJAX (same domain)
const url = new URL(filter.href, window.location.origin);
const isSameDomain = url.origin === window.location.origin;
if (isSameDomain) {
e.preventDefault();
// Update URL without page reload
window.history.pushState({ category: category }, '', filter.href);
// Remove active class from all filters
this.filters.forEach((f) => f.classList.remove('block-industry-insights__filter--active'));
// Add active class to clicked filter
filter.classList.add('block-industry-insights__filter--active');
this.selectedCategory = category === 'all' ? '' : category;
// Reset pagination
this.currentPage = 1;
this.startIndex = 0;
// Fetch filtered results (reset = true replaces content)
this.fetchResults(true);
}
// If different domain, let the link navigate normally
} else {
// Button behavior (AJAX only)
e.preventDefault();
// Build URL for this filter
const filterUrl = this.buildFilterUrl(category);
// Update URL without page reload
window.history.pushState({ category: category }, '', filterUrl);
// Remove active class from all filters
this.filters.forEach((f) => f.classList.remove('block-industry-insights__filter--active'));
// Add active class to clicked filter
filter.classList.add('block-industry-insights__filter--active');
this.selectedCategory = category === 'all' ? '' : category;
// Reset pagination
this.currentPage = 1;
this.startIndex = 0;
// Fetch filtered results (reset = true replaces content)
this.fetchResults(true);
}
});
});
}
setupLoadMore() {
if (!this.loadMoreBtn) return;
this.loadMoreBtn.addEventListener('click', (e) => {
e.preventDefault();
this.currentPage++;
this.fetchResults(false);
});
}
async fetchResults(reset = false) {
// Create cache key based on category and page
const cacheKey = `${this.selectedCategory || 'all'}_${this.currentPage}`;
// Check cache first (only for filtering, not for load more)
if (reset && this.cache[cacheKey]) {
// Use cached data instantly
this.updateResults(this.cache[cacheKey], reset);
return;
}
this.block.classList.add('loading');
try {
// Get AJAX URL and nonce from localized script or use defaults
const ajaxUrl = (typeof industryInsights !== 'undefined' && industryInsights.ajaxurl)
? industryInsights.ajaxurl
: '/wp-admin/admin-ajax.php';
const nonce = (typeof industryInsights !== 'undefined' && industryInsights.nonce)
? industryInsights.nonce
: '';
const formData = new FormData();
formData.append('action', 'filter_industry_insights');
formData.append('nonce', nonce);
formData.append('post_type', this.postType);
formData.append('taxonomy', this.taxonomy);
formData.append('paged', this.currentPage);
formData.append('posts_per_page', this.postsPerPage);
formData.append('start_index', this.startIndex);
// Add category filter if selected
if (this.selectedCategory) {
formData.append('category', this.selectedCategory);
}
const response = await fetch(ajaxUrl, {
method: 'POST',
credentials: 'same-origin',
body: formData,
});
const data = await response.json();
if (data.success) {
// Cache the response for future use (only cache first page when filtering)
if (reset && this.currentPage === 1) {
this.cache[cacheKey] = data.data;
}
this.updateResults(data.data, reset);
} else {
this.showError(data.data.message || 'Error loading insights');
}
} catch (error) {
console.error('Industry Insights AJAX Error:', error);
this.showError('Error loading insights. Please try again.');
} finally {
this.block.classList.remove('loading');
}
}
updateResults(data, reset = false) {
if (!this.timelineContainer) return;
if (reset) {
// Replace all content when filtering
this.timelineContainer.innerHTML = data.html || '';
this.startIndex = 0;
// Scroll to top of timeline smoothly
const timeline = this.block.querySelector('.block-industry-insights__timeline');
if (timeline) {
timeline.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
} else {
// Store current scroll position relative to document
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
// Append new content when loading more
this.timelineContainer.insertAdjacentHTML('beforeend', data.html || '');
// Restore scroll position after DOM updates to prevent jumping
// Use requestAnimationFrame to ensure DOM has updated
requestAnimationFrame(() => {
window.scrollTo(0, scrollPosition);
});
}
// Update start index for next load
this.startIndex += data.html ? (data.html.match(/block-industry-insights__timeline-item/g) || []).length : 0;
// Update load more button visibility
this.updateLoadMoreButton(data.has_more);
}
updateLoadMoreButton(hasMore) {
if (!this.loadMoreBtn) return;
const loadMoreWrapper = this.block.querySelector('.block-industry-insights__load-more-wrapper');
if (hasMore) {
if (loadMoreWrapper) {
loadMoreWrapper.style.display = 'block';
}
this.loadMoreBtn.style.display = 'inline-block';
} else {
if (loadMoreWrapper) {
loadMoreWrapper.style.display = 'none';
}
this.loadMoreBtn.style.display = 'none';
}
}
showError(message) {
if (!this.timelineContainer) return;
this.timelineContainer.innerHTML = `
${message}
`;
}
setupBackToTop() {
const backToTopBtn = this.block.querySelector('.block-industry-insights__back-to-top');
if (!backToTopBtn) {
console.warn('Back to top button not found in block:', this.block);
return;
}
// Scroll threshold - show button after scrolling 300px from top of page
const scrollThreshold = 300;
// Function to toggle button visibility
const toggleBackToTop = () => {
const scrollY = window.pageYOffset || document.documentElement.scrollTop;
// Check if we've scrolled past the threshold
if (scrollY > scrollThreshold) {
backToTopBtn.classList.add('block-industry-insights__back-to-top--visible');
} else {
backToTopBtn.classList.remove('block-industry-insights__back-to-top--visible');
}
};
// Listen for scroll events
let ticking = false;
const handleScroll = () => {
if (!ticking) {
window.requestAnimationFrame(() => {
toggleBackToTop();
ticking = false;
});
ticking = true;
}
};
window.addEventListener('scroll', handleScroll, { passive: true });
// Initial check
toggleBackToTop();
// Handle click - scroll to top of block
backToTopBtn.addEventListener('click', (e) => {
e.preventDefault();
const blockTop = this.block.getBoundingClientRect().top;
window.scrollTo({
top: blockTop,
behavior: 'smooth'
});
});
}
}
// Initialize all Industry Insights blocks on page load
document.addEventListener('DOMContentLoaded', () => {
const blocks = document.querySelectorAll('.block-industry-insights');
blocks.forEach((block) => {
new IndustryInsightsBlock(block);
});
});
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "strategiq/industry-insights",
"title": "Industry insights",
"description": "Industry insights block",
"category": "strategiq",
"icon": "strategiq",
"acf": {
"mode": "preview",
"renderTemplate": "block-industry-insights.php"
},
"supports": {
"anchor": true,
"align": false,
"color": {
"background": true,
"text": false,
"gradients": false
},
"spacing": {
"padding": [
"top",
"bottom"
],
"margin": [
"top",
"bottom"
]
}
},
"style": "file:../../assets/css/industry-insights/block-industry-insights.css",
"viewScript": "file:./block-industry-insights.js"
}