const DS = window.IseeITDesignSystem_37fb1f; // Sanftes Scrollen zu einem Element — dieselbe Kurve wie die Anker-Navigation der App, // nutzbar auch auf Unterseiten (Fachgebiete, Unternehmen). window.sanftZu = function (el, versatz) { if (typeof el === 'string') el = document.getElementById(el); if (!el) return; var ziel = Math.max(0, el.getBoundingClientRect().top + window.pageYOffset - (versatz == null ? 100 : versatz)); var start = window.pageYOffset, weg = ziel - start; if (window.matchMedia('(prefers-reduced-motion: reduce)').matches || Math.abs(weg) < 40) { window.scrollTo(0, ziel); return; } var dauer = Math.min(1900, 620 + Math.abs(weg) * 0.34), t0 = performance.now(); var schritt = function (t) { var p = Math.min(1, (t - t0) / dauer); var e = p < .5 ? 4 * p * p * p : 1 - Math.pow(-2 * p + 2, 3) / 2; window.scrollTo(0, start + weg * e); if (p < 1) requestAnimationFrame(schritt); }; requestAnimationFrame(schritt); }; function useBlobSrc(url, fortschritt) { const [src, setSrc] = React.useState(null); React.useEffect(() => { if (!url) { setSrc(null); return; } let alive = true, obj; fetch(url).then(async r => { if (!r.ok) throw new Error('http ' + r.status); const laenge = fortschritt ? +(r.headers.get('Content-Length') || 0) : 0; if (!fortschritt || !r.body || !laenge) return r.blob(); const leser = r.body.getReader(); const teile = []; let da = 0; for (;;) { const s = await leser.read(); if (s.done) break; teile.push(s.value); da += s.value.length; if (alive) fortschritt(Math.min(1, da / laenge)); } return new Blob(teile, { type: r.headers.get('Content-Type') || 'video/mp4' }); }).then(b => { if (!alive) return; obj = URL.createObjectURL(b); setSrc(obj); }).catch(() => { if (alive && fortschritt) setSrc('fehlgeschlagen'); }); return () => { alive = false; if (obj) URL.revokeObjectURL(obj); }; }, [url]); return src; } // Sprachumschalter: ein Regler — eine helle Platte gleitet in einem gemeinsamen // Rahmen zur gewählten Sprache. Die nicht gewählten Tasten bleiben vollständig // durchsichtig (das Hero-Video scheint durch); die gewählte trägt ihre eigene Farbe. const SPRACHFARBE = { de: '#A63A1E', en: '#221E19', es: '#A3521A', el: '#0E6E74' }; // konstant: liegt stets auf der cremefarbenen Platte const SPRACHEN_KURZ = [['de', 'DE'], ['en', 'EN'], ['el', 'EL'], ['es', 'ES']]; const SPRACHEN_LANG = [['de', 'Deutsch'], ['en', 'English'], ['el', 'Ελληνικά'], ['es', 'Español']]; function SprachPlatte({ eintraege, lang, waehle, label, gross, fuellen, fett }) { const box = React.useRef(null); const [p, setP] = React.useState({ x: 0, b: 0, sitzt: false }); const [an, setAn] = React.useState(false); const messen = React.useCallback(() => { const w = box.current; if (!w) return; const el = w.querySelector('[data-aktiv="ja"]'); if (!el) return; const x = el.offsetLeft, b = el.offsetWidth; setP(v => (v.sitzt && Math.abs(v.x - x) < .5 && Math.abs(v.b - b) < .5) ? v : { x, b, sitzt: true }); }, []); React.useLayoutEffect(() => { messen(); }, [messen, lang, eintraege, gross, fuellen]); React.useEffect(() => { const f = () => messen(); window.addEventListener('resize', f, { passive: true }); if (document.fonts && document.fonts.ready) document.fonts.ready.then(f).catch(() => {}); const id = requestAnimationFrame(() => setAn(true)); return () => { window.removeEventListener('resize', f); cancelAnimationFrame(id); }; }, [messen]); const still = window.matchMedia('(prefers-reduced-motion: reduce)').matches; const taste = (aktiv, code) => ({ position: 'relative', zIndex: 1, appearance: 'none', WebkitAppearance: 'none', background: 'none', border: 0, borderRadius: 0, margin: 0, cursor: 'pointer', boxSizing: 'border-box', padding: gross ? '10px 15px' : fuellen ? '13px 8px' : '7px 4px', minWidth: gross || fuellen ? 'auto' : '38px', minHeight: fuellen ? '44px' : 'auto', flex: fuellen ? '1 1 0' : '0 0 auto', textAlign: 'center', font: gross ? (fett ? "700 13.5px/1 var(--font-grotesk)" : "500 13.5px/1 var(--font-grotesk)") : (fett ? "700 11.5px/1 var(--font-mono)" : "500 11.5px/1 var(--font-mono)"), letterSpacing: gross ? '-.005em' : '.06em', color: aktiv ? SPRACHFARBE[code] : (fett ? '#FBF8F3' : 'rgba(251,248,243,.82)'), textShadow: aktiv ? 'none' : (fett ? '0 1px 12px rgba(34,30,25,.66), 0 1px 3px rgba(34,30,25,.35)' : '0 1px 12px rgba(34,30,25,.55)'), /* Der Text färbt sich einen Wimpernschlag nach dem Eintreffen der Platte. */ transition: 'color .42s ease ' + (aktiv ? '.14s' : '0s') }); return
{/* Die Platte ragt 1px über den Rahmen hinaus: an der gewählten Sprache verschwindet die Umrandung unter ihr, statt als dunkler Saum durchzuscheinen. */} {eintraege.map(([code, txt]) => )}
; } // Hell/Dunkel-Schalter: Sonne/Mond im Header, Wahl bleibt gespeichert (Startwert: Systemeinstellung). function ThemaKnopf() { const { t } = React.useContext(window.SpracheCtx); const [dunkel, setDunkel] = React.useState(() => document.documentElement.getAttribute('data-thema') === 'dunkel'); const [hov, setHov] = React.useState(false); // Einmaliges Aufleuchten: erst wenn der Hero betrachtet wurde und der Besucher ein Stück gescrollt hat — derselbe Lichtstrahl wie auf der Ladeblende. const [blitz, setBlitz] = React.useState(false); React.useEffect(() => { if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; let t1, t2, aus = false; const beiScroll = () => { if (aus || window.pageYOffset < window.innerHeight * 1.05) return; aus = true; window.removeEventListener('scroll', beiScroll); t1 = setTimeout(() => { setBlitz(true); t2 = setTimeout(() => setBlitz(false), 1600); }, 350); }; window.addEventListener('scroll', beiScroll, { passive: true }); return () => { window.removeEventListener('scroll', beiScroll); clearTimeout(t1); clearTimeout(t2); }; }, []); const wechsle = () => setDunkel(d => { const n = !d; if (n) document.documentElement.setAttribute('data-thema', 'dunkel'); else document.documentElement.removeAttribute('data-thema'); try { localStorage.setItem('iseeit-thema', n ? 'dunkel' : 'hell'); } catch (e) {} return n; }); const label = dunkel ? (t.nav.themaHell || 'Helles Design') : (t.nav.themaDunkel || 'Dunkles Design'); return ; } function SiteHeader({ onNav, onGoto }) { const { t, lang, waehle } = React.useContext(window.SpracheCtx); const schmal = window.useSchmal(); const [offen, setOffen] = React.useState(false); const [prog, setProg] = React.useState(0); const [kopfH, setKopfH] = React.useState(58); const kopfRef = React.useRef(null); const reiheRef = React.useRef(null), navRef = React.useRef(null), logoRef = React.useRef(null), grpRef = React.useRef(null); /* Griechisch braucht in der Navigation deutlich mehr Breite als Englisch. Statt die Zeile schrumpfen und überlappen zu lassen, misst die Kopfzeile den Bedarf: passt die Navigation nicht neben Logo, Sprachen und Kontakt, wandert sie ins Menü. */ const [engpass, setEngpass] = React.useState(false); React.useEffect(() => { if (schmal) { setEngpass(false); return; } const messe = () => { const reihe = reiheRef.current, nav = navRef.current, logo = logoRef.current, grp = grpRef.current; if (!reihe || !nav || !logo || !grp) return; const cs = getComputedStyle(reihe); const platz = reihe.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight); const luecke = parseFloat(cs.columnGap) || 12; const brauche = logo.offsetWidth + nav.scrollWidth + grp.offsetWidth + 2 * luecke; setEngpass(e => (e ? brauche + 12 > platz : brauche > platz)); }; messe(); window.addEventListener('resize', messe, { passive: true }); const id = requestAnimationFrame(messe); if (document.fonts && document.fonts.ready) document.fonts.ready.then(messe).catch(() => {}); return () => { window.removeEventListener('resize', messe); cancelAnimationFrame(id); }; }, [schmal, lang]); React.useEffect(() => { const f = () => { const m = document.documentElement.scrollHeight - window.innerHeight; setProg(m > 0 ? Math.min(1, window.pageYOffset / m) : 0); }; window.addEventListener('scroll', f, { passive: true }); f(); return () => window.removeEventListener('scroll', f); }, []); React.useEffect(() => { if (!schmal && !engpass) setOffen(false); }, [schmal, engpass]); React.useEffect(() => { document.body.style.overflow = offen ? 'hidden' : ''; return () => { document.body.style.overflow = ''; }; }, [offen]); React.useEffect(() => { if (!offen) return; const f = (e) => { if (e.key === 'Escape') setOffen(false); }; window.addEventListener('keydown', f); return () => window.removeEventListener('keydown', f); }, [offen]); React.useLayoutEffect(() => { if (offen && kopfRef.current) setKopfH(kopfRef.current.offsetHeight); }, [offen, schmal, engpass]); const [kHov, setKHov] = React.useState(false); const zu = () => setOffen(false); const geh = (id) => (e) => { e.preventDefault(); zu(); onNav(id); }; const eintraege = [['wahl', t.nav.wahl], ['prozess', t.nav.prozess], ['ankommen', t.nav.ankommen], ['ueber-uns', t.nav.wir], ['faq', t.nav.fragen]]; const L = (id, txt) => e.target.style.color = 'var(--gold-hell)'} onMouseLeave={e => e.target.style.color = 'var(--sektion-text)'}>{txt}; const mLnk = { color: 'var(--sektion-text)', textDecoration: 'none', font: "500 19px/1.25 var(--font-grotesk)", padding: '16px 0', borderBottom: '1px solid rgba(251,248,243,.12)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '14px' }; return
{ e.preventDefault(); zu(); onGoto('home'); }} style={{ textDecoration: 'none', display: 'inline-flex', alignItems: 'center', flex: 'none' }}> {!schmal && } {!schmal &&
setKHov(true)} onMouseLeave={() => setKHov(false)} style={{ background: kHov ? '#7F2A12' : '#A63A1E', color: '#FFFFFF', border: '1px solid rgba(251,247,242,.85)', textDecoration: 'none', padding: '9px 10px', minWidth: '122px', textAlign: 'center', fontSize: '13.5px', fontWeight: 500, whiteSpace: 'nowrap', transition: 'background var(--dauer-hover) var(--ease)' }}>{t.nav.kontakt}
} {(schmal || engpass) &&
{schmal && }
}
{(schmal || engpass) && offen &&
}
; } function Hero({ onNav }) { const { t, lang, waehle } = React.useContext(window.SpracheCtx); const mobil = window.useMobil(); const still = window.matchMedia('(prefers-reduced-motion: reduce)').matches; const heroSrc = useBlobSrc(still ? null : 'assets/hero-bergpanorama.mp4', React.useCallback((p) => { if (window.VorhangFortschritt) window.VorhangFortschritt(p); }, [])); const videoSrc = heroSrc && heroSrc !== 'fehlgeschlagen' ? heroSrc : null; // Eingangs-Vorhang (index.html): Der Hero hält Texte und Kamerafahrt zurück, bis das Video // abspielbereit ist und der Vorhang sich öffnet — dann steigt alles gemeinsam auf. const [bereit, setBereit] = React.useState(() => { const v = document.getElementById('vorhang'); return !v || !!v.dataset.zu; }); const offenRef = React.useRef(false); const oeffnen = React.useCallback(() => { if (offenRef.current) return; offenRef.current = true; if (!window.VorhangZu || !document.getElementById('vorhang')) { setBereit(true); return; } const mind = 3200; // Mindestanzeige: ruhiges Erscheinen (1,9 s) und ein voller Glanzlauf, bevor sich die Blende hebt const seit = window.__vorhangStart ? performance.now() - window.__vorhangStart : mind; setTimeout(() => window.VorhangZu(), Math.max(0, mind - seit)); }, []); React.useEffect(() => { const f = () => setBereit(true); window.addEventListener('vorhang-zu', f); return () => window.removeEventListener('vorhang-zu', f); }, []); React.useEffect(() => { if (still || heroSrc === 'fehlgeschlagen') oeffnen(); }, [still, heroSrc, oeffnen]); const rise = (d) => bereit ? { animation: `riseIn 1.4s var(--ease) ${d}s both` } : { opacity: 0 }; const [ctaHov, setCtaHov] = React.useState(false); const [pfeilHov, setPfeilHov] = React.useState(false); return
{videoSrc ? : } {/* Lesbarkeits-Schleier: dunkelt die hellen Videopartien (Himmel) leicht ab, damit die H1 überall so gut trägt wie die Unterzeile. */}

{t.hero.titel}

{mobil ? : }
{ e.preventDefault(); onNav('wahl'); }} onMouseEnter={() => setCtaHov(true)} onMouseLeave={() => setCtaHov(false)} style={{ marginTop: mobil ? '28px' : '24px', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: ctaHov ? '#7F2A12' : '#A63A1E', color: '#FFFFFF', textDecoration: 'none', fontSize: '17px', fontWeight: 700, width: mobil ? '100%' : '180px', minHeight: '52px', padding: '0 18px', maxWidth: '100%', boxSizing: 'border-box', transition: 'background var(--dauer-hover) var(--ease)', ...rise(.6) }}>{t.hero.cta}

{t.hero.nischen}

{t.hero.sub}

{!mobil && }
; } function Versprechen({ onNav, onGoto, onAnker }) { const { t } = React.useContext(window.SpracheCtx); const v = t.versprechen; return

{v.statement}

{v.text}

{/* Überschrift der Versprechen-Liste: Goldmarke, Mono-Zeile, auslaufende Linie. */}

{v.kicker}

{v.zeilen.map((z, i) => {z.t}} letzter={i === v.zeilen.length - 1}>{z.x})}
{[['cybersecurity', 'nische-cybersecurity'], ['cloud', 'nische-cloud'], ['devops', 'nische-devops'], ['platform', 'nische-platform-engineering']].map(([n, ziel]) => { e.preventDefault(); if (onAnker) onAnker('nischen', ziel); else onGoto && onGoto('nischen'); }} style={{ display: 'block', textDecoration: 'none', color: 'inherit' }}> )}

{ e.preventDefault(); onGoto && onGoto('nischen'); }}>{v.fachgebieteLink}

; } function Atempause({ src, video, caption, position }) { const still = window.matchMedia('(prefers-reduced-motion: reduce)').matches; const vFilter = 'saturate(.9) contrast(1.02)'; const vsrc = useBlobSrc(still ? null : video); return
{vsrc ? : }
{caption}
; } function ZumAnfang() { const mobil = window.useMobil(); const [zeig, setZeig] = React.useState(false); const [hov, setHov] = React.useState(false); React.useEffect(() => { // Erscheint erst, wenn der Nutzer ein Stück nach oben scrollt, und verschwindet beim Weiterlesen. let letzte = window.pageYOffset, hoch = 0; const f = () => { const y = window.pageYOffset; const d = y - letzte; letzte = y; const k = document.getElementById('kontakt'); const frei = !k || k.getBoundingClientRect().top > window.innerHeight; if (y < window.innerHeight || !frei) { hoch = 0; setZeig(false); return; } if (d < -1) hoch = Math.min(hoch - d, 400); else if (d > 3) { hoch = 0; setZeig(false); return; } if (hoch > 70) setZeig(true); }; window.addEventListener('scroll', f, { passive: true }); return () => window.removeEventListener('scroll', f); }, []); return ; } Object.assign(window, { SiteHeader, Hero, Versprechen, Atempause, ZumAnfang, SprachPlatte });