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'], ['es', 'ES'], ['el', 'EL']]; const SPRACHEN_LANG = [['de', 'Deutsch'], ['en', 'English'], ['es', 'Español'], ['el', 'Ελληνικά']]; // (i): eine kurze Erklärung hinter einem ruhigen Kreis. Klick öffnet eine Karte, Klick daneben oder Escape schließt sie. function InfoPunkt({ text, label, rechts, hell, style }) { const [offen, setOffen] = React.useState(false); const [hov, setHov] = React.useState(false); const ref = React.useRef(null); React.useEffect(() => { if (!offen) return; const zu = (e) => { if (ref.current && !ref.current.contains(e.target)) setOffen(false); }; const esc = (e) => { if (e.key === 'Escape') setOffen(false); }; document.addEventListener('mousedown', zu); document.addEventListener('keydown', esc); return () => { document.removeEventListener('mousedown', zu); document.removeEventListener('keydown', esc); }; }, [offen]); const ruhig = hell ? 'rgba(251,248,243,.8)' : 'var(--stein)'; const aktiv = offen || hov; return {offen && {text}} ; } // Flaggen im Sprachumschalter: Deutsch steht für beide Zielländer, daher Deutschland und Schweiz in einer Marke. const FLAGGE = { de: , en: , es: , el: }; const SPRACHNAME = { de: 'Deutsch', en: 'English', es: 'Español', el: 'Ελληνικά' }; function SprachPlatte({ eintraege, lang, waehle, label, gross, fuellen, fett, flaggen, nurFlaggen }) { 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: nurFlaggen ? '8px 7px' : gross ? '10px 15px' : fuellen ? '13px 8px' : flaggen ? '7px 8px' : '7px 4px', minWidth: gross || fuellen || nurFlaggen ? 'auto' : '38px', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '7px', 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; // Schimmert beim Überschreiten der Schwelle; wer wieder nach oben scrollt, stellt ihn neu scharf. let t1, t2, scharf = true; const beiScroll = () => { const unten = window.pageYOffset >= window.innerHeight * 1.05; if (!unten) { scharf = true; return; } if (!scharf) return; scharf = false; 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); // Die Flaggen klappen beim Lesen nach unten auf die gewählte Sprache zusammen und öffnen sich beim Zurückscrollen wieder. const [kompakt, setKompakt] = React.useState(false); React.useEffect(() => { // Ruhig: erst nach einem guten Stück Weg in eine Richtung (60px), nie unter 260px Scrollhöhe. let letzte = window.pageYOffset, weg = 0; const f = () => { const y = window.pageYOffset, d = y - letzte; letzte = y; const m = document.documentElement.scrollHeight - window.innerHeight; setProg(m > 0 ? Math.min(1, y / m) : 0); if ((d > 0 && weg < 0) || (d < 0 && weg > 0)) weg = 0; weg += d; if (y < 260) { setKompakt(false); weg = 0; } else if (weg > 60) setKompakt(true); else if (weg < -60) setKompakt(false); }; window.addEventListener('scroll', f, { passive: true }); f(); return () => window.removeEventListener('scroll', f); }, []); React.useEffect(() => { document.body.style.overflow = offen && schmal ? 'hidden' : ''; return () => { document.body.style.overflow = ''; }; }, [offen, schmal]); 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]); const [kHov, setKHov] = React.useState(false); const [mHov, setMHov] = React.useState(false); const [fHov, setFHov] = 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 mLnk = { color: 'var(--sektion-text)', textDecoration: 'none', font: schmal ? "500 19px/1.25 var(--font-grotesk)" : "500 17px/1.25 var(--font-grotesk)", padding: schmal ? '16px 0' : '14px 0', borderBottom: '1px solid rgba(251,248,243,.12)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '14px', transition: 'color .25s ease' }; const M = (props, kind) => e.currentTarget.style.color = 'var(--gold-hell)'} onMouseLeave={e => e.currentTarget.style.color = 'var(--sektion-text)'}>{kind}; const rahmen = (hov) => ({ background: hov ? 'rgba(251,248,243,.1)' : 'rgba(251,248,243,.05)', border: hov ? '1px solid var(--gold-hell)' : '1px solid rgba(251,248,243,.62)', color: hov ? 'var(--gold-hell)' : '#FBF8F3', transition: 'color .25s ease, border-color .25s ease, background .25s ease' }); 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}}
{offen && !schmal && } {offen &&
}
; } function Hero({ onNav }) { const { t } = 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}

{ 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: '19px', 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.sub}

{t.hero.nischen}

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

{v.statement}

{/* Ü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})}

{v.gesucht}

{[['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' }}> )}

{v.profilCtaPre} { e.preventDefault(); onNav && onNav('kontakt'); }}>{v.profilCta}

; } 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 jemand weit unten ist und dann bewusst ein gutes Stück // zurück nach oben liest; jede Abwärtsbewegung setzt den Zähler wieder auf null. 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 * 2.5 || !frei) { hoch = 0; setZeig(false); return; } if (d < -1) hoch = Math.min(hoch - d, 1200); else if (d > 3) { hoch = 0; setZeig(false); return; } if (hoch > 500) setZeig(true); }; window.addEventListener('scroll', f, { passive: true }); return () => window.removeEventListener('scroll', f); }, []); return ; } Object.assign(window, { SiteHeader, Hero, Versprechen, Atempause, ZumAnfang, SprachPlatte, InfoPunkt, FLAGGE });