Hello world!
Welcome to WordPress. This is your first post. Edit or delete it, then start writing!
/* Villa Noir – Custom JavaScript (Settings → Custom Code / Scripts) * Status: VALIDATED (Practical Validation 24.09.2026) * Reproduziert 1:1 die Bewegungslogik aus Villa Noir.dc.html (componentDidMount/update/renderVals): * Hero-Scroll-Progress (--hp), Reveals (--o), Bild-Parallax (--py), horizontale Material-Strecke (--hh/--hx/--hq), * Kennzahlen-Hover mit Cursor-Vorschau, CTA-Hover, Formular auf/zu + Danke-Zustand. * Kein Framework, keine Abhängigkeiten. Läuft nur, wenn .vn-hero auf der Seite existiert. * Formularversand: BLOCKED_BY_F004 (siehe 09-blocked-by-f004.md) – hier nur der Front-End-Zustand wie im Design. */ (function () { 'use strict'; var CFG = { motion: true, cursorPreview: true, grain: true }; // Tweaks aus dem Design (Defaults = freigegebener Stand) var MOB = 820; function init() { var hero = document.querySelector('.vn-hero'); if (!hero) return; var root = document.documentElement; root.classList.add('vn-js', 'vn-page'); if (CFG.motion) root.classList.add('vn-motion'); if (CFG.grain) root.classList.add('vn-grain'); var nav = document.querySelector('.vn-nav'); if (nav) document.body.insertBefore(nav, document.body.firstChild); // aus dem Stacking-Kontext lösen (mix-blend-mode) var cl = function (v) { return Math.max(0, Math.min(1, v)); }; var isMob = function () { return window.innerWidth < MOB; }; // Hero-Einlauf setTimeout(function () { document.querySelectorAll('[data-vn-hero-in]').forEach(function (el) { el.style.setProperty('--o', '1'); }); }, 80); // Reveals var groups = new Map(); var io = new IntersectionObserver(function (es) { es.forEach(function (e) { if (!e.isIntersecting) return; (groups.get(e.target) || [e.target]).forEach(function (t) { t.style.setProperty('--o', '1'); }); io.unobserve(e.target); }); }, { threshold: 0, rootMargin: '0px 0px -8% 0px' }); document.querySelectorAll('[data-vn-reveal]').forEach(function (el) { if (!CFG.motion) { el.style.setProperty('--o', '1'); return; } if (getComputedStyle(el).clipPath !== 'none') { var p = el.parentElement; if (!groups.has(p)) { groups.set(p, []); io.observe(p); } groups.get(p).push(el); } else io.observe(el); }); // Scroll-Engine var speeds = [].slice.call(document.querySelectorAll('[data-vn-speed]')); var hs = document.querySelector('.vn-material'); var stage = hs && hs.querySelector('.vn-m-stage'); var ticking = false; function update() { var vh = window.innerHeight, vw = window.innerWidth; var r = hero.getBoundingClientRect(); hero.style.setProperty('--hp', (CFG.motion ? cl(-r.top / Math.max(1, r.height - vh)) : 0).toFixed(4)); speeds.forEach(function (el) { if (!CFG.motion) { el.style.setProperty('--py', '0px'); return; } var pr = el.parentElement.getBoundingClientRect(); if (pr.bottom < -200 || pr.top > vh + 200) return; el.style.setProperty('--py', ((pr.top + pr.height / 2 - vh / 2) * -parseFloat(el.getAttribute('data-vn-speed'))).toFixed(1) + 'px'); }); if (hs && stage) { var panels = stage.querySelectorAll('.vn-m-panel'); var last = panels[panels.length - 1]; var padR = parseFloat(getComputedStyle(stage).paddingRight) || 0; var dist = last ? Math.max(0, last.offsetLeft + last.offsetWidth + padR - vw) : 0; hs.style.setProperty('--hh', (dist + vh) + 'px'); var hr = hs.getBoundingClientRect(), p = cl(-hr.top / Math.max(1, dist)); hs.style.setProperty('--hx', (-dist * p).toFixed(1) + 'px'); hs.style.setProperty('--hq', p.toFixed(4)); } } window.addEventListener('scroll', function () { if (ticking) return; ticking = true; requestAnimationFrame(function () { ticking = false; update(); }); }, { passive: true }); window.addEventListener('resize', update); update(); setTimeout(update, 600); // Kennzahlen-Hover + Cursor-Vorschau var facts = document.querySelector('.vn-facts'); if (facts) { var rows = [].slice.call(facts.querySelectorAll('.vn-fact')); var prev = null, layers = []; if (CFG.cursorPreview) { prev = document.createElement('div'); prev.className = 'vn-preview'; prev.setAttribute('aria-hidden', 'true'); rows.forEach(function (row) { var l = document.createElement('div'); l.className = 'vn-preview-layer'; l.style.backgroundImage = 'url("' + row.getAttribute('data-vn-img') + '")'; prev.appendChild(l); layers.push(l); }); document.body.appendChild(prev); } var cur = { x: -999, y: -999, tx: -999, ty: -999 }; var setActive = function (i) { var on = i != null && !isMob(); facts.classList.toggle('is-hover', on); rows.forEach(function (r, j) { r.classList.toggle('is-active', on && j === i); }); if (prev) { prev.classList.toggle('is-on', on); layers.forEach(function (l, j) { l.classList.toggle('is-on', on && j === i); }); } }; rows.forEach(function (r, i) { r.addEventListener('mouseenter', function () { setActive(i); }); }); facts.addEventListener('mouseleave', function () { setActive(null); }); facts.addEventListener('mousemove', function (e) { cur.tx = e.clientX + 40; cur.ty = e.clientY; if (cur.x < -900) { cur.x = cur.tx; cur.y = cur.ty; } }); if (prev) (function loop() { cur.x += (cur.tx - cur.x) * 0.14; cur.y += (cur.ty - cur.y) * 0.14; prev.style.setProperty('--cx', cur.x.toFixed(1) + 'px'); prev.style.setProperty('--cy', cur.y.toFixed(1) + 'px'); prev.style.setProperty('--cr', Math.max(-6, Math.min(6, (cur.tx - cur.x) * 0.04)).toFixed(2) + 'deg'); requestAnimationFrame(loop); })(); } // CTA + Formular var box = document.querySelector('.vn-contact'); if (box) { var btn = box.querySelector('.vn-cta'), form = box.querySelector('.vn-form'), sent = box.querySelector('.vn-sent'); btn.addEventListener('mouseenter', function () { if (!isMob()) btn.classList.add('is-hover'); }); btn.addEventListener('mouseleave', function () { btn.classList.remove('is-hover'); }); btn.addEventListener('click', function () { var open = box.classList.toggle('is-open'); btn.setAttribute('aria-expanded', open ? 'true' : 'false'); }); form.addEventListener('submit', function (e) { e.preventDefault(); if (!form.checkValidity()) { form.reportValidity(); return; } var name = String(new FormData(form).get('name') || ''); sent.querySelector('.vn-sent-name').textContent = name ? ', ' + name.split(' ')[0] : ''; form.hidden = true; form.style.display = 'none'; sent.hidden = false; // BLOCKED_BY_F004: Kein Versand. Anbindung an ZOOlanders Essentials Form erst nach F-004. }); } } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init(); })();
Welcome to WordPress. This is your first post. Edit or delete it, then start writing!