/* cursor.js : Curseur custom (desktop uniquement) YBCursor.init(); Au repos : point accent. Sur un lien / bouton : le point s'étire en index et la main apparaît autour (le bout du doigt reste sur la position de la souris). data-cursor-label="VOIR LE PROJET" = label contextuel. La main change selon la fête quand c'est cohérent (html[data-season]), relue à chaque événement yb:theme. Dans la roue des projets et articles (.cp-wheel) : le point devient une loupe, elle aussi accordée à la fête. */ window.YBCursor = (function () { let wrap, dot, hand, loupe, lbl, curSeason = null, override = null; const m = { x: -100, y: -100, cx: -100, cy: -100 }; /* Silhouette commune : main qui pointe, bout de l'index en (11.5, 1) */ const SHAPE = 'M11.5 1C13.2 1 14.5 2.3 14.5 4v9.2c.5-.7 1.4-1.1 2.4-1.1 1.3 0 2.4.8 2.7 1.9.5-.5 1.3-.8 2.2-.8 1.5 0 2.7 1.1 2.8 2.5.5-.4 1.2-.6 1.9-.6 1.5 0 2.7 1.2 2.7 2.7V25c0 5.5-4 9.5-9.5 9.5h-2.3c-3.4 0-5.8-1.3-7.8-3.9l-5.4-7.1c-.9-1.2-.7-2.9.5-3.8 1.2-.9 2.8-.7 3.7.4l.1.1V4c0-1.7 1.3-3 3-3z'; const CREASES = 'M14.5 13.2V19M19.6 14v5.5M24.6 15.7V20'; const base = (fill, stroke, extra, crease) => '' + '' + (extra || ''); const HANDS = { '': () => base('var(--lime)', 'var(--bg)'), // gant squelette : os blancs sur gant noir halloween: () => base('#15101a', 'var(--lime)', '', '#efe6d2'), // gant rouge et revers en fourrure blanche noel: () => base('#d61f35', '#5a0b16', '', '#8e1222'), // gant vert, trèfle doré sur le dos de la main 'st-patrick': () => base('#1b8f4c', '#0b3d21', ''), // gant blanc de cérémonie, revers doré, étincelle au bout du doigt 'nouvel-an': () => base('#ffffff', '#16141c', '', '#9aa0b0'), // gant de fête : turquoise, petite bague dorée anniversaire: () => base('var(--lime)', 'var(--bg)', '') }; /* Loupe : centre de la lentille en (14, 14) = position de la souris ; manche dessiné à plat puis tourné à 45° */ const lens = (ring, glass, handle, extra, inside) => '' + (extra || '') + '' + '' + (inside || '') + '' + ''; const stripes = (cols, w) => cols.map((c, i) => '').join(''); const LOUPES = { '': () => lens('var(--lime)', 'rgba(255,255,255,.08)', 'var(--ink)'), // toile d'araignée dans le verre, manche en bois sombre halloween: () => lens('var(--lime)', 'rgba(255,123,28,.1)', '#9a6a3a', '', ''), // manche en sucre d'orge, cerclage doré noel: () => lens('#f2c14e', 'rgba(255,255,255,.1)', '#fdfbf6', stripes(['#d61f35', 'transparent', '#d61f35', 'transparent', '#d61f35', 'transparent', '#d61f35'], 2)), 'st-patrick': () => lens('#f2c14e', 'rgba(61,220,132,.12)', '#1b8f4c', '', ''), 'nouvel-an': () => lens('#f2c14e', 'rgba(242,193,78,.1)', '#d9dde8', stripes(['#f2c14e', '#f2c14e'], 2), ''), juillet: () => lens('#ffffff', 'rgba(255,255,255,.08)', '#ffffff', stripes(['#1f3fa8', '#1f3fa8', '#ffffff', '#ffffff', '#c8102e', '#c8102e', '#c8102e'], 2)), 'black-friday': () => lens('#ffd400', 'rgba(255,212,0,.1)', '#0b0b0b', stripes(['#ffd400', '#0b0b0b', '#ffd400', '#0b0b0b', '#ffd400', '#0b0b0b', '#ffd400'], 2)), anniversaire: () => lens('var(--lime)', 'rgba(46,230,197,.1)', '#ffffff', stripes(['#2ee6c5', '#ffffff', '#ff6b81', '#ffffff', '#ffcf4a', '#ffffff', '#2ee6c5'], 2)), poisson: () => lens('#4fd1ff', 'rgba(79,209,255,.12)', '#ffb347', '', '') }; function paint() { const s = override !== null ? override : document.documentElement.getAttribute('data-season') || ''; const k = HANDS[s] ? s : ''; // aperçu (roue des thèmes) : la main prend aussi l'accent de la fête survolée const acc = override !== null && override ? (((window.YB_SEASONS || {}).list || {})[override] || {}).accent || '' : ''; [hand, loupe].forEach(e => acc ? e.style.setProperty('--lime', acc) : e.style.removeProperty('--lime')); const key = s + '|' + k; if (key === curSeason) return; curSeason = key; hand.innerHTML = '' + HANDS[k]() + ''; loupe.innerHTML = '' + (LOUPES[s] || LOUPES[''])() + ''; } function setState(label, link, inWheel) { lbl.textContent = label || ''; lbl.style.opacity = label ? '1' : '0'; lbl.style.transform = label ? 'scale(1)' : 'scale(.6)'; wrap.classList.toggle('is-loupe', !!inWheel); wrap.classList.toggle('is-hand', !!link && !inWheel); } function loop() { m.cx += (m.x - m.cx) * 0.25; m.cy += (m.y - m.cy) * 0.25; wrap.style.transform = 'translate(' + m.cx + 'px,' + m.cy + 'px)'; requestAnimationFrame(loop); } return { /* Montre la main d'une fête sans l'activer ('' = main normale, null = revenir à la fête affichée) */ preview(key) { if (!hand) return; override = key; curSeason = null; paint(); }, init() { // Animations inconditionnelles : seul ?ybstatic=1 (html.yb-static) coupe le curseur if (matchMedia('(pointer: coarse)').matches || document.documentElement.classList.contains('yb-static')) return; document.documentElement.classList.add('has-custom-cursor'); // CSS : .has-custom-cursor, .has-custom-cursor * { cursor:none } wrap = document.createElement('div'); wrap.className = 'yb-cursor'; dot = document.createElement('div'); dot.className = 'yb-cursor__dot'; hand = document.createElement('div'); hand.className = 'yb-cursor__hand'; loupe = document.createElement('div'); loupe.className = 'yb-cursor__loupe'; lbl = document.createElement('div'); lbl.className = 'yb-cursor__label'; wrap.append(dot, hand, loupe, lbl); document.body.appendChild(wrap); paint(); addEventListener('yb:theme', paint); addEventListener('mousemove', e => { m.x = e.clientX; m.y = e.clientY; }); document.addEventListener('mouseover', e => { const w = e.target.closest('.cp-wheel'); const t = e.target.closest('[data-cursor-label], a, button, [data-cursor]'); if (!t) return setState('', false, w); setState(t.getAttribute('data-cursor-label') || '', true, w); }); requestAnimationFrame(loop); } }; })();