Update 22082025-1637
This commit is contained in:
parent
a1219489d6
commit
463e7fa1ea
1 changed files with 476 additions and 98 deletions
|
|
@ -120,134 +120,513 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
<!-- =========================
|
<!-- =========================
|
||||||
FEATURES (Icon-Left, keine Cards)
|
FEATURES CAROUSEL (Große Cards mit Auto-Play)
|
||||||
Quelle: .Params.features (array of {title, text, icon?})
|
Quelle: .Params.features (array of {title, text, icon?})
|
||||||
========================= -->
|
========================= -->
|
||||||
{{ with .Params.features }}
|
{{ with .Params.features }}
|
||||||
<section class="section service-features" aria-label="Leistungen">
|
<section class="section service-features" aria-label="Leistungen">
|
||||||
<style>
|
<style>
|
||||||
/* ===== Scroll-Snap Carousel – local styles ===== */
|
/* ===== Features Carousel Styles ===== */
|
||||||
.sf-wrap{--gap:16px;--card-w:86vw}
|
.service-features {
|
||||||
@media (min-width:640px){.sf-wrap{--card-w:68vw}}
|
background-color: #f3f3f3;
|
||||||
@media (min-width:992px){.sf-wrap{--card-w:420px}}
|
padding: 60px 0;
|
||||||
.sf-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 12px}
|
|
||||||
.sf-title-h{margin:0}
|
|
||||||
.sf-controls{display:flex;gap:8px}
|
|
||||||
.sf-btn{
|
|
||||||
appearance:none;border:1px solid rgba(0,0,0,.12);background:#fff;
|
|
||||||
width:40px;height:40px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;
|
|
||||||
font-weight:700;cursor:pointer;box-shadow:0 4px 16px rgba(0,0,0,.06);transition:box-shadow .2s,transform .04s
|
|
||||||
}
|
}
|
||||||
.sf-btn:hover{box-shadow:0 10px 24px rgba(245,166,35,.25);background:#FDF7EC}
|
|
||||||
.sf-btn:active{transform:translateY(1px)}
|
.sf-carousel {
|
||||||
.sf-viewport{
|
position: relative;
|
||||||
overflow:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
|
max-width: 1200px;
|
||||||
border-radius:14px;scrollbar-width:thin
|
margin: 0 auto;
|
||||||
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
.sf-viewport:focus-visible{outline:2px dashed #F5A623;outline-offset:4px}
|
|
||||||
.sf-track{display:grid;grid-auto-flow:column;grid-auto-columns:var(--card-w);gap:var(--gap);padding:2px 2px 10px}
|
.sf-title {
|
||||||
.sf-card{
|
text-align: center;
|
||||||
scroll-snap-align:start;background:#fff;border:1px solid rgba(0,0,0,.08);border-radius:14px;
|
margin: 0 0 40px;
|
||||||
box-shadow:0 6px 22px rgba(0,0,0,.06);padding:16px;display:flex;gap:14px;align-items:flex-start
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
}
|
}
|
||||||
.sf-ico{
|
|
||||||
flex:0 0 44px;width:44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;
|
/* Viewport & Track */
|
||||||
color:#F5A623;background:rgba(245,166,35,.12)
|
.sf-viewport {
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-track {
|
||||||
|
display: flex;
|
||||||
|
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards - Always one card visible */
|
||||||
|
.sf-card {
|
||||||
|
flex: 0 0 100%;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-card-inner {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0,0,0,0.08);
|
||||||
|
padding: 40px 30px;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 320px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
transition: transform 0.3s, box-shadow 0.3s;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.sf-carousel.is-dragging .sf-card-inner {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon */
|
||||||
|
.sf-icon {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: linear-gradient(135deg, #F5A623 0%, #FFC947 100%);
|
||||||
|
border-radius: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
box-shadow: 0 8px 24px rgba(245, 166, 35, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-icon svg {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content */
|
||||||
|
.sf-card h3 {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-card p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation Dots */
|
||||||
|
.sf-dots {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 32px;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0,0,0,0.2);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-dot:hover {
|
||||||
|
background: rgba(245, 166, 35, 0.5);
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-dot.active {
|
||||||
|
background: #F5A623;
|
||||||
|
width: 32px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Arrow Navigation */
|
||||||
|
.sf-arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 2;
|
||||||
|
transition: all 0.3s;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-arrow:hover {
|
||||||
|
background: #F5A623;
|
||||||
|
color: #fff;
|
||||||
|
transform: translateY(-50%) scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-arrow.prev {
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-arrow.next {
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide arrows on mobile */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.sf-arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Touch feedback */
|
||||||
|
@media (hover: none) {
|
||||||
|
.sf-card-inner:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress bar for autoplay */
|
||||||
|
.sf-progress {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -4px;
|
||||||
|
left: 0;
|
||||||
|
height: 3px;
|
||||||
|
background: #F5A623;
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 0;
|
||||||
|
transition: width 8s linear;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sf-carousel.autoplay .sf-progress {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pause on hover */
|
||||||
|
.sf-carousel:hover .sf-progress {
|
||||||
|
animation-play-state: paused;
|
||||||
}
|
}
|
||||||
.sf-ico svg{width:22px;height:22px;display:block}
|
|
||||||
.sf-body{display:flex;flex-direction:column;gap:4px}
|
|
||||||
.sf-card h3{margin:0;font-weight:700;line-height:1.3}
|
|
||||||
.sf-card p{margin:0;opacity:.9}
|
|
||||||
/* dezente Scrollbar */
|
|
||||||
.sf-viewport::-webkit-scrollbar{height:8px}
|
|
||||||
.sf-viewport::-webkit-scrollbar-track{background:transparent}
|
|
||||||
.sf-viewport::-webkit-scrollbar-thumb{background:rgba(0,0,0,.12);border-radius:8px}
|
|
||||||
/* Drag cursor */
|
|
||||||
.sf-viewport.is-drag{cursor:grabbing;cursor:-webkit-grabbing}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="container-wide">
|
{{/* Store the features list and count */}}
|
||||||
<div class="sf-wrap">
|
{{ $features := . }}
|
||||||
<div class="sf-head">
|
{{ $totalFeatures := len $features }}
|
||||||
<h2 class="h3 sf-title-h">Leistungen</h2>
|
|
||||||
<div class="sf-controls" aria-label="Carousel-Steuerung">
|
|
||||||
<button class="sf-btn" data-dir="-1" type="button" aria-label="Zurück">‹</button>
|
|
||||||
<button class="sf-btn" data-dir="1" type="button" aria-label="Weiter">›</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{/* Daten vorbereiten */}}
|
<div class="sf-carousel" id="sfCarousel">
|
||||||
{{ $list := . }}
|
<h2 class="sf-title">Unsere Leistungen</h2>
|
||||||
{{ $total := len $list }}
|
|
||||||
|
|
||||||
<div class="sf-viewport" id="sf-viewport" tabindex="0" role="region" aria-roledescription="carousel" aria-label="Leistungen Carousel">
|
<button class="sf-arrow prev" aria-label="Vorherige Leistung">
|
||||||
<div class="sf-track" id="sf-track">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||||
{{ range $i, $f := $list }}
|
<path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<article class="sf-card" role="group" aria-roledescription="slide" aria-label='{{ printf "%d von %d" (add $i 1) $total }}'>
|
</svg>
|
||||||
<div class="sf-ico" aria-hidden="true">
|
</button>
|
||||||
|
|
||||||
|
<button class="sf-arrow next" aria-label="Nächste Leistung">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="sf-viewport" role="region" aria-roledescription="carousel" aria-label="Leistungen Carousel">
|
||||||
|
<div class="sf-track" id="sfTrack">
|
||||||
|
{{ range $i, $f := $features }}
|
||||||
|
<div class="sf-card" role="group" aria-roledescription="slide" aria-label='{{ printf "Leistung %d von %d" (add $i 1) $totalFeatures }}'>
|
||||||
|
<div class="sf-card-inner">
|
||||||
|
<div class="sf-icon" aria-hidden="true">
|
||||||
{{ $icon := $f.icon | default (printf "i-%d" (mod $i 5)) }}
|
{{ $icon := $f.icon | default (printf "i-%d" (mod $i 5)) }}
|
||||||
{{ if or (eq $icon "pv") (eq $icon "i-0") }}
|
{{ if or (eq $icon "pv") (eq $icon "i-0") }}
|
||||||
<!-- PV -->
|
<!-- PV/Solar -->
|
||||||
<svg viewBox="0 0 24 24"><path d="M3 9h18v6H3z" fill="currentColor" opacity=".18"/><path d="M3 9h18v6H3M7 9v6M11 9v6M15 9v6M19 9v6" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M3.55 18.54L4.96 19.95L6.76 18.16L5.34 16.74M11 22.45C11.32 22.45 13 22.45 13 22.45V19.5H11M12 5.5C8.14 5.5 5 8.64 5 12.5C5 16.36 8.14 19.5 12 19.5C15.86 19.5 19 16.36 19 12.5C19 8.64 15.86 5.5 12 5.5M20 12.5H23V10.5H20M17.24 18.16L19.04 19.95L20.45 18.54L18.66 16.74M20.45 6.46L19.04 5.05L17.24 6.84L18.66 8.26M13 0.55H11V3.5H13M4 10.5H1V12.5H4M6.76 6.84L4.96 5.05L3.55 6.46L5.34 8.26"/>
|
||||||
|
</svg>
|
||||||
{{ else if or (eq $icon "speicher") (eq $icon "i-1") }}
|
{{ else if or (eq $icon "speicher") (eq $icon "i-1") }}
|
||||||
<!-- Battery -->
|
<!-- Battery/Speicher -->
|
||||||
<svg viewBox="0 0 24 24"><rect x="3" y="7" width="16" height="10" rx="2" fill="none" stroke="currentColor" stroke-width="1.5"/><rect x="19" y="10" width="2" height="4" fill="currentColor"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M16 10V8H15V6C15 4.89 14.11 4 13 4H5C3.89 4 3 4.89 3 6V18C3 19.11 3.89 20 5 20H13C14.11 20 15 19.11 15 18V16H16V14H19V10H16M5 6H13V18H5V6M16.5 12.5H18V11.5H16.5V12.5Z"/>
|
||||||
|
</svg>
|
||||||
{{ else if or (eq $icon "lade") (eq $icon "i-2") }}
|
{{ else if or (eq $icon "lade") (eq $icon "i-2") }}
|
||||||
<!-- EV -->
|
<!-- EV Charging -->
|
||||||
<svg viewBox="0 0 24 24"><path d="M9 7v4a4 4 0 0 0 4 4h2v4" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M14 3v4M10 3v4M17 7h2a2 2 0 0 1 2 2v2h-4" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M18 7V4C18 2.9 17.1 2 16 2H5C3.9 2 3 2.9 3 4V20C3 21.1 3.9 22 5 22H16C17.1 22 18 21.1 18 20V18H20C21.1 18 22 17.1 22 16V12C22 10.9 21.1 10 20 10H18V7M5 4H16V20H5V4M18 12H20V16H18V12M11 5H7L11 13V9H15L11 17V13Z"/>
|
||||||
|
</svg>
|
||||||
{{ else if or (eq $icon "study") (eq $icon "i-3") }}
|
{{ else if or (eq $icon "study") (eq $icon "i-3") }}
|
||||||
<!-- Study -->
|
<!-- Analytics/Study -->
|
||||||
<svg viewBox="0 0 24 24"><path d="M4 18V6m4 12V9m4 9v-6m4 6v-8m4 8V8" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M22 12C22 6.48 17.52 2 12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12M6.5 13L10 9.5L13 12.5L16.5 9L18 10.5L13 15.5L10 12.5L6.5 16L5 14.5L6.5 13Z"/>
|
||||||
|
</svg>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<!-- Doc/Check -->
|
<!-- Check/Service -->
|
||||||
<svg viewBox="0 0 24 24"><path d="M7 3h7l5 5v11a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z" fill="currentColor" opacity=".14"/><path d="M14 3v6h6M9 14l2 2 4-4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2M10 17L5 12L6.41 10.59L10 14.17L17.59 6.58L19 8L10 17Z"/>
|
||||||
|
</svg>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<div class="sf-body">
|
<h3>{{ $f.title }}</h3>
|
||||||
<h3 class="h5">{{ $f.title }}</h3>
|
{{ with $f.text }}<p>{{ . | $.Page.RenderString (dict "display" "inline") }}</p>{{ end }}
|
||||||
{{ with $f.text }}<p>{{ . | $.Page.RenderString (dict "display" "inline") }}</p>{{ end }}
|
</div>
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
</div>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sf-progress"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sf-dots" role="tablist" aria-label="Carousel Navigation">
|
||||||
|
{{ range $i, $f := $features }}
|
||||||
|
<button class="sf-dot{{ if eq $i 0 }} active{{ end }}"
|
||||||
|
role="tab"
|
||||||
|
aria-selected="{{ if eq $i 0 }}true{{ else }}false{{ end }}"
|
||||||
|
aria-label="Gehe zu Leistung {{ add $i 1 }}"
|
||||||
|
data-slide="{{ $i }}">
|
||||||
|
</button>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function(){
|
(function() {
|
||||||
var wrap = document.querySelector('.service-features .sf-wrap');
|
const carousel = document.getElementById('sfCarousel');
|
||||||
if(!wrap) return;
|
if (!carousel) return;
|
||||||
var viewport = wrap.querySelector('.sf-viewport');
|
|
||||||
var track = wrap.querySelector('.sf-track');
|
|
||||||
var btns = wrap.querySelectorAll('.sf-btn');
|
|
||||||
|
|
||||||
function cardStep(){
|
const track = document.getElementById('sfTrack');
|
||||||
var gap = parseFloat(getComputedStyle(track).gap) || 16;
|
const cards = track.querySelectorAll('.sf-card');
|
||||||
var card = track.querySelector('.sf-card');
|
const dots = carousel.querySelectorAll('.sf-dot');
|
||||||
return card ? card.getBoundingClientRect().width + gap : 320;
|
const prevBtn = carousel.querySelector('.sf-arrow.prev');
|
||||||
|
const nextBtn = carousel.querySelector('.sf-arrow.next');
|
||||||
|
const progress = carousel.querySelector('.sf-progress');
|
||||||
|
|
||||||
|
let currentIndex = 0;
|
||||||
|
let autoplayInterval;
|
||||||
|
let touchStartX = 0;
|
||||||
|
let touchEndX = 0;
|
||||||
|
let isDragging = false;
|
||||||
|
const AUTOPLAY_DELAY = 8000; // 8 seconds
|
||||||
|
|
||||||
|
// Clone first and last cards for infinite loop
|
||||||
|
function setupInfiniteLoop() {
|
||||||
|
const firstClone = cards[0].cloneNode(true);
|
||||||
|
const lastClone = cards[cards.length - 1].cloneNode(true);
|
||||||
|
firstClone.setAttribute('aria-hidden', 'true');
|
||||||
|
lastClone.setAttribute('aria-hidden', 'true');
|
||||||
|
track.appendChild(firstClone);
|
||||||
|
track.insertBefore(lastClone, cards[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
btns.forEach(function(b){
|
// Update carousel position
|
||||||
b.addEventListener('click', function(){
|
function updateCarousel(animate = true) {
|
||||||
var dir = this.getAttribute('data-dir') === '1' ? 1 : -1;
|
const translateX = -(currentIndex + 1) * 100; // +1 because of the cloned card at the beginning
|
||||||
viewport.scrollBy({left: cardStep() * dir, behavior: 'smooth'});
|
|
||||||
|
if (animate) {
|
||||||
|
track.style.transition = 'transform 0.5s cubic-bezier(0.4, 0, 0.2, 1)';
|
||||||
|
} else {
|
||||||
|
track.style.transition = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
track.style.transform = `translateX(${translateX}%)`;
|
||||||
|
|
||||||
|
// Update dots
|
||||||
|
const realIndex = currentIndex % cards.length;
|
||||||
|
dots.forEach((dot, index) => {
|
||||||
|
dot.classList.toggle('active', index === realIndex);
|
||||||
|
dot.setAttribute('aria-selected', index === realIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove disabled state from buttons for infinite loop
|
||||||
|
prevBtn.disabled = false;
|
||||||
|
nextBtn.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle infinite loop transitions
|
||||||
|
function handleInfiniteTransition() {
|
||||||
|
track.addEventListener('transitionend', () => {
|
||||||
|
if (currentIndex === cards.length) {
|
||||||
|
currentIndex = 0;
|
||||||
|
updateCarousel(false);
|
||||||
|
} else if (currentIndex === -1) {
|
||||||
|
currentIndex = cards.length - 1;
|
||||||
|
updateCarousel(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go to specific slide
|
||||||
|
function goToSlide(index) {
|
||||||
|
currentIndex = index;
|
||||||
|
updateCarousel();
|
||||||
|
resetAutoplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next slide
|
||||||
|
function nextSlide() {
|
||||||
|
currentIndex++;
|
||||||
|
if (currentIndex > cards.length) {
|
||||||
|
updateCarousel();
|
||||||
|
setTimeout(() => {
|
||||||
|
currentIndex = 0;
|
||||||
|
updateCarousel(false);
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
updateCarousel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Previous slide
|
||||||
|
function prevSlide() {
|
||||||
|
currentIndex--;
|
||||||
|
if (currentIndex < -1) {
|
||||||
|
updateCarousel();
|
||||||
|
setTimeout(() => {
|
||||||
|
currentIndex = cards.length - 1;
|
||||||
|
updateCarousel(false);
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
updateCarousel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Autoplay functionality
|
||||||
|
function startAutoplay() {
|
||||||
|
carousel.classList.add('autoplay');
|
||||||
|
progress.style.transition = 'none';
|
||||||
|
progress.style.width = '0';
|
||||||
|
setTimeout(() => {
|
||||||
|
progress.style.transition = `width ${AUTOPLAY_DELAY}ms linear`;
|
||||||
|
progress.style.width = '100%';
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
autoplayInterval = setInterval(nextSlide, AUTOPLAY_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAutoplay() {
|
||||||
|
carousel.classList.remove('autoplay');
|
||||||
|
clearInterval(autoplayInterval);
|
||||||
|
progress.style.transition = 'none';
|
||||||
|
progress.style.width = '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetAutoplay() {
|
||||||
|
stopAutoplay();
|
||||||
|
startAutoplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touch/Drag handling
|
||||||
|
function handleTouchStart(e) {
|
||||||
|
touchStartX = e.type.includes('mouse') ? e.clientX : e.touches[0].clientX;
|
||||||
|
isDragging = true;
|
||||||
|
carousel.classList.add('is-dragging');
|
||||||
|
|
||||||
|
if (e.type.includes('mouse')) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
stopAutoplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchMove(e) {
|
||||||
|
if (!isDragging) return;
|
||||||
|
|
||||||
|
if (e.type.includes('mouse')) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchEnd(e) {
|
||||||
|
if (!isDragging) return;
|
||||||
|
|
||||||
|
isDragging = false;
|
||||||
|
carousel.classList.remove('is-dragging');
|
||||||
|
|
||||||
|
touchEndX = e.type.includes('mouse') ? e.clientX : (e.changedTouches[0]?.clientX || touchStartX);
|
||||||
|
handleSwipe();
|
||||||
|
|
||||||
|
resetAutoplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSwipe() {
|
||||||
|
const swipeThreshold = 50;
|
||||||
|
const diff = touchStartX - touchEndX;
|
||||||
|
|
||||||
|
if (Math.abs(diff) > swipeThreshold) {
|
||||||
|
if (diff > 0) {
|
||||||
|
nextSlide();
|
||||||
|
} else {
|
||||||
|
prevSlide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
prevBtn.addEventListener('click', () => {
|
||||||
|
prevSlide();
|
||||||
|
resetAutoplay();
|
||||||
|
});
|
||||||
|
|
||||||
|
nextBtn.addEventListener('click', () => {
|
||||||
|
nextSlide();
|
||||||
|
resetAutoplay();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dot navigation
|
||||||
|
dots.forEach((dot, index) => {
|
||||||
|
dot.addEventListener('click', () => {
|
||||||
|
goToSlide(index);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Keyboard navigation
|
// Touch events
|
||||||
viewport.addEventListener('keydown', function(e){
|
track.addEventListener('touchstart', handleTouchStart, { passive: true });
|
||||||
if(e.key === 'ArrowRight'){ viewport.scrollBy({left: cardStep(), behavior:'smooth'}); }
|
track.addEventListener('touchmove', handleTouchMove, { passive: true });
|
||||||
if(e.key === 'ArrowLeft'){ viewport.scrollBy({left:-cardStep(), behavior:'smooth'}); }
|
track.addEventListener('touchend', handleTouchEnd, { passive: true });
|
||||||
|
|
||||||
|
// Mouse events for desktop drag
|
||||||
|
track.addEventListener('mousedown', handleTouchStart);
|
||||||
|
track.addEventListener('mousemove', handleTouchMove);
|
||||||
|
track.addEventListener('mouseup', handleTouchEnd);
|
||||||
|
track.addEventListener('mouseleave', () => {
|
||||||
|
if (isDragging) {
|
||||||
|
handleTouchEnd({ type: 'mouse', clientX: touchStartX });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Drag to scroll (desktop)
|
// Keyboard navigation
|
||||||
var down=false, startX=0, sl=0;
|
carousel.addEventListener('keydown', (e) => {
|
||||||
viewport.addEventListener('mousedown', function(e){ down=true; viewport.classList.add('is-drag'); startX=e.pageX; sl=viewport.scrollLeft; });
|
if (e.key === 'ArrowLeft') {
|
||||||
window.addEventListener('mouseup', function(){ down=false; viewport.classList.remove('is-drag'); });
|
prevSlide();
|
||||||
viewport.addEventListener('mousemove', function(e){ if(!down) return; e.preventDefault(); viewport.scrollLeft = sl - (e.pageX - startX); });
|
resetAutoplay();
|
||||||
|
}
|
||||||
|
if (e.key === 'ArrowRight') {
|
||||||
|
nextSlide();
|
||||||
|
resetAutoplay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pause on hover (optional - can be removed if you want continuous autoplay)
|
||||||
|
carousel.addEventListener('mouseenter', stopAutoplay);
|
||||||
|
carousel.addEventListener('mouseleave', startAutoplay);
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
setupInfiniteLoop();
|
||||||
|
handleInfiniteTransition();
|
||||||
|
updateCarousel(false);
|
||||||
|
startAutoplay();
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -255,7 +634,6 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- =========================
|
<!-- =========================
|
||||||
OUTCOMES (Nutzen/Ergebnisse)
|
OUTCOMES (Nutzen/Ergebnisse)
|
||||||
Quelle: .Params.outcomes (array of strings)
|
Quelle: .Params.outcomes (array of strings)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue