A module for showcasing B2B case studies, client portfolios, or success stories in a responsive, interactive slider format. Editors can easily add multiple case study cards, each with an image, tags, title, company name, and a link.
This module is designed to display multiple case studies or client achievements in a visually appealing and space-efficient slider format.
Purpose of Use
What it can achieve
Implementation Points & Notes
repeater
field for the "Case Studies" section. The group for the repeater should contain an image
field, text
field (title), text
field (company name), another repeater
field for the tags, and a link
field.color picker
(for primary_color
) and number
fields (for font sizes) to allow for easy visual customization without needing to edit code.object-fit: cover
, which handles minor size variations, but consistency is best for a clean design.[
{
"name": "main_title",
"label": "メインタイトル",
"type": "text",
"default": "導入インタビュー"
},
{
"name": "primary_color",
"label": "プライマリーカラー",
"type": "color",
"default": {
"color": "#007bff"
}
},
{
"name": "title_font_size",
"label": "タイトルフォントサイズ(px)",
"type": "number",
"default": 32
},
{
"name": "company_font_size",
"label": "会社名フォントサイズ(px)",
"type": "number",
"default": 14
},
{
"name": "description_font_size",
"label": "説明文フォントサイズ(px)",
"type": "number",
"default": 16
},
{
"name": "tag_font_size",
"label": "タグフォントサイズ(px)",
"type": "number",
"default": 12
},
{
"name": "case_studies",
"label": "事例",
"type": "group",
"expanded": true,
"occurrence": {
"min": 1,
"max": 20,
"default": 3
},
"children": [
{
"name": "image",
"label": "画像",
"type": "image",
"required": false,
"resizable": true
},
{
"name": "title",
"label": "タイトル",
"type": "text",
"default": "事例のタイトルを入力してください",
"required": true
},
{
"name": "company_name",
"label": "会社名",
"type": "text",
"default": "会社名を入力してください",
"required": true
},
{
"name": "link_url",
"label": "リンク先URL",
"type": "url",
"required": false
},
{
"name": "tags",
"label": "タグ",
"type": "group",
"occurrence": {
"min": 0,
"max": 10,
"default": 2
},
"children": [
{
"name": "tag_text",
"label": "タグテキスト",
"type": "text",
"required": true,
"help_text": "表示したいタグのテキストを入力してください"
}
]
}
]
}
]
{% set module_id = name|lower|replace(' ', '_') %}
<div class="case-study-slider" id="{{ module_id }}"
style="--primary-color: {{ module.primary_color.color }};
--title-font-size: {{ module.title_font_size }}px;
--company-font-size: {{ module.company_font_size }}px;
--description-font-size: {{ module.description_font_size }}px;
--tag-font-size: {{ module.tag_font_size }}px;">
{% if module.main_title %}
<h2 class="slider-title">{{ module.main_title }}</h2>
{% endif %}
<div class="slider-container">
<div class="slider-track" id="{{ module_id }}_track">
{% for case in module.case_studies %}
<div class="case-card">
{% if case.image and case.image.src %}
<div class="case-image">
<img src="{{ case.image.src }}" alt="{{ case.image.alt or case.title }}" loading="lazy">
</div>
{% else %}
{# デバッグ用:画像がない場合のプレースホルダー #}
<div class="case-image placeholder">
<div class="placeholder-content">No Image</div>
</div>
{% endif %}
<div class="case-content">
{# タグの表示 #}
{% if case.tags and case.tags|length > 0 %}
<div class="tags-container">
{% for tag in case.tags %}
{% if tag.tag_text %}
<span class="tag">{{ tag.tag_text }}</span>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% if case.title %}
<h3 class="case-title">{{ case.title }}</h3>
{% endif %}
{% if case.company_name %}
<p class="company-name">{{ case.company_name }}</p>
{% endif %}
{% if case.link_url and case.link_url.href %}
<a href="{{ case.link_url.href }}" class="case-link"
{% if case.link_url.open_in_new_tab %}target="_blank"{% endif %}>
<svg class="arrow-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
<div class="slider-controls">
<button class="prev-btn" data-target="{{ module_id }}_track" aria-label="前のスライド">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</svg>
</button>
<div class="pagination">
{% for case in module.case_studies %}
<button class="pagination-dot{% if loop.first %} active{% endif %}"
data-slide="{{ loop.index0 }}" data-target="{{ module_id }}_track"></button>
{% endfor %}
</div>
<button class="next-btn" data-target="{{ module_id }}_track" aria-label="次のスライド">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</button>
</div>
</div>
</div>
.case-study-slider {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.slider-title {
font-size: var(--title-font-size, 32px);
font-weight: bold;
margin-bottom: 40px;
text-align: left;
color: var(--primary-color, #333);
}
.slider-container {
position: relative;
overflow: hidden;
border-radius: 12px;
}
.slider-track {
display: flex;
transition: transform 0.3s ease;
gap: 20px;
}
.case-card {
flex: 0 0 calc(33.333% - 14px);
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.case-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.case-image.placeholder {
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
}
.placeholder-content {
color: #999;
font-size: 14px;
}
.case-image {
position: relative;
height: 200px;
overflow: hidden;
}
.case-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.case-card:hover .case-image img {
transform: scale(1.05);
}
.case-content {
padding: 24px;
position: relative;
}
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.tag {
background-color: var(--primary-color, #007bff);
color: white;
padding: 6px 12px;
border-radius: 20px;
font-size: var(--tag-font-size, 12px);
font-weight: 500;
white-space: nowrap;
}
.case-title {
font-size: var(--description-font-size, 16px);
font-weight: 600;
line-height: 1.5;
margin-bottom: 16px;
color: #333;
min-height: 48px;
}
.company-name {
font-size: var(--company-font-size, 14px);
color: #666;
margin-bottom: 20px;
font-weight: 500;
}
.case-link {
position: absolute;
bottom: 20px;
right: 20px;
width: 40px;
height: 40px;
background-color: var(--primary-color, #007bff);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
transition: all 0.3s ease;
}
.case-link:hover {
transform: scale(1.1);
background-color: color-mix(in srgb, var(--primary-color, #007bff) 80%, black);
}
.arrow-icon {
width: 20px;
height: 20px;
color: white;
}
.slider-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
margin-top: 30px;
}
.prev-btn, .next-btn {
width: 48px;
height: 48px;
border: 2px solid var(--primary-color, #007bff);
background: transparent;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
color: var(--primary-color, #007bff);
}
.prev-btn:hover, .next-btn:hover {
background-color: var(--primary-color, #007bff);
color: white;
transform: scale(1.05);
}
.prev-btn:disabled, .next-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.prev-btn:disabled:hover, .next-btn:disabled:hover {
background: transparent;
color: var(--primary-color, #007bff);
transform: none;
}
.prev-btn svg, .next-btn svg {
width: 20px;
height: 20px;
}
.pagination {
display: flex;
gap: 8px;
}
.pagination-dot {
width: 12px;
height: 12px;
border-radius: 50%;
border: none;
background-color: #ddd;
cursor: pointer;
transition: all 0.3s ease;
}
.pagination-dot.active {
background-color: var(--primary-color, #007bff);
}
.pagination-dot:hover {
background-color: var(--primary-color, #007bff);
opacity: 0.7;
}
/* レスポンシブデザイン */
@media (max-width: 768px) {
.case-card {
flex: 0 0 calc(50% - 10px);
}
.slider-title {
font-size: calc(var(--title-font-size, 32px) * 0.8);
}
}
@media (max-width: 480px) {
.case-card {
flex: 0 0 calc(100% - 0px);
}
.slider-track {
gap: 10px;
}
.case-content {
padding: 20px;
}
.slider-title {
font-size: calc(var(--title-font-size, 32px) * 0.7);
margin-bottom: 30px;
}
}
document.addEventListener('DOMContentLoaded', function() {
// すべてのスライダーを初期化
const sliders = document.querySelectorAll('.case-study-slider');
sliders.forEach(function(slider) {
initializeSlider(slider);
});
function initializeSlider(sliderElement) {
const track = sliderElement.querySelector('.slider-track');
const prevBtn = sliderElement.querySelector('.prev-btn');
const nextBtn = sliderElement.querySelector('.next-btn');
const paginationDots = sliderElement.querySelectorAll('.pagination-dot');
const cards = sliderElement.querySelectorAll('.case-card');
if (!track || cards.length === 0) return;
let currentIndex = 0;
let cardsPerView = getCardsPerView();
const totalCards = cards.length;
const maxIndex = Math.max(0, totalCards - cardsPerView);
// ウィンドウサイズに応じて表示カード数を決定
function getCardsPerView() {
const width = window.innerWidth;
if (width <= 480) return 1;
if (width <= 768) return 2;
return 3;
}
// スライダーの位置を更新
function updateSliderPosition() {
const cardWidth = cards[0].offsetWidth;
const gap = 20;
const translateX = -(currentIndex * (cardWidth + gap));
track.style.transform = `translateX(${translateX}px)`;
// ボタンの状態を更新
if (prevBtn) prevBtn.disabled = currentIndex === 0;
if (nextBtn) nextBtn.disabled = currentIndex >= maxIndex;
// ページネーションドットの状態を更新
paginationDots.forEach(function(dot, index) {
dot.classList.toggle('active', index === currentIndex);
});
}
// 前のスライドに移動
function goToPrevSlide() {
if (currentIndex > 0) {
currentIndex--;
updateSliderPosition();
}
}
// 次のスライドに移動
function goToNextSlide() {
if (currentIndex < maxIndex) {
currentIndex++;
updateSliderPosition();
}
}
// 特定のスライドに移動
function goToSlide(index) {
currentIndex = Math.min(Math.max(0, index), maxIndex);
updateSliderPosition();
}
// イベントリスナーを設定
if (prevBtn) {
prevBtn.addEventListener('click', goToPrevSlide);
}
if (nextBtn) {
nextBtn.addEventListener('click', goToNextSlide);
}
paginationDots.forEach(function(dot, index) {
dot.addEventListener('click', function() {
goToSlide(index);
});
});
// キーボードナビゲーション
sliderElement.addEventListener('keydown', function(e) {
if (e.key === 'ArrowLeft') {
goToPrevSlide();
e.preventDefault();
} else if (e.key === 'ArrowRight') {
goToNextSlide();
e.preventDefault();
}
});
// タッチ/スワイプサポート
let startX = 0;
let isDragging = false;
track.addEventListener('touchstart', function(e) {
startX = e.touches[0].clientX;
isDragging = true;
});
track.addEventListener('touchmove', function(e) {
if (!isDragging) return;
e.preventDefault();
});
track.addEventListener('touchend', function(e) {
if (!isDragging) return;
const endX = e.changedTouches[0].clientX;
const diff = startX - endX;
if (Math.abs(diff) > 50) {
if (diff > 0) {
goToNextSlide();
} else {
goToPrevSlide();
}
}
isDragging = false;
});
// マウスドラッグサポート
track.addEventListener('mousedown', function(e) {
startX = e.clientX;
isDragging = true;
track.style.cursor = 'grabbing';
e.preventDefault();
});
document.addEventListener('mousemove', function(e) {
if (!isDragging) return;
e.preventDefault();
});
document.addEventListener('mouseup', function(e) {
if (!isDragging) return;
const endX = e.clientX;
const diff = startX - endX;
if (Math.abs(diff) > 50) {
if (diff > 0) {
goToNextSlide();
} else {
goToPrevSlide();
}
}
isDragging = false;
track.style.cursor = 'grab';
});
// リサイズ時の再計算
window.addEventListener('resize', function() {
const newCardsPerView = getCardsPerView();
if (newCardsPerView !== cardsPerView) {
cardsPerView = newCardsPerView;
currentIndex = Math.min(currentIndex, Math.max(0, totalCards - cardsPerView));
updateSliderPosition();
}
});
// 初期化
updateSliderPosition();
track.style.cursor = 'grab';
}
});
We can customize this sample to match your specific business requirements.
Book Free Consultation