SEO Optimierung + Leistungsscroll

This commit is contained in:
astosic 2025-08-14 22:28:22 +02:00
parent 2ae734e906
commit e77017c70a
17 changed files with 259 additions and 165 deletions

View file

@ -126,108 +126,136 @@
Quelle: .Params.features (array of {title, text, icon?})
========================= -->
{{ with .Params.features }}
<section class="service-ticker section" aria-label="Leistungen" style="background-color: #f8f9fa;">
<section class="section service-features" aria-label="Leistungen">
<style>
.service-ticker{padding-top:24px;padding-bottom:24px}
.service-ticker h2{margin:0 0 12px 0}
.ticker-viewport{position:relative;overflow:hidden;background: #f8f9fa;}
.ticker-viewport:before,.ticker-viewport:after{
content:"";position:absolute;top:0;bottom:0;width:64px;pointer-events:none;z-index:2
/* ===== Scroll-Snap Carousel local styles ===== */
.sf-wrap{--gap:16px;--card-w:86vw}
@media (min-width:640px){.sf-wrap{--card-w:68vw}}
@media (min-width:992px){.sf-wrap{--card-w:420px}}
.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
}
.ticker-viewport:before{left:0;background:linear-gradient(to right,#fff 40%,rgba(255,255,255,0))}
.ticker-viewport:after{right:0;background:linear-gradient(to left,#fff 40%,rgba(255,255,255,0))}
.ticker-track{
display:flex;gap:24px;align-items:center;width:max-content;
animation:marquee var(--dur,30s) linear infinite;
.sf-btn:hover{box-shadow:0 10px 24px rgba(245,166,35,.25);background:#FDF7EC}
.sf-btn:active{transform:translateY(1px)}
.sf-viewport{
overflow:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
border-radius:14px;scrollbar-width:thin
}
.ticker-viewport:hover .ticker-track{animation-play-state:running}
@keyframes marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:reduce){.ticker-track{animation:none}}
/* === Card-Ticks === */
.tick{
display:flex;align-items:center;gap:16px;
padding:14px 16px;border-radius:14px;background:#fff;
box-shadow:0 2px 14px rgba(0,0,0,.06);
flex:0 0 auto; /* nicht schrumpfen */
width:clamp(280px,36vw,420px); /* feste Kartenbreite für sauberes Scrolling */
.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-card{
scroll-snap-align:start;background:#fff;border:1px solid rgba(0,0,0,.08);border-radius:14px;
box-shadow:0 6px 22px rgba(0,0,0,.06);padding:16px;display:flex;gap:14px;align-items:flex-start
}
.tick-icon{
color:#F5A623;background:rgba(245,166,35,.12);
width:44px;height:44px;border-radius:12px;
display:flex;align-items:center;justify-content:center;flex:0 0 44px
.sf-ico{
flex:0 0 44px;width:44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;
color:#F5A623;background:rgba(245,166,35,.12)
}
.tick-icon svg{width:22px;height:22px;display:block}
.tick-text{display:flex;flex-direction:column;gap:2px;white-space:normal}
.tick-title{font-weight:700;font-size:inherit;line-height:1.3;margin:0}
.tick-desc{margin:0;opacity:.88}
.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>
<div class="container-wide">
<h2 class data-reveal>Leistungen</h2>
</div>
<div class="sf-wrap">
<div class="sf-head">
<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>
<div class="ticker-viewport">
<div class="ticker-track" style="--dur: {{ mul (len .) 15 }}s">
{{/* Daten vorbereiten */}}
{{ $list := . }}
{{ $total := len $list }}
{{/* ===== 1. Durchlauf ===== */}}
{{ range $i, $f := $list }}
<div class="tick">
<span class="tick-icon" aria-hidden="true">
{{ $icon := $f.icon | default (printf "i-%d" (mod $i 5)) }}
{{ if or (eq $icon "pv") (eq $icon "i-0") }}
<svg viewBox="0 0 24 24" role="img"><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>
{{ else if or (eq $icon "speicher") (eq $icon "i-1") }}
<svg viewBox="0 0 24 24" role="img"><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>
{{ else if or (eq $icon "lade") (eq $icon "i-2") }}
<svg viewBox="0 0 24 24" role="img"><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>
{{ else if or (eq $icon "study") (eq $icon "i-3") }}
<svg viewBox="0 0 24 24" role="img"><path d="M4 18V6m4 12V9m4 9v-6m4 6v-8m4 8V8" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
{{ else }}
<svg viewBox="0 0 24 24" role="img"><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"/></svg>
<div class="sf-viewport" id="sf-viewport" tabindex="0" role="region" aria-roledescription="carousel" aria-label="Leistungen Carousel">
<div class="sf-track" id="sf-track">
{{ range $i, $f := $list }}
<article class="sf-card" role="group" aria-roledescription="slide" aria-label='{{ printf "%d von %d" (add $i 1) $total }}'>
<div class="sf-ico" aria-hidden="true">
{{ $icon := $f.icon | default (printf "i-%d" (mod $i 5)) }}
{{ if or (eq $icon "pv") (eq $icon "i-0") }}
<!-- PV -->
<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>
{{ else if or (eq $icon "speicher") (eq $icon "i-1") }}
<!-- Battery -->
<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>
{{ else if or (eq $icon "lade") (eq $icon "i-2") }}
<!-- EV -->
<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>
{{ else if or (eq $icon "study") (eq $icon "i-3") }}
<!-- 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>
{{ else }}
<!-- Doc/Check -->
<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>
{{ end }}
</div>
<div class="sf-body">
<h3 class="h5">{{ $f.title }}</h3>
{{ with $f.text }}<p>{{ . | $.Page.RenderString (dict "display" "inline") }}</p>{{ end }}
</div>
</article>
{{ end }}
</span>
<div class="tick-text">
<h3 class="tick-title">{{ $f.title }}</h3>
{{ with $f.text }}<p class="tick-desc">{{ . | $.Page.RenderString (dict "display" "inline") }}</p>{{ end }}
</div>
</div>
{{ end }}
{{/* ===== 2. Durchlauf (Duplikat) ===== */}}
{{ range $i, $f := $list }}
<div class="tick">
<span class="tick-icon" aria-hidden="true">
{{ $icon := $f.icon | default (printf "i-%d" (mod $i 5)) }}
{{ if or (eq $icon "pv") (eq $icon "i-0") }}
<svg viewBox="0 0 24 24" role="img"><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>
{{ else if or (eq $icon "speicher") (eq $icon "i-1") }}
<svg viewBox="0 0 24 24" role="img"><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>
{{ else if or (eq $icon "lade") (eq $icon "i-2") }}
<svg viewBox="0 0 24 24" role="img"><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>
{{ else if or (eq $icon "study") (eq $icon "i-3") }}
<svg viewBox="0 0 24 24" role="img"><path d="M4 18V6m4 12V9m4 9v-6m4 6v-8m4 8V8" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
{{ else }}
<svg viewBox="0 0 24 24" role="img"><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"/></svg>
{{ end }}
</span>
<div class="tick-text">
<h3 class="tick-title">{{ $f.title }}</h3>
{{ with $f.text }}<p class="tick-desc">{{ . | $.Page.RenderString (dict "display" "inline") }}</p>{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
<script>
(function(){
var wrap = document.querySelector('.service-features .sf-wrap');
if(!wrap) return;
var viewport = wrap.querySelector('.sf-viewport');
var track = wrap.querySelector('.sf-track');
var btns = wrap.querySelectorAll('.sf-btn');
function cardStep(){
var gap = parseFloat(getComputedStyle(track).gap) || 16;
var card = track.querySelector('.sf-card');
return card ? card.getBoundingClientRect().width + gap : 320;
}
btns.forEach(function(b){
b.addEventListener('click', function(){
var dir = this.getAttribute('data-dir') === '1' ? 1 : -1;
viewport.scrollBy({left: cardStep() * dir, behavior: 'smooth'});
});
});
// Keyboard navigation
viewport.addEventListener('keydown', function(e){
if(e.key === 'ArrowRight'){ viewport.scrollBy({left: cardStep(), behavior:'smooth'}); }
if(e.key === 'ArrowLeft'){ viewport.scrollBy({left:-cardStep(), behavior:'smooth'}); }
});
// Drag to scroll (desktop)
var down=false, startX=0, sl=0;
viewport.addEventListener('mousedown', function(e){ down=true; viewport.classList.add('is-drag'); startX=e.pageX; sl=viewport.scrollLeft; });
window.addEventListener('mouseup', function(){ down=false; viewport.classList.remove('is-drag'); });
viewport.addEventListener('mousemove', function(e){ if(!down) return; e.preventDefault(); viewport.scrollLeft = sl - (e.pageX - startX); });
})();
</script>
</section>
{{ end }}
<!-- =========================
OUTCOMES (Nutzen/Ergebnisse)
Quelle: .Params.outcomes (array of strings)
@ -248,7 +276,7 @@
<!-- Text links -->
<div class="col-lg-6" data-reveal>
<h2 class="mb-3">Ergebnis & Mehrwert</h2>
<h3 class="mb-3">Ergebnis & Mehrwert</h3>
<ul class="checklist">
{{ range . }}
<li data-reveal>
@ -297,8 +325,8 @@
<div class="cta__bg" aria-hidden="true">
<div class="cta__grid"></div>
<div class="cta__energy-lines">
<div class="energy-line-h energy-line-h1"></div>
<div class="energy-line-h energy-line-h2"></div>
<div class="energy-line-h energy-line-h3"></div>
<div class="energy-line-v energy-line-v1"></div>
<div class="energy-line-v energy-line-v2"></div>
<div class="energy-node energy-node1"></div>
@ -311,9 +339,9 @@
<!-- Inhalt: jetzt dynamisch aus Front-Matter -->
<div class="my-container">
<div class="cta__wrap" data-reveal>
<h2 class="cta__title">
<h3 class="cta__title">
{{ .Params.cta_text | default "Lassen Sie uns Ihre Vision mit unserer Expertise zur Realität machen." }}
</h2>
</h3>
{{/* Optional: Bei Bedarf zweite Zeile aus subtitle wiederverwenden */}}
{{ with .Params.subtitle }}