/* Veld — ten small delights. Vanilla, self-initializing, defensive. */ (function () { 'use strict'; /* ---------- 1 · Living tab title + candle favicon ---------- */ const ORIG_TITLE = document.title; let away = false, restoreT = 0; function favicon(emoji) { try { const c = document.createElement('canvas'); c.width = c.height = 64; const x = c.getContext('2d'); x.font = '52px serif'; x.textAlign = 'center'; x.textBaseline = 'middle'; x.fillText(emoji, 32, 38); let l = document.querySelector("link[rel~='icon']"); if (!l) { l = document.createElement('link'); l.rel = 'icon'; document.head.appendChild(l); } l.href = c.toDataURL('image/png'); } catch (e) {} } document.addEventListener('visibilitychange', () => { if (document.hidden) { away = true; document.title = '🕯 the gate is still open…'; favicon('🕯'); } else if (away) { away = false; document.title = '✦ welcome back'; favicon('🌕'); clearTimeout(restoreT); restoreT = setTimeout(() => { document.title = ORIG_TITLE; }, 2600); } }); /* ---------- 6 · A lone firefly that trails the cursor (hero only) ---------- */ const fly = document.createElement('div'); fly.className = 'cursor-firefly'; document.body.appendChild(fly); let fx = innerWidth / 2, fy = innerHeight * 0.4, tx = fx, ty = fy, shown = false; addEventListener('pointermove', (e) => { tx = e.clientX; ty = e.clientY; const hero = document.querySelector('.hero-pin-wrap'); const inHero = hero && e.clientY < hero.getBoundingClientRect().bottom && window.scrollY < innerHeight * 0.55 && e.pointerType !== 'touch'; if (inHero && !shown) { shown = true; fly.classList.add('on'); } else if (!inHero && shown) { shown = false; fly.classList.remove('on'); } }, { passive: true }); (function loop() { fx += (tx - fx) * 0.055; fy += (ty - fy) * 0.055; fly.style.transform = `translate(${fx}px, ${fy}px)`; requestAnimationFrame(loop); })(); /* ---------- 7 · Secret words ---------- */ const WORDS = { czocha: '✦ the gate hears you', open: '✦ not yet — but soon', veld: '✦ you are already one of us', moon: '☽ she is watching', }; let buf = ''; addEventListener('keydown', (e) => { const t = e.target; if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return; if (e.key.length !== 1 || e.metaKey || e.ctrlKey || e.altKey) return; buf = (buf + e.key.toLowerCase()).slice(-8); for (const w in WORDS) { if (buf.endsWith(w)) { bloom(WORDS[w]); buf = ''; break; } } }); function bloom(msg) { const flash = document.createElement('div'); flash.className = 'secret-flash'; document.body.appendChild(flash); const note = document.createElement('div'); note.className = 'secret-whisper'; note.textContent = msg; document.body.appendChild(note); requestAnimationFrame(() => { flash.classList.add('show'); note.classList.add('show'); }); setTimeout(() => { flash.classList.remove('show'); note.classList.remove('show'); }, 2200); setTimeout(() => { flash.remove(); note.remove(); }, 3000); } window.__veldBloom = bloom; })(); /* ======================= TWENTY MORE ======================= */ (function () { 'use strict'; const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches; const touch = matchMedia('(hover: none)').matches; const $ = (s, r) => (r || document).querySelector(s); /* 11 · candle-glow cursor on dark sections */ let glow; if (!touch && !reduce) { glow = document.createElement('div'); glow.className = 'candle-glow'; document.body.appendChild(glow); } /* 13 · idle firefly drift */ let idleT = 0; function scheduleIdle() { clearTimeout(idleT); idleT = setTimeout(driftFly, 24000); } function driftFly() { if (reduce || document.hidden) { scheduleIdle(); return; } const f = document.createElement('div'); f.className = 'idle-fly'; document.body.appendChild(f); const y0 = innerHeight * (0.3 + Math.random() * 0.4); const dir = Math.random() < 0.5 ? 1 : -1; const x0 = dir > 0 ? -20 : innerWidth + 20; const dur = 9000 + Math.random() * 4000; const t0 = performance.now(); (function step(t) { const p = (t - t0) / dur; if (p >= 1) { f.remove(); scheduleIdle(); return; } const x = x0 + dir * (innerWidth + 40) * p; const y = y0 + Math.sin(p * Math.PI * 3) * 42; f.style.transform = `translate(${x}px, ${y}px)`; f.style.opacity = Math.sin(p * Math.PI).toFixed(3); requestAnimationFrame(step); })(t0); } scheduleIdle(); /* shared pointer rAF: candle-glow follows cursor */ let px = innerWidth / 2, py = innerHeight / 2, pdirty = false; addEventListener('pointermove', (e) => { px = e.clientX; py = e.clientY; pdirty = true; scheduleIdle(); if (glow) { const dark = !e.target.closest('.who-bg, .paper, footer .footer-light, [data-light-bg]'); glow.classList.toggle('on', dark && e.pointerType !== 'touch'); } }, { passive: true }); (function gloop() { if (pdirty && glow) { glow.style.transform = `translate(${px}px, ${py}px)`; pdirty = false; } requestAnimationFrame(gloop); })(); /* 12 · rising click embers */ if (!touch) addEventListener('pointerdown', (e) => { if (reduce) return; if (e.target.closest('a, button, input, textarea, select, label, .g-item, [role="button"]')) return; for (let i = 0; i < 3; i++) { const em = document.createElement('div'); em.className = 'tap-ember'; em.style.left = (e.clientX + (Math.random() * 16 - 8)) + 'px'; em.style.top = (e.clientY + (Math.random() * 8 - 4)) + 'px'; em.style.animationDelay = (i * 70) + 'ms'; document.body.appendChild(em); setTimeout(() => em.remove(), 1700); } }, { passive: true }); /* 14 · molten wax scroll progress */ const wax = document.createElement('div'); wax.className = 'wax-progress'; document.body.appendChild(wax); let waxRaf = 0; function paintWax() { waxRaf = 0; const h = document.documentElement.scrollHeight - innerHeight; const p = h > 0 ? Math.min(scrollY / h, 1) : 0; wax.style.width = (p * 100).toFixed(2) + 'vw'; wax.style.opacity = p > 0.004 ? '1' : '0'; } addEventListener('scroll', () => { if (!waxRaf) waxRaf = requestAnimationFrame(paintWax); }, { passive: true }); paintWax(); /* 6 · Konami → ember storm */ const KONAMI = ['arrowup','arrowup','arrowdown','arrowdown','arrowleft','arrowright','arrowleft','arrowright','b','a']; let ki = 0; addEventListener('keydown', (e) => { const k = (e.key || '').toLowerCase(); ki = (k === KONAMI[ki]) ? ki + 1 : (k === KONAMI[0] ? 1 : 0); if (ki === KONAMI.length) { ki = 0; if (window.__veldBloom) window.__veldBloom('✦ the castle exhales'); emberStorm(); } }); function emberStorm() { if (reduce) return; for (let i = 0; i < 60; i++) { const em = document.createElement('div'); em.className = 'tap-ember'; em.style.left = (Math.random() * innerWidth) + 'px'; em.style.top = (innerHeight + Math.random() * 40) + 'px'; em.style.animationDuration = (1600 + Math.random() * 1600) + 'ms'; em.style.animationDelay = (Math.random() * 700) + 'ms'; document.body.appendChild(em); setTimeout(() => em.remove(), 4000); } } /* helper: run fn whenever a matching node exists (handles SPA route swaps) */ function whenever(selector, fn) { const seen = new WeakSet(); const scan = () => document.querySelectorAll(selector).forEach((el) => { if (seen.has(el)) return; seen.add(el); try { fn(el); } catch (e) {} }); scan(); new MutationObserver(scan).observe(document.body, { childList: true, subtree: true }); } /* 7 · visit memory — "your Nth crossing" in the footer */ let crossings = 0; try { crossings = (parseInt(localStorage.getItem('veld_crossings') || '0', 10) || 0) + 1; localStorage.setItem('veld_crossings', String(crossings)); } catch (e) { crossings = 1; } const ORD = (n) => { if (n === 1) return 'first'; if (n === 2) return 'second'; if (n === 3) return 'third'; const s = ['th','st','nd','rd'], v = n % 100; return n + (s[(v - 20) % 10] || s[v] || s[0]); }; whenever('footer .mark, footer .footer-closing', (el) => { if (document.querySelector('.crossing-note')) return; const note = document.createElement('div'); note.className = 'crossing-note'; note.style.cssText = "font-family:'Cormorant',serif;font-style:italic;font-size:14px;color:var(--ink-mute);opacity:0.75;margin-top:14px;text-align:center;"; note.textContent = crossings <= 1 ? 'Your first crossing of the threshold.' : `Your ${ORD(crossings)} crossing — the castle remembers you.`; el.insertAdjacentElement('afterend', note); }); /* 8 · found after dark */ (function () { const h = new Date().getHours(); if (h >= 21 || h < 5) { try { if (sessionStorage.getItem('veld_dark')) return; sessionStorage.setItem('veld_dark', '1'); } catch (e) {} setTimeout(() => { if (window.__veldBloom) window.__veldBloom('☽ you found us after dark'); }, 2600); } })(); /* 9 · double-click wordmark → snuff the candles */ whenever('.hero .wordmark, nav .wordmark', (el) => { el.style.cursor = 'pointer'; el.addEventListener('dblclick', () => { if (document.querySelector('.snuff')) return; const s = document.createElement('div'); s.className = 'snuff'; s.style.cssText = 'position:fixed;inset:0;z-index:9997;pointer-events:none;background:#0a0713;opacity:0;transition:opacity 0.5s ease;'; document.body.appendChild(s); requestAnimationFrame(() => { s.style.opacity = '0.92'; }); setTimeout(() => { s.style.opacity = '0'; }, 1200); setTimeout(() => s.remove(), 1900); if (window.__veldBloom) setTimeout(() => window.__veldBloom('✦ relit'), 1250); }); }); /* 19 & 20 · poetic letter meter + forming wax seal (event-delegated, SPA-proof) */ const phase = (n) => { if (n === 0) return 'A blank page. Begin where it itches.'; if (n < 40) return 'a first breath…'; if (n < 120) return 'a sentence forming'; if (n < 280) return 'a paragraph — keep going'; if (n < 480) return 'a letter worth reading'; return 'enough. now make it true.'; }; function ensureLetterUI(ta) { const wrap = ta.closest('.form-field') || ta.parentElement; if (wrap && !wrap.classList.contains('letter-field-wrap')) wrap.classList.add('letter-field-wrap'); let meter = wrap && wrap.querySelector('.letter-meter'); if (!meter) { meter = document.createElement('div'); meter.className = 'letter-meter'; ta.insertAdjacentElement('afterend', meter); const cc = wrap && wrap.querySelector('.char-count'); if (cc) cc.style.display = 'none'; } let seal = wrap && wrap.querySelector('.letter-seal'); if (!seal && wrap) { seal = document.createElement('div'); seal.className = 'letter-seal'; seal.textContent = 'V'; wrap.appendChild(seal); } return { meter, seal }; } function updLetter(ta) { const { meter, seal } = ensureLetterUI(ta); const n = ta.value.trim().length; if (meter) meter.textContent = phase(n); if (seal) { const f = Math.min(n / 280, 1); seal.style.opacity = (f * 0.96).toFixed(2); seal.style.transform = `scale(${(0.4 + f * 0.6).toFixed(2)}) rotate(${(-22 + f * 22).toFixed(1)}deg)`; } } const isLetter = (el) => el && el.matches && el.matches('#message, textarea[name="message"]'); document.addEventListener('input', (e) => { if (isLetter(e.target)) updLetter(e.target); }); whenever('#message, textarea[name="message"]', updLetter); /* 12b · lightbox dust motes */ whenever('.lightbox .lb-stage', (stage) => { if (reduce || stage.querySelector('.lb-motes')) return; const m = document.createElement('div'); m.className = 'lb-motes'; let html = ''; for (let i = 0; i < 14; i++) { const dur = 7 + Math.random() * 8, delay = -Math.random() * dur, left = Math.random() * 100; html += ``; } m.innerHTML = html; stage.insertBefore(m, stage.firstChild); }); /* 15 · pricing figure counts up on reveal */ whenever('.pricing-figure .amt', (amt) => { const target = 1800, start = 900; const fmt = (v) => '€' + v.toLocaleString('en-US'); // avoid a visible snap: if it's still below the fold, pre-set the start value if (!reduce && amt.getBoundingClientRect().top > innerHeight) amt.textContent = fmt(start); const io = new IntersectionObserver((ents) => { ents.forEach((en) => { if (!en.isIntersecting) return; io.disconnect(); if (reduce) { amt.textContent = fmt(target); return; } const t0 = performance.now(), dur = 1100; (function tick(t) { const p = Math.min((t - t0) / dur, 1); const e = 1 - Math.pow(1 - p, 3); amt.textContent = fmt(Math.round(start + (target - start) * e)); if (p < 1) requestAnimationFrame(tick); })(t0); }); }, { threshold: 0.6 }); io.observe(amt); }); /* 17 · gallery tiles tilt toward the cursor */ if (!touch && !reduce) whenever('.g-item', (tile) => { const img = tile.querySelector('.g-img'); if (!img) return; tile.addEventListener('pointermove', (e) => { const r = tile.getBoundingClientRect(); const rx = ((e.clientY - r.top) / r.height - 0.5) * -6; const ry = ((e.clientX - r.left) / r.width - 0.5) * 6; tile.style.transform = `perspective(800px) rotateX(${rx.toFixed(2)}deg) rotateY(${ry.toFixed(2)}deg)`; tile.style.transition = 'transform 0.08s linear'; }); tile.addEventListener('pointerleave', () => { tile.style.transition = 'transform 0.5s ease'; tile.style.transform = ''; }); }); /* 19b · eyebrows flicker-ignite the first time they enter view */ whenever('.eyebrow', (eb) => { const io = new IntersectionObserver((ents) => { ents.forEach((en) => { if (!en.isIntersecting) return; io.disconnect(); if (reduce) return; eb.style.animation = 'eyebrow-ignite 0.9s ease-out'; }); }, { threshold: 0.9 }); io.observe(eb); }); /* 30 · footer "leave a light" — persistent fireflies + count */ const lightLayer = document.createElement('div'); lightLayer.className = 'left-lights'; let lights = 0; try { lights = parseInt(localStorage.getItem('veld_lights') || '0', 10) || 0; } catch (e) {} // baseline so it never reads lonely const BASE = 1240; function placePersisted() { let pts = []; try { pts = JSON.parse(localStorage.getItem('veld_light_pts') || '[]'); } catch (e) {} pts.slice(-30).forEach((pt) => addLight(pt.x, pt.y, false)); } function addLight(xPct, yPct, persist) { const i = document.createElement('i'); i.style.left = xPct + '%'; i.style.top = yPct + '%'; i.style.animationDelay = (-Math.random() * 3) + 's'; lightLayer.appendChild(i); if (persist) { let pts = []; try { pts = JSON.parse(localStorage.getItem('veld_light_pts') || '[]'); } catch (e) {} pts.push({ x: xPct, y: yPct }); try { localStorage.setItem('veld_light_pts', JSON.stringify(pts.slice(-30))); } catch (e) {} } } whenever('footer .wrap', (fw) => { if (document.querySelector('.leave-light')) return; const footer = fw.closest('footer') || fw.parentElement; if (footer && !footer.contains(lightLayer)) footer.appendChild(lightLayer); placePersisted(); const btn = document.createElement('button'); btn.className = 'leave-light'; btn.type = 'button'; const setLabel = () => { btn.innerHTML = ` ${(BASE + lights).toLocaleString('en-US')} souls have left a light`; }; setLabel(); btn.addEventListener('click', () => { lights += 1; try { localStorage.setItem('veld_lights', String(lights)); } catch (e) {} addLight(8 + Math.random() * 84, 12 + Math.random() * 70, true); setLabel(); if (window.__veldBloom && lights === 1) window.__veldBloom('✦ your light will be here when you return'); }); const closing = fw.querySelector('.footer-closing') || fw.firstElementChild; if (closing) closing.insertAdjacentElement('afterend', btn); else fw.appendChild(btn); }); })(); /* ======================= TWENTY HOME-PAGE DELIGHTS ======================= */ (function () { 'use strict'; const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches; const touch = matchMedia('(hover: none)').matches; function whenever(selector, fn) { const seen = new WeakSet(); const scan = () => document.querySelectorAll(selector).forEach((el) => { if (seen.has(el)) return; seen.add(el); try { fn(el); } catch (e) {} }); scan(); new MutationObserver(scan).observe(document.body, { childList: true, subtree: true }); } const onReveal = (el, fn, threshold) => { const io = new IntersectionObserver((ents) => { ents.forEach((en) => { if (en.isIntersecting) { io.disconnect(); fn(el); } }); }, { threshold: threshold || 0.5 }); io.observe(el); }; /* h1 · wordmark ignite is pure CSS (.hero .wordmark) — runs once on mount */ /* h3 · primary hero CTA breathes (pure CSS, see stylesheet) */ /* h6 · the CTA breathes a few embers on hover (delegated — survives re-renders) */ let ctaLast = 0; if (!touch && !reduce) document.addEventListener('pointerover', (e) => { const cta = e.target.closest && e.target.closest('.hero-cta'); if (!cta) return; const now = Date.now(); if (now - ctaLast < 700) return; ctaLast = now; const r = cta.getBoundingClientRect(); for (let i = 0; i < 4; i++) { const em = document.createElement('div'); em.className = 'tap-ember'; em.style.left = (r.left + r.width * (0.2 + Math.random() * 0.6)) + 'px'; em.style.top = (r.top + r.height * 0.7) + 'px'; em.style.animationDelay = (i * 80) + 'ms'; document.body.appendChild(em); setTimeout(() => em.remove(), 1700); } }); /* h8 · the moon drifts a little with the cursor (hero only) */ if (!touch && !reduce) { let moon = null, mx = 0, my = 0, mdirty = false; whenever('.hero .moon', (m) => { moon = m; m.style.transition = 'transform 0.6s ease-out'; }); addEventListener('pointermove', (e) => { if (!moon) return; const hero = document.querySelector('.hero-pin-wrap'); if (!hero || window.scrollY > innerHeight * 0.7) { if (mdirty) { moon.style.transform = ''; mdirty = false; } return; } mx = (e.clientX / innerWidth - 0.5) * 10; my = (e.clientY / innerHeight - 0.5) * 6; moon.style.transform = `translate(${mx.toFixed(1)}px, ${my.toFixed(1)}px)`; mdirty = true; }, { passive: true }); } /* h14 · key-facts stagger up on reveal */ whenever('.keyfacts-grid', (g) => onReveal(g, () => g.classList.add('kf-in'), 0.3)); /* h16 · explore card images drift with the cursor */ if (!touch && !reduce) whenever('.ex-card', (card) => { const img = card.querySelector('.ex-img'); if (!img) return; card.addEventListener('pointermove', (e) => { const r = card.getBoundingClientRect(); const dx = (e.clientX - r.left) / r.width - 0.5; const dy = (e.clientY - r.top) / r.height - 0.5; img.style.transform = `scale(1.08) translate(${(dx * -10).toFixed(1)}px, ${(dy * -10).toFixed(1)}px)`; }); card.addEventListener('pointerleave', () => { img.style.transform = ''; }); }); /* h20 · tag the 'From' key-fact so it can press a wax seal */ whenever('.fact', (f) => { const k = f.querySelector('.fact-k'); if (k && /^from$/i.test(k.textContent.trim())) f.classList.add('fact-price'); }); })(); /* ======================= EXCLUSIVE / WELL-TOUCHED ======================= */ (function () { 'use strict'; function whenever(selector, fn) { const seen = new WeakSet(); const scan = () => document.querySelectorAll(selector).forEach((el) => { if (seen.has(el)) return; seen.add(el); try { fn(el); } catch (e) {} }); scan(); new MutationObserver(scan).observe(document.body, { childList: true, subtree: true }); } /* p1 + p2 · engraved invitation frame with corner filigree */ if (!document.querySelector('.invite-frame')) { const frame = document.createElement('div'); frame.className = 'invite-frame'; frame.innerHTML = ''; document.body.appendChild(frame); } /* p8 · parchment grain */ if (!document.querySelector('.grain')) { const g = document.createElement('div'); g.className = 'grain'; document.body.appendChild(g); } /* p15 · establishment mark in the footer */ whenever('footer .sig', (sig) => { if (sig.parentElement && sig.parentElement.querySelector('.est-mark')) return; const est = document.createElement('div'); est.className = 'est-mark'; est.innerHTML = 'Convened MMXXVI Zamek Czocha By invitation'; sig.insertAdjacentElement('afterend', est); }); })();