forked from markus/AMPERION_Webpage
Update29082025
This commit is contained in:
parent
463e7fa1ea
commit
2f9aa2adac
15 changed files with 817 additions and 513 deletions
|
|
@ -6,7 +6,6 @@
|
|||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4 sticky-top">
|
||||
<h2>{{ .Title }}</h2>
|
||||
{{ with .Params.subtitle }}{{ . | $.Page.RenderString (dict "display" "block") }}{{ end }}
|
||||
<p>{{ i18n "last_update" }}: {{ time.Format ":date_long" .Lastmod }}</p>
|
||||
<h3>{{ i18n "faq_toc_title" }}</h3>
|
||||
|
|
|
|||
|
|
@ -75,51 +75,63 @@
|
|||
{{ end }}
|
||||
</div>
|
||||
|
||||
<!-- Intro-Text rechts + USP darunter -->
|
||||
<!-- Intro-Text rechts -->
|
||||
<div class="col-lg-6 order-2 order-lg-2" data-reveal>
|
||||
{{ if .Content }}
|
||||
<div class="service-hero__text">
|
||||
{{ .Content }}
|
||||
{{ with .Params.usp }}
|
||||
<div class="usp-inline" role="list">
|
||||
{{ range $i, $u := . }}
|
||||
<div class="usp-item" role="listitem">
|
||||
<div class="usp-icon" aria-hidden="true">
|
||||
{{ if eq (mod $i 3) 0 }}
|
||||
<!-- Shield -->
|
||||
<svg viewBox="0 0 24 24" role="img" focusable="false">
|
||||
<path d="M12 3l7 3v6c0 4.97-3.58 9.43-7 10-3.42-.57-7-5.03-7-10V6l7-3z" fill="currentColor" opacity=".2"/>
|
||||
<path d="M12 3l7 3v6c0 4.97-3.58 9.43-7 10-3.42-.57-7-5.03-7-10V6l7-3z" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||
</svg>
|
||||
{{ else if eq (mod $i 3) 1 }}
|
||||
<!-- Badge -->
|
||||
<svg viewBox="0 0 24 24" role="img" focusable="false">
|
||||
<circle cx="12" cy="9" r="5" fill="currentColor" opacity=".2"/>
|
||||
<circle cx="12" cy="9" r="5" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M8 14l-2 7 6-3 6 3-2-7" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{{ else }}
|
||||
<!-- Target -->
|
||||
<svg viewBox="0 0 24 24" role="img" focusable="false">
|
||||
<circle cx="12" cy="12" r="9" fill="currentColor" opacity=".15"/>
|
||||
<circle cx="12" cy="12" r="6" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="12" r="2" fill="currentColor"/>
|
||||
</svg>
|
||||
{{ end }}
|
||||
</div>
|
||||
<p class="usp-text">{{ $u }}</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Eigenständiger Container für USPs unterhalb der Text-/Bild-Reihe -->
|
||||
{{ with .Params.usp }}
|
||||
<div class="usp-container" style="
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
justify-items: center;
|
||||
gap: 2rem;
|
||||
margin-top: 2rem;
|
||||
text-align: center;
|
||||
">
|
||||
{{ range $i, $u := . }}
|
||||
<div class="usp-item" style="display:flex; flex-direction:column; align-items:center;">
|
||||
<div class="usp-icon" aria-hidden="true">
|
||||
{{ if eq (mod $i 3) 0 }}
|
||||
<!-- Shield -->
|
||||
<svg viewBox="0 0 24 24" role="img" focusable="false">
|
||||
<path d="M12 3l7 3v6c0 4.97-3.58 9.43-7 10-3.42-.57-7-5.03-7-10V6l7-3z" fill="currentColor" opacity=".2"/>
|
||||
<path d="M12 3l7 3v6c0 4.97-3.58 9.43-7 10-3.42-.57-7-5.03-7-10V6l7-3z" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||
</svg>
|
||||
{{ else if eq (mod $i 3) 1 }}
|
||||
<!-- Badge -->
|
||||
<svg viewBox="0 0 24 24" role="img" focusable="false">
|
||||
<circle cx="12" cy="9" r="5" fill="currentColor" opacity=".2"/>
|
||||
<circle cx="12" cy="9" r="5" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M8 14l-2 7 6-3 6 3-2-7" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{{ else }}
|
||||
<!-- Target -->
|
||||
<svg viewBox="0 0 24 24" role="img" focusable="false">
|
||||
<circle cx="12" cy="12" r="9" fill="currentColor" opacity=".15"/>
|
||||
<circle cx="12" cy="12" r="6" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="12" r="2" fill="currentColor"/>
|
||||
</svg>
|
||||
{{ end }}
|
||||
</div>
|
||||
<p class="usp-text" style="text-align:center;">{{ $u | markdownify }}</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<!-- =========================
|
||||
FEATURES CAROUSEL (Große Cards mit Auto-Play)
|
||||
Quelle: .Params.features (array of {title, text, icon?})
|
||||
|
|
@ -143,9 +155,6 @@
|
|||
.sf-title {
|
||||
text-align: center;
|
||||
margin: 0 0 40px;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Viewport & Track */
|
||||
|
|
@ -171,7 +180,6 @@
|
|||
.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;
|
||||
|
|
@ -210,16 +218,11 @@
|
|||
/* 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;
|
||||
}
|
||||
|
||||
|
|
@ -401,234 +404,155 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const carousel = document.getElementById('sfCarousel');
|
||||
if (!carousel) return;
|
||||
|
||||
const track = document.getElementById('sfTrack');
|
||||
const cards = track.querySelectorAll('.sf-card');
|
||||
const dots = carousel.querySelectorAll('.sf-dot');
|
||||
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]);
|
||||
}
|
||||
|
||||
// Update carousel position
|
||||
function updateCarousel(animate = true) {
|
||||
const translateX = -(currentIndex + 1) * 100; // +1 because of the cloned card at the beginning
|
||||
|
||||
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
|
||||
<script>
|
||||
(function () {
|
||||
const carousel = document.getElementById('sfCarousel');
|
||||
if (!carousel) return;
|
||||
|
||||
const track = document.getElementById('sfTrack');
|
||||
const cards = track.querySelectorAll('.sf-card');
|
||||
const dots = carousel.querySelectorAll('.sf-dot');
|
||||
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;
|
||||
let isAnimating = false; // blockiert neue Aktionen
|
||||
const AUTOPLAY_DELAY = 20000;
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
function updateCarousel(animate = true) {
|
||||
const translateX = -(currentIndex + 1) * 100;
|
||||
track.style.transition = animate
|
||||
? 'transform 0.5s cubic-bezier(0.4, 0, 0.2, 1)'
|
||||
: 'none';
|
||||
track.style.transform = `translateX(${translateX}%)`;
|
||||
|
||||
const realIndex = (currentIndex + cards.length) % cards.length;
|
||||
dots.forEach((dot, index) => {
|
||||
dot.addEventListener('click', () => {
|
||||
goToSlide(index);
|
||||
});
|
||||
dot.classList.toggle('active', index === realIndex);
|
||||
dot.setAttribute('aria-selected', index === realIndex);
|
||||
});
|
||||
|
||||
// Touch events
|
||||
track.addEventListener('touchstart', handleTouchStart, { passive: true });
|
||||
track.addEventListener('touchmove', handleTouchMove, { passive: true });
|
||||
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 });
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard navigation
|
||||
carousel.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowLeft') {
|
||||
prevSlide();
|
||||
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();
|
||||
|
||||
// Buttons während Animation blockieren
|
||||
prevBtn.disabled = animate;
|
||||
nextBtn.disabled = animate;
|
||||
}
|
||||
|
||||
track.addEventListener('transitionend', () => {
|
||||
if (currentIndex === cards.length) currentIndex = 0;
|
||||
if (currentIndex === -1) currentIndex = cards.length - 1;
|
||||
updateCarousel(false);
|
||||
isAnimating = false;
|
||||
prevBtn.disabled = false;
|
||||
nextBtn.disabled = false;
|
||||
});
|
||||
|
||||
function goToSlide(index) {
|
||||
if (isAnimating) return;
|
||||
isAnimating = true;
|
||||
currentIndex = index;
|
||||
updateCarousel();
|
||||
resetAutoplay();
|
||||
}
|
||||
|
||||
function nextSlide() {
|
||||
if (isAnimating) return;
|
||||
isAnimating = true;
|
||||
currentIndex++;
|
||||
updateCarousel();
|
||||
}
|
||||
|
||||
function prevSlide() {
|
||||
if (isAnimating) return;
|
||||
isAnimating = true;
|
||||
currentIndex--;
|
||||
updateCarousel();
|
||||
}
|
||||
|
||||
function startAutoplay() {
|
||||
carousel.classList.add('autoplay');
|
||||
progress.style.transition = 'none';
|
||||
progress.style.width = '0%';
|
||||
requestAnimationFrame(() => {
|
||||
progress.style.transition = `width ${AUTOPLAY_DELAY}ms linear`;
|
||||
progress.style.width = '100%';
|
||||
});
|
||||
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();
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
|
||||
// Touch/Swipe
|
||||
function handleTouchStart(e) {
|
||||
if (isAnimating) return;
|
||||
touchStartX = e.type.includes('mouse') ? e.clientX : e.touches[0].clientX;
|
||||
isDragging = true;
|
||||
carousel.classList.add('is-dragging');
|
||||
stopAutoplay();
|
||||
}
|
||||
|
||||
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 diff = touchStartX - touchEndX;
|
||||
if (Math.abs(diff) > 50) diff > 0 ? nextSlide() : prevSlide();
|
||||
}
|
||||
|
||||
// Events
|
||||
prevBtn.addEventListener('click', () => prevSlide());
|
||||
nextBtn.addEventListener('click', () => nextSlide());
|
||||
dots.forEach((dot, i) => dot.addEventListener('click', () => goToSlide(i)));
|
||||
|
||||
track.addEventListener('touchstart', handleTouchStart, { passive: true });
|
||||
track.addEventListener('touchend', handleTouchEnd, { passive: true });
|
||||
track.addEventListener('mousedown', handleTouchStart);
|
||||
track.addEventListener('mouseup', handleTouchEnd);
|
||||
|
||||
carousel.addEventListener('mouseenter', stopAutoplay);
|
||||
carousel.addEventListener('mouseleave', startAutoplay);
|
||||
carousel.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowLeft') prevSlide();
|
||||
if (e.key === 'ArrowRight') nextSlide();
|
||||
});
|
||||
|
||||
setupInfiniteLoop();
|
||||
updateCarousel(false);
|
||||
startAutoplay();
|
||||
})();
|
||||
</script>
|
||||
|
||||
</section>
|
||||
{{ end }}
|
||||
|
||||
|
|
@ -654,14 +578,14 @@
|
|||
|
||||
<!-- Text links -->
|
||||
<div class="col-lg-6" data-reveal>
|
||||
<h3 class="mb-3">Ergebnis & Mehrwert</h3>
|
||||
<h2 class="mb-3">Ergebnis & Mehrwert</h2>
|
||||
<ul class="checklist">
|
||||
{{ range . }}
|
||||
<li data-reveal>
|
||||
<svg class="check" viewBox="0 0 24 24" width="22" height="22" aria-hidden="true">
|
||||
<path d="M20 6L9 17l-5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span>{{ . }}</span>
|
||||
<span>{{ . | markdownify }}</span>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue