diff --git a/assets/styles.css b/assets/styles.css index 1b2d190..6ff1205 100644 --- a/assets/styles.css +++ b/assets/styles.css @@ -27,6 +27,7 @@ body { padding-bottom: 0; margin-top: 0px; position: relative; + z-index: 1; display: grid; grid-template-columns: 1fr auto; align-items: start; @@ -79,6 +80,8 @@ body { align-items: baseline; justify-content: space-between; max-width: 550px; + position: relative; + z-index: 1; } .index-header .now { @@ -393,6 +396,226 @@ body { opacity: 1; } +/* === VINE ASCII ANIMATION === */ +#vine-container { + position: fixed; + top: 0; + right: 0; + width: calc(100vw - 640px); + height: 100vh; + z-index: 0; + overflow: hidden; +} + +#vine-container:hover { + cursor: crosshair; +} + +.vine-char { + cursor: pointer; +} + +.vine-char:hover { + color: #111 !important; +} + +.vine-flower { + cursor: grab; +} + +.vine-flower:active { + cursor: grabbing; +} + +.vine-flower:hover { + filter: brightness(1.4); +} + +@media (max-width: 1000px) { + #vine-container { + display: none; + } +} + +@keyframes flowerFall { + 0% { + transform: translateY(0) rotate(0deg); + opacity: 0.95; + } + 100% { + transform: translateY(100px) rotate(60deg); + opacity: 0; + } +} + +@keyframes leafFall { + 0% { + transform: translateY(0) rotate(0deg) scale(1); + opacity: 0.6; + } + 100% { + transform: translateY(80px) rotate(90deg) scale(0.6); + opacity: 0; + } +} + +@keyframes flowerPulse { + 0%, 100% { + transform: scale(1); + opacity: 0.9; + } + 50% { + transform: scale(1.15); + opacity: 1; + } +} + +.falling-flower { + animation: flowerFall 2.5s ease-in forwards; +} + +.falling-leaf { + animation: leafFall 2s ease-in forwards; +} + +.vine-flower { + animation: flowerPulse 2.5s ease-in-out infinite; +} + +#vine-help-btn { + position: absolute; + top: 10px; + right: 10px; + width: 22px; + height: 22px; + border: 1px solid #111; + background: transparent; + font-family: 'IM Fell English', serif; + font-size: 14px; + line-height: 1; + color: #111; + cursor: pointer; + z-index: 10; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + transition: background 0.2s, color 0.2s; +} + +#vine-help-btn:hover { + background: #111; + color: #fff; +} + +#vine-flower-counter { + position: absolute; + top: 36px; + right: 10px; + font-family: 'IM Fell English', serif; + font-size: 13px; + color: hsl(180, 60%, 70%); + text-align: center; + width: 22px; + line-height: 1; + z-index: 10; + pointer-events: none; +} + +#vine-help-panel { + position: absolute; + top: 40px; + right: 10px; + width: 260px; + background: #fff; + border: 1px solid #111; + padding: 0.8rem 1rem; + z-index: 10; + font-family: 'IM Fell English', serif; + font-size: 14px; + line-height: 1.5; + color: #111; + cursor: default; +} + +#vine-help-panel .close { + position: absolute; + top: 2px; + right: 6px; + font-size: 18px; + cursor: pointer; + line-height: 1; + padding: 0; + background: none; + border: none; + color: #111; + font-family: 'IM Fell English', serif; +} + +#vine-help-panel .close:hover { + opacity: 0.6; +} + +#vine-help-panel h3 { + margin: 0 0 0.6rem 0; + font-size: 15px; + font-weight: normal; + border-bottom: 1px solid #eee; + padding-bottom: 0.3rem; +} + +#vine-help-panel ul { + margin: 0; + padding: 0; + list-style: none; +} + +#vine-help-panel li { + margin-bottom: 0.4rem; + padding-left: 0.8rem; + position: relative; +} + +#vine-help-panel li::before { + content: '—'; + position: absolute; + left: 0; + opacity: 0.4; +} + +#vine-help-panel li kbd { + font-family: 'Courier New', monospace; + font-size: 11px; + border: 1px solid #ddd; + padding: 0 3px; + background: #f9f9f9; + margin-right: 2px; +} + +/* === SWIRL (page d'accueil) === */ +#swirl-container { + position: fixed; + top: 0; + right: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + z-index: 0; + overflow: hidden; +} + +.swirl-char { + display: inline-block; + will-change: transform, opacity; +} + +@media (prefers-reduced-motion: reduce) { + #vine-container, + #swirl-container { + display: none; + } +} + /* === DETAILS / SUMMARY === */ summary { cursor: pointer; diff --git a/assets/swirl.js b/assets/swirl.js new file mode 100644 index 0000000..95d1e31 --- /dev/null +++ b/assets/swirl.js @@ -0,0 +1,124 @@ +(function () { + const CHARS = ['~', '-', '_', '·', '°', '*', 'o', '+', '×', '=', '≈', '∿', '◦']; + + function pick(arr) { + return arr[Math.floor(Math.random() * arr.length)]; + } + + class Swirl { + constructor(container) { + this.container = container; + this.cx = container.offsetWidth / 2 || window.innerWidth / 2; + this.cy = container.offsetHeight / 2 || window.innerHeight / 2; + this.elements = []; + this.frameCount = 0; + this.angle = 0; + this.radius = 2; + this.hue = Math.random() * 360; + this.hueSpeed = 0.25; + this.angleStep = 0.18; + this.radiusStep = 0.55; + this.globalRotation = 0; + + this.loop = this.loop.bind(this); + requestAnimationFrame(this.loop); + + window.addEventListener('resize', () => { + this.cx = container.offsetWidth / 2 || window.innerWidth / 2; + this.cy = container.offsetHeight / 2 || window.innerHeight / 2; + }); + } + + createChar(x, y, hue, scale) { + const color = `hsl(${hue}, 60%, 68%)`; + const el = document.createElement('span'); + el.textContent = pick(CHARS); + el.classList.add('swirl-char'); + el.style.cssText = + `position:absolute;` + + `left:${x}px;top:${y}px;` + + `font-family:"Courier New",Courier,monospace;` + + `font-size:15px;line-height:1;` + + `color:${color};` + + `pointer-events:none;user-select:none;` + + `opacity:0.85;`; + el.style.transform = `scale(${scale}) rotate(${Math.random() * 360}deg)`; + el.style.display = 'inline-block'; + + this.container.appendChild(el); + this.elements.push({ + el, + born: this.frameCount, + maxAge: 900 + Math.floor(Math.random() * 400), + }); + } + + ageElements() { + if (this.frameCount % 12 !== 0) return; + const now = this.frameCount; + let pruneCount = 0; + const maxPrune = 12; + for (let i = 0; i < this.elements.length && pruneCount < maxPrune; i++) { + const item = this.elements[i]; + const age = now - item.born; + if (age >= item.maxAge) { + item.el.remove(); + this.elements.splice(i, 1); + i--; + pruneCount++; + } else if (age > item.maxAge - 150) { + const fade = 1 - (age - (item.maxAge - 150)) / 150; + item.el.style.opacity = Math.max(0, fade * 0.85); + } + } + } + + update() { + this.frameCount++; + + // Rotation globale du tourbillon + this.globalRotation += 0.002; + + // Évolution de la couleur + this.hue = (this.hue + this.hueSpeed) % 360; + + // Ajouter un nouveau caractère à la spirale + if (this.frameCount % 2 === 0) { + this.angle += this.angleStep; + // L'espacement augmente avec le rayon (spirale qui s'écarte) + const dynamicStep = this.radiusStep + (this.radius / 100) * 0.18; + this.radius += dynamicStep; + + const effectiveAngle = this.angle + this.globalRotation; + const x = this.cx + Math.cos(effectiveAngle) * this.radius; + const y = this.cy + Math.sin(effectiveAngle) * this.radius * 0.6; + + // Scale qui grandit avec le rayon + const scale = 0.6 + (this.radius / 600) * 0.6; + + this.createChar(x, y, this.hue, scale); + } + + // Quand le rayon dépasse l'écran, on recommence plus petit + const maxRadius = Math.max(this.cx, this.cy) + 80; + if (this.radius > maxRadius) { + this.radius = 8 + Math.random() * 12; + this.hue = (this.hue + 60 + Math.random() * 120) % 360; + } + + this.ageElements(); + } + + loop() { + this.update(); + requestAnimationFrame(this.loop); + } + } + + document.addEventListener('DOMContentLoaded', () => { + const container = document.getElementById('swirl-container'); + if (container) { + new Swirl(container); + } + }); +})(); diff --git a/assets/vine.js b/assets/vine.js new file mode 100644 index 0000000..6c926f7 --- /dev/null +++ b/assets/vine.js @@ -0,0 +1,539 @@ +(function () { + const CHARS = { + down: ['|', '¦', '¡', 'l', 'I', '1'], + downRight: ['\\', '╲'], + downLeft: ['/', '╱'], + leaf: ['.', 'o', '*', "'", '"', '·', '°', '`', '¨', '+'], + flower: ['✿', '❀', '✾', '❁', '⚘'], + debris: ['°', '·', '¨', '`', ','], + }; + + function pick(arr) { + return arr[Math.floor(Math.random() * arr.length)]; + } + + function charForAngle(angle) { + const deg = angle * 180 / Math.PI; + if (deg >= 60 && deg <= 120) return pick(CHARS.down); + if (deg < 60) return pick(CHARS.downRight); + return pick(CHARS.downLeft); + } + + class Vine { + constructor(container) { + this.container = container; + this.width = container.offsetWidth || window.innerWidth; + this.height = container.offsetHeight || window.innerHeight; + this.tips = []; + this.elements = []; + this.flowers = []; + this.frameCount = 0; + this.minStep = 24; + this.flowerCount = 0; + + this.startVine(); + this.startVine(); + this.startVine(); + this.startVine(); + + this.createHelpUI(); + + this.loop = this.loop.bind(this); + requestAnimationFrame(this.loop); + + window.addEventListener('resize', () => { + this.width = container.offsetWidth || window.innerWidth; + this.height = container.offsetHeight || window.innerHeight; + }); + + // Interactions + this.container.addEventListener('click', (e) => this.handleClick(e)); + this.container.addEventListener('dblclick', (e) => this.handleDoubleClick(e)); + this.container.addEventListener('contextmenu', (e) => { + e.preventDefault(); + this.handleRightClick(e); + }); + } + + startVine() { + if (this.tips.length >= 12) return; + this.tips.push({ + x: 40 + Math.random() * Math.max(0, this.width - 80), + y: -5, + angle: Math.PI / 2 + (Math.random() - 0.5) * 0.25, + hue: Math.random() * 360, + hueSpeed: (Math.random() - 0.5) * 0.3, + generation: 0, + age: 0, + alive: true, + nextBranch: 140 + Math.random() * 140, + speed: 0.7 + Math.random() * 0.3, + distSinceLastChar: 0, + }); + } + + createHelpUI() { + const btn = document.createElement('button'); + btn.id = 'vine-help-btn'; + btn.textContent = '?'; + this.container.appendChild(btn); + + const panel = document.createElement('div'); + panel.id = 'vine-help-panel'; + panel.style.display = 'none'; + + const close = document.createElement('button'); + close.className = 'close'; + close.innerHTML = '×'; + close.setAttribute('aria-label', 'fermer'); + panel.appendChild(close); + + const title = document.createElement('h3'); + title.innerHTML = this.animText('jardinage'); + panel.appendChild(title); + + const items = [ + { k: 'clic', a: 'sur tige', d: 'coupe la liane' }, + { k: 'double-clic', a: 'sur tige', d: 'plante une fleur' }, + { k: 'clic droit', a: 'sur tige', d: 'plante une fleur' }, + { k: 'clic', a: 'sur fleur', d: "l'attrape" }, + { k: 'clic', a: 'sur fond', d: 'dévie la racine' }, + ]; + + const ul = document.createElement('ul'); + items.forEach((it) => { + const li = document.createElement('li'); + li.innerHTML = + '' + it.k + ' ' + + it.a + ' ' + it.d; + ul.appendChild(li); + }); + panel.appendChild(ul); + + this.container.appendChild(panel); + + const counter = document.createElement('div'); + counter.id = 'vine-flower-counter'; + counter.textContent = '0'; + this.counterEl = counter; + this.container.appendChild(counter); + + btn.addEventListener('click', (e) => { + e.stopPropagation(); + panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; + }); + + close.addEventListener('click', (e) => { + e.stopPropagation(); + panel.style.display = 'none'; + }); + + panel.addEventListener('click', (e) => e.stopPropagation()); + } + + animText(text) { + return text.split('').map((c, i) => { + if (c === ' ') return ' '; + const delayColor = (i * 0.3).toFixed(1); + const delayLevite = (Math.random() * 4).toFixed(2); + const duree = (3 + Math.random() * 3).toFixed(2); + return `${c}`; + }).join(''); + } + + // ===== INTERACTIONS ===== + + handleClick(e) { + const target = e.target; + + // 1. Clic sur une fleur vivante -> attraper + if (target.classList.contains('vine-flower') && !target.classList.contains('falling-flower')) { + this.catchFlower(target); + return; + } + + // 2. Clic sur caractère de tige (pas feuille) -> couper + if (target.classList.contains('vine-char') && !target.classList.contains('vine-leaf')) { + this.cutVine(parseFloat(target.style.left), parseFloat(target.style.top)); + return; + } + + // 3. Clic sur fond vide -> dévier la racine la plus proche + if (target === this.container) { + this.redirectVine(e.offsetX, e.offsetY); + } + } + + handleDoubleClick(e) { + const target = e.target; + // Double-clic sur caractère de tige -> planter une fleur + if (target.classList.contains('vine-char')) { + const x = parseFloat(target.style.left); + const y = parseFloat(target.style.top); + this.plantFlower(x, y); + } + } + + handleRightClick(e) { + const target = e.target; + // Clic droit sur caractère de tige -> planter une fleur + if (target.classList.contains('vine-char')) { + const x = parseFloat(target.style.left); + const y = parseFloat(target.style.top); + this.plantFlower(x, y); + } + } + + findClosestTip(x, y, onlyRoots = false) { + let closest = null; + let closestDist = Infinity; + for (const tip of this.tips) { + if (onlyRoots && tip.generation > 0) continue; + const dx = tip.x - x; + const dy = tip.y - y; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < closestDist) { + closestDist = dist; + closest = tip; + } + } + return closest; + } + + cutVine(clickX, clickY) { + const tip = this.findClosestTip(clickX, clickY); + if (!tip) return; + + // Effet de débris visuels + for (let i = 0; i < 4; i++) { + this.createDebris(clickX + (Math.random() - 0.5) * 20, clickY + (Math.random() - 0.5) * 20, tip.hue); + } + + // Créer deux nouvelles lianes qui divergent + const hue = tip.hue; + const hueSpeed = tip.hueSpeed; + + this.tips.push({ + x: clickX, + y: clickY, + angle: Math.PI / 2 - 0.3 - Math.random() * 0.4, + hue, + hueSpeed: hueSpeed * (0.8 + Math.random() * 0.4), + generation: 0, + age: 0, + alive: true, + nextBranch: 60 + Math.random() * 80, + speed: 0.9 + Math.random() * 0.4, + distSinceLastChar: 0, + }); + + this.tips.push({ + x: clickX, + y: clickY, + angle: Math.PI / 2 + 0.3 + Math.random() * 0.4, + hue, + hueSpeed: hueSpeed * (0.8 + Math.random() * 0.4), + generation: 0, + age: 0, + alive: true, + nextBranch: 60 + Math.random() * 80, + speed: 0.9 + Math.random() * 0.4, + distSinceLastChar: 0, + }); + + // Tuer l'ancienne liane + tip.alive = false; + } + + plantFlower(x, y) { + const tip = this.findClosestTip(x, y); + const hue = tip ? tip.hue : Math.random() * 360; + this.createFlower(x, y, hue); + } + + catchFlower(el) { + const idx = this.flowers.findIndex((f) => f.el === el); + if (idx === -1) return; + const flower = this.flowers[idx]; + if (flower.dying) return; + + flower.dying = true; + flower.el.classList.remove('vine-flower'); + flower.el.classList.add('falling-flower'); + flower.el.style.animationDuration = '0.6s'; + flower.el.style.animationTimingFunction = 'ease-out'; + + setTimeout(() => { + if (flower.el.parentNode) flower.el.remove(); + }, 700); + this.flowers.splice(idx, 1); + + this.flowerCount++; + if (this.counterEl) { + this.counterEl.textContent = this.flowerCount; + } + } + + redirectVine(clickX, clickY) { + const root = this.findClosestTip(clickX, clickY, true); + if (!root) return; + + const dx = clickX - root.x; + const dy = clickY - root.y; + let targetAngle = Math.atan2(Math.abs(dy), dx); + targetAngle = Math.max(0.3, Math.min(Math.PI - 0.3, targetAngle)); + + // Transition douce : on s'approche progressivement de l'angle cible + const diff = targetAngle - root.angle; + root.angle += diff * 0.4; + root.speed = Math.min(root.speed * 1.3, 2.5); + } + + createDebris(x, y, hue) { + const color = this.getColor(hue, y, false); + const el = document.createElement('span'); + el.textContent = pick(CHARS.debris); + el.style.cssText = + `position:absolute;left:${x}px;top:${y}px;` + + `font-family:"Courier New",Courier,monospace;` + + `font-size:12px;line-height:1;color:${color};` + + `pointer-events:none;user-select:none;` + + `opacity:0.8;`; + el.style.display = 'inline-block'; + this.container.appendChild(el); + this.elements.push({ + el, + born: this.frameCount, + maxAge: 60 + Math.floor(Math.random() * 40), + }); + } + + // ===== LOGIQUE EXISTANTE ===== + + getColor(hue, y, isFlower) { + const drifted = (hue + y * 0.04 + Math.random() * 6) % 360; + const sat = isFlower ? 68 : 58; + const lit = isFlower ? 74 : 68; + return `hsl(${drifted}, ${sat}%, ${lit}%)`; + } + + createChar(x, y, angle, hue, isLeaf) { + const color = this.getColor(hue, y, false); + const el = document.createElement('span'); + el.textContent = isLeaf ? pick(CHARS.leaf) : charForAngle(angle); + el.classList.add('vine-char'); + if (isLeaf) el.classList.add('vine-leaf'); + el.style.cssText = + `position:absolute;left:${x}px;top:${y}px;` + + `font-family:"Courier New",Courier,monospace;` + + `font-size:17px;line-height:1;color:${color};` + + `text-shadow:0 0 3px currentColor;` + + `pointer-events:auto;user-select:none;` + + `opacity:${isLeaf ? 0.55 : 0.85};`; + + const scale = 0.9 + Math.random() * 0.12; + const rot = (Math.random() - 0.5) * 5; + el.style.transform = `scale(${scale}) rotate(${rot}deg)`; + el.style.display = 'inline-block'; + + this.container.appendChild(el); + const lifeRatio = Math.min(1, y / Math.max(1, this.height)); + const maxAge = Math.floor(1800 - lifeRatio * 1000 + Math.random() * 300); + this.elements.push({ + el, + born: this.frameCount, + maxAge, + }); + } + + createFlower(x, y, hue) { + if (this.flowers.length >= 8) return; + const color = this.getColor(hue, y, true); + const el = document.createElement('span'); + el.textContent = pick(CHARS.flower); + el.classList.add('vine-flower'); + el.style.cssText = + `position:absolute;left:${x}px;top:${y}px;` + + `font-family:"Courier New",Courier,monospace;` + + `font-size:18px;line-height:1;color:${color};` + + `text-shadow:0 0 5px currentColor;` + + `pointer-events:auto;user-select:none;` + + `opacity:0.9;display:inline-block;`; + + this.container.appendChild(el); + this.flowers.push({ + el, + x, y, + life: 30 + Math.random() * 40, + dying: false, + }); + } + + updateFlowers() { + for (let i = this.flowers.length - 1; i >= 0; i--) { + const f = this.flowers[i]; + if (f.dying) continue; + f.life -= 1; + if (f.life <= 0) { + f.dying = true; + f.el.classList.remove('vine-flower'); + f.el.classList.add('falling-flower'); + setTimeout(() => { + if (f.el.parentNode) f.el.remove(); + }, 2600); + this.flowers.splice(i, 1); + } + } + } + + ageElements() { + if (this.frameCount % 10 !== 0) return; + const now = this.frameCount; + let pruneCount = 0; + const maxPrune = 18; + for (let i = 0; i < this.elements.length && pruneCount < maxPrune; i++) { + const item = this.elements[i]; + const age = now - item.born; + if (age >= item.maxAge) { + if (item.el.classList.contains('vine-leaf') && !item.falling) { + item.falling = true; + item.el.classList.add('falling-leaf'); + setTimeout(() => { + if (item.el.parentNode) item.el.remove(); + }, 2100); + } else { + item.el.remove(); + } + this.elements.splice(i, 1); + i--; + pruneCount++; + } else if (age > item.maxAge - 120 && !item.falling) { + const fade = 1 - (age - (item.maxAge - 120)) / 120; + item.el.style.opacity = Math.max(0, fade * 0.85); + } + } + } + + update() { + this.frameCount++; + if (this.frameCount % 3 !== 0) return; + + this.updateFlowers(); + this.ageElements(); + + const newTips = []; + + for (let i = 0; i < this.tips.length; i++) { + const tip = this.tips[i]; + tip.age++; + + tip.hue = (tip.hue + tip.hueSpeed) % 360; + if (tip.hue < 0) tip.hue += 360; + + tip.angle += (Math.random() - 0.5) * 0.025; + if (tip.angle < 0.3) tip.angle = 0.3; + if (tip.angle > Math.PI - 0.3) tip.angle = Math.PI - 0.3; + + const dx = Math.cos(tip.angle); + const dy = Math.sin(tip.angle); + + const step = tip.speed * 1.6; + tip.x += dx * step; + tip.y += dy * step; + tip.distSinceLastChar += step; + + if (tip.distSinceLastChar >= this.minStep) { + tip.distSinceLastChar = 0; + this.createChar(tip.x, tip.y, tip.angle, tip.hue, false); + } + + if (tip.generation > 0 && Math.random() < 0.015) { + const lx = tip.x + (Math.random() - 0.5) * 14; + const ly = tip.y + (Math.random() - 0.5) * 14; + this.createChar(lx, ly, tip.angle, tip.hue, true); + } + + if ( + tip.age >= tip.nextBranch && + tip.generation < 2 + ) { + tip.nextBranch = tip.age + 140 + Math.random() * 160; + const side = Math.random() > 0.5 ? 1 : -1; + const spread = 0.35 + Math.random() * 0.55; + newTips.push({ + x: tip.x, + y: tip.y, + angle: tip.angle + side * spread, + hue: tip.hue, + hueSpeed: tip.hueSpeed, + generation: tip.generation + 1, + age: 0, + alive: true, + nextBranch: 120 + Math.random() * 140, + speed: tip.speed * (0.75 + Math.random() * 0.15), + distSinceLastChar: 0, + }); + + this.createFlower( + tip.x + (Math.random() - 0.5) * 8, + tip.y + (Math.random() - 0.5) * 8, + tip.hue + ); + } + + if (tip.generation >= 1 && Math.random() < 0.008) { + this.createFlower( + tip.x + (Math.random() - 0.5) * 6, + tip.y + (Math.random() - 0.5) * 6, + tip.hue + ); + } + + // Branches : meurent quand sortent + if (tip.generation > 0) { + if ( + tip.y > this.height + 15 || + tip.x < -20 || + tip.x > this.width + 20 + ) { + tip.alive = false; + } + } + // Racines : respawn immédiat quand touchent le bas + else if (tip.y > this.height + 15) { + tip.y = -5; + tip.x = 40 + Math.random() * Math.max(0, this.width - 80); + tip.angle = Math.PI / 2 + (Math.random() - 0.5) * 0.25; + tip.hue = Math.random() * 360; + tip.hueSpeed = (Math.random() - 0.5) * 0.3; + tip.age = 0; + tip.nextBranch = 140 + Math.random() * 140; + tip.distSinceLastChar = 0; + // Chance de faire apparaître une nouvelle racine (max 6 racines) + if (this.tips.filter((t) => t.generation === 0).length < 6 && Math.random() < 0.35) { + this.startVine(); + } + } + } + + this.tips = this.tips.filter((t) => t.alive).concat(newTips); + + // Respawn de secours si jamais il y a trop peu de tips + if (this.tips.filter((t) => t.generation === 0).length < 3 && Math.random() < 0.5) { + this.startVine(); + } + } + + loop() { + this.update(); + requestAnimationFrame(this.loop); + } + } + + document.addEventListener('DOMContentLoaded', () => { + const container = document.getElementById('vine-container'); + if (container) { + new Vine(container); + } + }); +})(); diff --git a/blog/entries/march2026/bonjour.html b/blog/entries/march2026/bonjour.html index 62ab0b1..8ab0fa3 100644 --- a/blog/entries/march2026/bonjour.html +++ b/blog/entries/march2026/bonjour.html @@ -40,6 +40,8 @@ merci d'avoir lu +
+ diff --git a/blog/entries/march2026/multipotentiel.html b/blog/entries/march2026/multipotentiel.html index 888dc60..5fafacf 100644 --- a/blog/entries/march2026/multipotentiel.html +++ b/blog/entries/march2026/multipotentiel.html @@ -47,6 +47,8 @@ merci d'avoir lu +
+ diff --git a/blog/entries/march2026/retransmettre.html b/blog/entries/march2026/retransmettre.html index c11e43b..65756a9 100644 --- a/blog/entries/march2026/retransmettre.html +++ b/blog/entries/march2026/retransmettre.html @@ -40,6 +40,8 @@ merci d'avoir lu +
+ diff --git a/blog/entries/may2026/reutiliser.html b/blog/entries/may2026/reutiliser.html index fa222b1..140ab4f 100644 --- a/blog/entries/may2026/reutiliser.html +++ b/blog/entries/may2026/reutiliser.html @@ -59,6 +59,8 @@ merci d'avoir lu +
+ diff --git a/index.html b/index.html index 1d3675e..8e4d551 100644 --- a/index.html +++ b/index.html @@ -41,6 +41,8 @@ +
+