Update300820252359

This commit is contained in:
astosic 2025-08-31 00:22:16 +02:00
parent af6021b9cf
commit 4055e3ae54
17 changed files with 293 additions and 227 deletions

View file

@ -23,39 +23,38 @@
<div class="col-lg-5 order-1 order-lg-2" data-aos="fade-in" data-aos-delay="200">
<div class="intro-media ratio ratio-16x9 shadow-sm overflow-hidden">
<video id="aboutVideo"
class="intro-video"
muted
loop
playsinline
preload="none"
poster='{{ "images/brand_poster.jpg" | relURL }}'>
<!-- Quellen werden per JS gesetzt -->
</video>
class="intro-video"
autoplay
muted
loop
playsinline
preload="none"
poster='{{ "images/brand_poster.jpg" | relURL }}'>
<!-- Quellen werden per JS gesetzt -->
</video>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const vid = document.getElementById("aboutVideo");
if (!vid) return;
const srcWebm = '{{ "images/Imagevideo_Short.webm" | relURL }}';
const srcMp4 = '{{ "images/Imagevideo_Short.mp4" | relURL }}';
const srcWebm = '{{ "images/Imagevideo_Short.webm" | relURL }}';
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
vid.innerHTML = `
<source src="${srcWebm}" type="video/webm">
<source src="${srcMp4}" type="video/mp4">
<source src="${srcWebm}" type="video/webm">
`;
vid.play().catch(() => {});
observer.disconnect();
}
});
}, { rootMargin: "200px" });
observer.observe(vid);
});
</script>
@ -142,7 +141,7 @@ document.addEventListener("DOMContentLoaded", () => {
{{ $step := 100 }}
<article class="service-card text-center" data-aos="zoom-in" data-aos-delay="{{ $delay = add $delay $step }}">
<img src="/icons/stamp.svg" alt="Staatlich geprüft" class="service-icon mb-3" style="filter: invert(74%) sepia(39%) saturate(2198%) hue-rotate(344deg) brightness(101%) contrast(92%);">
<img src="/icons/stamp.svg" alt="Staatlich geprüft" class="service-icon mb-3">
<h4 class="service-card-title">Staatlich geprüft & konessioniert</h4>
<p class="service-card-description">
<strong>Ingenieurbüro und Elektrotechnikbetrieb </strong> Planung und Prüfung mit Qualität &amp; Sicherheit.
@ -150,7 +149,7 @@ document.addEventListener("DOMContentLoaded", () => {
</article>
<article class="service-card text-center" data-aos="zoom-in" data-aos-delay="{{ $delay = add $delay $step }}">
<img src="/icons/brain.svg" alt="Erfahrung & Innovation" class="service-icon mb-3" style="filter: invert(57%) sepia(94%) saturate(602%) hue-rotate(359deg) brightness(101%) contrast(103%);">
<img src="/icons/brain.svg" alt="Erfahrung & Innovation" class="service-icon mb-3">
<h4 class="service-card-title">Erfahrung &amp; Innovation</h4>
<p class="service-card-description">
<strong>Über 10 Jahre Praxis </strong> kombiniert mit modernen Methoden und Tools.
@ -158,15 +157,16 @@ document.addEventListener("DOMContentLoaded", () => {
</article>
<article class="service-card text-center" data-aos="zoom-in" data-aos-delay="{{ $delay = add $delay $step }}">
<img src="/icons/earth.svg" alt="Ganzheitlicher Ansatz" class="service-icon mb-3" style="filter: invert(57%) sepia(94%) saturate(602%) hue-rotate(359deg) brightness(101%) contrast(103%);">
<img src="/icons/earth.svg" alt="Ganzheitlicher Ansatz" class="service-icon mb-3">
<h4 class="service-card-title">Ganzheitlicher Ansatz</h4>
<p class="service-card-description">
<strong>Technik, Wirtschaft & Umwelt </strong> von Beginn an zukunftssicher gedacht.
</p>
</article>
<article class="service-card text-center" data-aos="zoom-in" data-aos-delay="{{ $delay = add $delay $step }}">
<img src="/icons/briefcase-business.svg" alt="Unabhängige Beratung" class="service-icon mb-3" style="filter: invert(57%) sepia(94%) saturate(602%) hue-rotate(359deg) brightness(101%) contrast(103%);">
<img src="/icons/briefcase-business.svg" alt="Unabhängige Beratung" class="service-icon mb-3">
<h4 class="service-card-title">Unabhängige Beratung</h4>
<p class="service-card-description">
<strong>Herstellerneutral </strong> wir empfehlen, was zu Ihrem Projekt wirklich passt.

View file

@ -167,9 +167,8 @@
<!-- =========================
FEATURES CAROUSEL (Große Cards mit Auto-Play)
FEATURES CAROUSEL (Mobile-optimiert)
Quelle: .Params.features (array of {title, text, icon?})
========================= -->
{{ with .Params.features }}
@ -198,19 +197,27 @@
overflow: hidden;
border-radius: 20px;
position: relative;
/* Verhindert, dass andere Cards sichtbar sind */
margin: 0 auto;
max-width: 100%;
}
.sf-track {
display: flex;
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
transition: transform 0.3s ease-out;
will-change: transform;
/* Touch-optimiert */
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
/* Cards - Always one card visible */
/* Cards - Immer nur eine Card sichtbar */
.sf-card {
flex: 0 0 100%;
padding: 10px;
box-sizing: border-box;
min-width: 100%;
}
.sf-card-inner {
@ -223,13 +230,9 @@
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;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
/* Touch-Feedback entfernt für bessere Performance */
-webkit-tap-highlight-color: transparent;
}
/* Icon */
@ -255,21 +258,25 @@
.sf-card h3 {
margin: 0 0 16px;
line-height: 1.3;
/* font-size entfernt - verwendet Standard */
}
.sf-card p {
margin: 0;
line-height: 1.6;
/* font-size entfernt - verwendet Standard */
}
/* Navigation Dots */
.sf-dots {
display: flex;
justify-content: center;
gap: 8px;
gap: 12px;
margin-top: 32px;
padding: 0;
list-style: none;
/* Touch-Target vergrößert */
padding: 10px 0;
}
.sf-dot {
@ -281,6 +288,17 @@
cursor: pointer;
transition: all 0.3s;
padding: 0;
/* Touch-Target vergrößert */
position: relative;
}
.sf-dot::before {
content: '';
position: absolute;
top: -10px;
left: -10px;
right: -10px;
bottom: -10px;
}
.sf-dot:hover {
@ -294,7 +312,7 @@
border-radius: 6px;
}
/* Arrow Navigation */
/* Arrow Navigation - nur Desktop */
.sf-arrow {
position: absolute;
top: 50%;
@ -306,7 +324,7 @@
border-radius: 50%;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
cursor: pointer;
display: flex;
display: none; /* Standardmäßig ausgeblendet */
align-items: center;
justify-content: center;
z-index: 2;
@ -321,6 +339,11 @@
transform: translateY(-50%) scale(1.1);
}
.sf-arrow:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.sf-arrow.prev {
left: 20px;
}
@ -329,40 +352,76 @@
right: 20px;
}
/* Hide arrows on mobile */
@media (max-width: 767px) {
/* Arrows nur auf Desktop anzeigen */
@media (min-width: 768px) {
.sf-arrow {
display: none;
display: flex;
}
}
/* Touch feedback */
@media (hover: none) {
.sf-card-inner:active {
transform: scale(0.98);
/* Mobile-Optimierungen */
@media (max-width: 767px) {
.sf-carousel {
padding: 0 15px;
}
.sf-card {
padding: 5px;
}
.sf-card-inner {
padding: 30px 20px;
min-height: 280px;
}
/* Schriftgrößen beibehalten - nicht verkleinern auf Mobile */
.sf-icon {
width: 70px;
height: 70px;
}
.sf-icon svg {
width: 35px;
height: 35px;
}
/* Swipe-Indikator */
.sf-viewport::after {
content: '';
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 4px;
background: rgba(0,0,0,0.1);
border-radius: 2px;
pointer-events: none;
}
}
/* Progress bar for autoplay */
/* Progress bar für Autoplay */
.sf-progress {
position: absolute;
bottom: -4px;
bottom: 0;
left: 0;
height: 3px;
background: #F5A623;
border-radius: 3px;
width: 0;
transition: width 8s linear;
z-index: 3;
opacity: 0.7;
}
.sf-carousel.autoplay .sf-progress {
width: 100%;
/* Dragging State */
.sf-carousel.is-dragging .sf-track {
transition: none;
cursor: grabbing;
}
/* Pause on hover */
.sf-carousel:hover .sf-progress {
animation-play-state: paused;
.sf-carousel.is-dragging .sf-card-inner {
pointer-events: none;
}
</style>
@ -440,155 +499,158 @@
</div>
</div>
<script>
(function () {
const carousel = document.getElementById('sfCarousel');
if (!carousel) return;
(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');
const track = document.getElementById('sfTrack');
const cards = Array.from(track.querySelectorAll('.sf-card'));
const dots = carousel.querySelectorAll('.sf-dot');
const prevBtn = carousel.querySelector('.sf-arrow.prev');
const nextBtn = carousel.querySelector('.sf-arrow.next');
let currentIndex = 0;
let autoplayInterval;
let touchStartX = 0;
let touchEndX = 0;
let isDragging = false;
let isAnimating = false; // blockiert neue Aktionen
const AUTOPLAY_DELAY = 20000;
const AUTOPLAY_DELAY = 5000; // 5Sekunden
const SWIPE_THRESHOLD = 50;
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]);
}
let currentIndex = 0;
let autoplayInterval = null;
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}%)`;
let isDragging = false;
let startX = 0;
let currentTranslate = 0;
let prevTranslate = 0;
const realIndex = (currentIndex + cards.length) % cards.length;
dots.forEach((dot, index) => {
dot.classList.toggle('active', index === realIndex);
dot.setAttribute('aria-selected', index === realIndex);
});
// --- Infinite Loop: Klone erste und letzte Karte ---
const firstClone = cards[0].cloneNode(true);
const lastClone = cards[cards.length - 1].cloneNode(true);
track.appendChild(firstClone);
track.insertBefore(lastClone, cards[0]);
const allCards = Array.from(track.children);
const totalSlides = allCards.length;
// Buttons während Animation blockieren
prevBtn.disabled = animate;
nextBtn.disabled = animate;
}
currentIndex = 1;
updateCarousel(false);
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;
// --- Funktionen ---
function updateCarousel(animate = true) {
track.style.transition = animate ? 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)' : 'none';
track.style.transform = `translateX(${-currentIndex * 100}%)`;
// Dots nur für echte Slides
dots.forEach((dot, i) => {
const slideIndex = (currentIndex - 1 + cards.length) % cards.length;
dot.classList.toggle('active', i === slideIndex);
dot.setAttribute('aria-selected', i === slideIndex);
});
function goToSlide(index) {
if (isAnimating) return;
isAnimating = true;
currentIndex = index;
updateCarousel();
resetAutoplay();
}
prevTranslate = -currentIndex * 100;
}
function nextSlide() {
if (isAnimating) return;
isAnimating = true;
currentIndex++;
updateCarousel();
}
function goToSlide(index, animate = true) {
currentIndex = index;
updateCarousel(animate);
}
function prevSlide() {
if (isAnimating) return;
isAnimating = true;
currentIndex--;
updateCarousel();
}
function nextSlide() { goToSlide(currentIndex + 1); }
function prevSlide() { goToSlide(currentIndex - 1); }
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);
}
// --- Autoplay ---
function startAutoplay() {
if (autoplayInterval) return;
autoplayInterval = setInterval(nextSlide, AUTOPLAY_DELAY);
}
function stopAutoplay() {
carousel.classList.remove('autoplay');
clearInterval(autoplayInterval);
progress.style.transition = 'none';
progress.style.width = '0%';
}
function stopAutoplay() {
clearInterval(autoplayInterval);
autoplayInterval = null;
}
function resetAutoplay() {
stopAutoplay();
startAutoplay();
}
// 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);
function resetAutoplay() {
stopAutoplay();
startAutoplay();
})();
}
// --- Buttons und Dots ---
dots.forEach((dot, i) => dot.addEventListener('click', () => goToSlide(i + 1)));
if (prevBtn) prevBtn.addEventListener('click', prevSlide);
if (nextBtn) nextBtn.addEventListener('click', nextSlide);
// --- Pointer Events für Drag/Swipe ---
carousel.addEventListener('pointerdown', pointerStart);
carousel.addEventListener('pointermove', pointerMove);
carousel.addEventListener('pointerup', pointerEnd);
carousel.addEventListener('pointercancel', pointerEnd);
carousel.addEventListener('pointerleave', pointerEnd);
function pointerStart(e) {
// Buttons und Dots sollen Drag nicht auslösen
if (e.target.closest('.sf-arrow') || e.target.closest('.sf-dot')) return;
isDragging = true;
startX = e.clientX;
stopAutoplay();
carousel.setPointerCapture(e.pointerId);
}
function pointerMove(e) {
if (!isDragging) return;
const diffX = e.clientX - startX;
currentTranslate = prevTranslate + (diffX / carousel.offsetWidth) * 100;
// Sanfte Drag-Begrenzung für Looping
const maxTranslate = 0 + 20; // leichtes Stretch nach rechts
const minTranslate = -(totalSlides - 1) * 100 - 20; // leichtes Stretch nach links
currentTranslate = Math.max(minTranslate, Math.min(maxTranslate, currentTranslate));
track.style.transition = 'none';
track.style.transform = `translateX(${currentTranslate}%)`;
}
function pointerEnd(e) {
if (!isDragging) return;
isDragging = false;
const diffX = startX - e.clientX;
if (Math.abs(diffX) > SWIPE_THRESHOLD) {
diffX > 0 ? nextSlide() : prevSlide();
} else {
updateCarousel();
}
resetAutoplay();
}
// --- Infinite Loop Adjustments ---
track.addEventListener('transitionend', () => {
if (currentIndex === 0) {
currentIndex = cards.length;
updateCarousel(false);
} else if (currentIndex === totalSlides - 1) {
currentIndex = 1;
updateCarousel(false);
}
});
// --- Keyboard ---
carousel.addEventListener('keydown', e => {
if (e.key === 'ArrowLeft') prevSlide();
else if (e.key === 'ArrowRight') nextSlide();
});
// --- Visibility change ---
document.addEventListener('visibilitychange', () => {
document.hidden ? stopAutoplay() : startAutoplay();
});
// --- Init ---
updateCarousel(false);
startAutoplay();
})();
</script>
</section>
{{ end }}