To achieve Summary Block single carousel for Portfolio Page, like this.

#1. First, find Portfolio Page URL

#2. Next, add a Summary Block Carousel

#3. Enter Portfolio Page URL in Header text

#4. Use this code to Page Header Injection
<script>
const PortfolioCardConverter = {
initialize() {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
this.convertSummaryBlocks();
}, 1000);
});
},
convertSummaryBlocks() {
const summaryBlocks = document.querySelectorAll('.summary-block-wrapper');
summaryBlocks.forEach((block) => {
const headerText = block.querySelector('.summary-header-text');
if (headerText && headerText.textContent.includes('/projects')) {
this.convertToCardCarousel(block, headerText.textContent.trim());
}
});
},
async convertToCardCarousel(summaryBlock, headerText) {
const headerElement = summaryBlock.querySelector('.summary-heading');
const pagerElement = summaryBlock.querySelector('.summary-carousel-pager');
const listContainer = summaryBlock.querySelector('.summary-item-list-container');
if (headerElement) headerElement.style.display = 'none';
if (pagerElement) pagerElement.style.display = 'none';
if (!listContainer) return;
listContainer.innerHTML = '<div style="text-align:center;padding:40px;color:#666;">Loading portfolio...</div>';
try {
const portfolioData = await this.fetchPortfolioData(headerText);
if (portfolioData.length > 0) {
listContainer.innerHTML = this.generateCardCarousel(portfolioData);
this.initializeCardCarousel(listContainer);
} else {
listContainer.innerHTML = '<div style="text-align:center;padding:40px;color:#666;">No portfolio items found</div>';
}
} catch (error) {
console.log('Error loading portfolio:', error);
listContainer.innerHTML = '<div style="text-align:center;padding:40px;color:#666;">Error loading portfolio</div>';
}
},
async fetchPortfolioData(headerText) {
const projectsUrl = headerText.replace(/^\//, '');
try {
const response = await fetch('/' + projectsUrl);
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return this.extractPortfolioData(doc);
} catch (error) {
console.log('Fetch error:', error);
return [];
}
},
extractPortfolioData(doc) {
const portfolioData = [];
const gridItems = doc.querySelectorAll('.grid-item');
const limitedItems = Array.from(gridItems).slice(0, 7);
limitedItems.forEach((item) => {
const titleElement = item.querySelector('.portfolio-title');
const imgElement = item.querySelector('.grid-image img');
const href = item.getAttribute('href');
if (titleElement && imgElement && href) {
const title = titleElement.textContent.trim();
const image = imgElement.getAttribute('data-src') || imgElement.getAttribute('src') || imgElement.getAttribute('data-image');
portfolioData.push({
title: title,
url: href,
image: image
});
}
});
return portfolioData;
},
generateCardCarousel(portfolioData) {
const items = portfolioData.map(item => `
<div class="tp-item">
<div class="tp-img" style="background-image:url('${item.image}')"></div>
<a class="tp-desc" href="${item.url}">${item.title}</a>
<p class="tp-foot" style="display:none;">${item.title}</p>
</div>
`).join('');
return `
<section class="tp-carousel">
<div class="tp-card">
<div class="tp-viewport">
<div class="tp-track">
${items}
</div>
</div>
<div class="tp-footer">
<p class="tp-foot-text"></p>
<div class="tp-arrows">
<button class="tp-arrow tp-prev">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button class="tp-arrow tp-next">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M9 18L15 12L9 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
</div>
</div>
</section>
`;
},
initializeCardCarousel(container) {
setTimeout(() => {
const track = container.querySelector('.tp-track');
const slides = container.querySelectorAll('.tp-item').length;
const foot = container.querySelector('.tp-foot-text');
const prevBtn = container.querySelector('.tp-prev');
const nextBtn = container.querySelector('.tp-next');
if (!track || !foot || !prevBtn || !nextBtn || slides === 0) return;
let i = 0;
function update() {
const current = container.querySelectorAll('.tp-item')[i];
if (current) {
foot.textContent = '';
track.style.transform = 'translateX(' + (-i * 100) + '%)';
}
}
function go(n) {
i = (n + slides) % slides;
update();
}
nextBtn.addEventListener('click', () => go(i + 1));
prevBtn.addEventListener('click', () => go(i - 1));
update();
}, 100);
}
};
PortfolioCardConverter.initialize();
</script>
<style>.summary-header-text,.summary-item-list{opacity:0}.tp-carousel{display:flex;justify-content:center;align-items:center;margin:40px auto}.tp-card{width:280px;height:500px;max-width:92vw;background:#fff;padding:28px;position:relative;display:flex;flex-direction:column;justify-content:space-between}.tp-viewport{overflow:hidden;flex:1}.tp-track{display:flex;transition:transform .4s ease;height:100%}.tp-item{flex:0 0 100%;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;row-gap:18px;height:100%}.tp-item .tp-foot{display:none}.tp-img{width:100%;height:280px;background-size:cover;background-position:center;background-repeat:no-repeat}.tp-desc{margin:0;text-align:center;font-size:16px;line-height:1.4;color:#111;font-weight:600;text-decoration:none;transition:opacity 0.3s ease;flex:1;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.tp-desc:hover{opacity:.7}.tp-footer{display:flex;align-items:center;justify-content:center;margin-top:18px}.tp-arrows{display:flex;gap:8px}.tp-arrow{width:36px;height:36px;border-radius:50%;border:1px solid rgb(0 0 0 / .15);background:#f8f8f8;cursor:pointer;display:grid;place-items:center;font-size:16px;line-height:1;transition:all 0.2s ease}.tp-arrow:hover{background:#e8e8e8;border-color:rgb(0 0 0 / .25)}.tp-arrow:active{transform:scale(.95)}@media (min-width:768px){.tp-card{width:320px;height:520px;padding:32px}.tp-img{height:340px}.tp-desc{font-size:18px}.tp-foot-text{font-size:18px}.tp-arrows{gap:10px}.tp-arrow{width:42px;height:42px;font-size:18px}}@media (max-width:767px){.tp-card{height:440px;padding:24px}.tp-img{height:240px}.tp-desc{font-size:14px}.tp-foot-text{font-size:14px}}
.summary-header-text, .summary-item-list {opacity: 0;}
</style>
