/* 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);
});
})();