1/* ========================================================================== 2 Formality â interactions (vanilla JS, sans dépendance) 3 Généré depuis Formality Home V2.dc.html. Amélioration progressive : la page reste 4 lisible et indexable sans JavaScript. 5 ========================================================================== */ 6(function () { 7 'use strict'; 8 9 var C = { 10 props: { ambientAnimations: true, loopSpeed: 1.3 }, 11 gen: 0, timers: [], cleanup: [], 12 13 isStatic: function() { 14 return matchMedia('(prefers-reduced-motion: reduce)').matches || (this.props.ambientAnimations ?? true) === false; 15 }, 16 17 sp: function() { 18 const v = +(this.props.loopSpeed ?? 1); 19 return isNaN(v) ? 1 : Math.min(2, Math.max(0.4, v)); 20 }, 21 22 q: function(s) { return document.querySelector(s); }, 23 24 qa: function(s) { return [...document.querySelectorAll(s)]; }, 25 26 after: function(ms, fn) { const gen = this.gen; this.timers.push(setTimeout(() => { if (gen === this.gen) fn(); }, ms)); }, 27 28 lockScroll: function(on) { 29 const b = document.body; 30 if (on) { 31 this._scrollY = window.scrollY || 0; 32 b.style.top = -this._scrollY + 'px'; 33 b.setAttribute('data-scroll-lock', ''); 34 } else if (b.hasAttribute('data-scroll-lock')) { 35 b.removeAttribute('data-scroll-lock'); 36 b.style.top = ''; 37 window.scrollTo(0, this._scrollY || 0); 38 } 39 }, 40 41 setupVideos: function() { 42 const onClick = (e) => { 43 const btn = e.target.closest('[data-yt]'); 44 if (!btn) return; 45 const id = btn.getAttribute('data-yt'); 46 const frame = btn.closest('[data-video]'); 47 if (!id || !frame || frame.querySelector('iframe')) return; 48 e.preventDefault(); 49 const iframe = document.createElement('iframe'); 50 iframe.src = 'https://www.youtube.com/embed/' + id + '?autoplay=1&rel=0&modestbranding=1&playsinline=1'; 51 iframe.title = btn.getAttribute('aria-label') || 'Témoignage vidéo'; 52 iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'; 53 iframe.allowFullscreen = true; 54 iframe.referrerPolicy = 'strict-origin-when-cross-origin'; 55 iframe.setAttribute('style', 'position:absolute;inset:0;width:100%;height:100%;border:0;display:block'); 56 frame.appendChild(iframe); 57 }; 58 document.addEventListener('click', onClick); 59 this.cleanup.push(() => document.removeEventListener('click', onClick)); 60 }, 61 62 setupContactForm: function() { 63 const FORM_URL = 'https://share-eu1.hsforms.com/1GKQF3MSpRIOcE98fc4tz0Q2dkfbc'; 64 const modal = this.q('[data-form-modal]'); 65 if (!modal) return; 66 const body = modal.querySelector('[data-form-body]'); 67 const open = () => { 68 if (!body.querySelector('iframe')) { 69 body.setAttribute('data-frame', ''); 70 const iframe = document.createElement('iframe'); 71 iframe.src = FORM_URL; 72 iframe.title = 'Prendre rendez-vous avec Formality'; 73 iframe.setAttribute('style', 'width:100%;height:100%;border:0;display:block'); 74 body.appendChild(iframe); 75 } 76 modal.style.display = 'flex'; 77 C.lockScroll(true); 78 }; 79 const close = () => { 80 modal.style.display = 'none'; 81 C.lockScroll(false); 82 }; 83 const onClick = (e) => { 84 const a = e.target.closest('a'); 85 if (a && (a.hasAttribute('data-open-form') || a.textContent.indexOf('Parlons de vos enjeux') !== -1)) { e.preventDefault(); open(); return; } 86 if (e.target.closest('[data-form-close]') || e.target.hasAttribute('data-form-backdrop')) { e.preventDefault(); close(); } 87 }; 88 const onKey = (e) => { if (e.key === 'Escape' && modal.style.display === 'flex') close(); }; 89 document.addEventListener('click', onClick); 90 document.addEventListener('keydown', onKey); 91 this.cleanup.push(() => { document.removeEventListener('click', onClick); document.removeEventListener('keydown', onKey); }); 92 }, 93 94 setupLangMenu: function() { 95 const sw = this.q('[data-lang-switch]'); 96 if (!sw) return; 97 const trigger = sw.querySelector('[data-lang-trigger]'); 98 const set = (open) => { 99 if (open) sw.setAttribute('data-open', ''); else sw.removeAttribute('data-open'); 100 if (trigger) trigger.setAttribute('aria-expanded', open ? 'true' : 'false'); 101 }; 102 if (trigger) trigger.addEventListener('click', (e) => { e.preventDefault(); set(!sw.hasAttribute('data-open')); }); 103 document.addEventListener('click', (e) => { if (!sw.contains(e.target)) set(false); }); 104 document.addEventListener('keydown', (e) => { if (e.key === 'Escape') set(false); }); 105 }, 106 107 setupMobileNav: function() { 108 const nav = this.q('[data-navbar]'); 109 const btn = this.q('[data-nav-toggle]'); 110 if (!nav || !btn) return; 111 const set = (open) => { if (open) nav.setAttribute('data-nav-open', ''); else nav.removeAttribute('data-nav-open'); btn.setAttribute('aria-expanded', open ? 'true' : 'false'); }; 112 const toggle = (e) => { e.preventDefault(); set(!nav.hasAttribute('data-nav-open')); }; 113 const onDoc = (e) => { if (nav.hasAttribute('data-nav-open') && !nav.contains(e.target)) set(false); }; 114 const onLink = (e) => { if (e.target.closest('[data-navlinks] a')) set(false); }; 115 btn.addEventListener('click', toggle); 116 document.addEventListener('click', onDoc); 117 nav.addEventListener('click', onLink); 118 this.cleanup.push(() => { btn.removeEventListener('click', toggle); document.removeEventListener('click', onDoc); nav.removeEventListener('click', onLink); }); 119 }, 120 121 componentDidMount: function() { 122 document.documentElement.lang = 'fr'; 123 this.gen = 0; this.timers = []; this.cleanup = []; 124 const onScroll = () => { 125 const nav = this.q('[data-navbar]'); 126 if (!nav) return; 127 const on = window.scrollY > 30; 128 nav.style.background = on ? 'rgba(11,3,65,.88)' : 'transparent'; 129 nav.style.backdropFilter = on ? 'blur(16px)' : 'none'; 130 nav.style.borderColor = on ? 'rgba(190,181,246,.26)' : 'transparent'; 131 }; 132 addEventListener('scroll', onScroll, { passive: true }); 133 this.cleanup.push(() => removeEventListener('scroll', onScroll)); 134 this.setupReveals(); 135 this.setupClauses(); 136 this.setupCounters(); 137 this.setupFaq(); 138 this.setupUseCases(); 139 this.setupVideos(); 140 this.setupContactForm(); 141 this.setupMobileNav(); 142 this.setupLangMenu(); 143 this.initMotion(); 144 }, 145
146 stopMotion: function() { this.gen++; this.timers.forEach(clearTimeout); this.timers = []; }, 147 148 initMotion: function() { 149 this.gen++; 150 const track = this.q('[data-tickertrack]'); 151 if (track) track.style.animationPlayState = this.isStatic() ? 'paused' : 'running'; 152 if (this.isStatic()) this.applyStatic(); 153 else { this.startHero(); this.startPipe(); this.startAsk(); } 154 }, 155 156 lineHit: function(l) { l.style.background = '#81EBC5'; l.style.boxShadow = '0 0 14px rgba(129,235,197,.5)'; }, 157 158 lineMk: function(l) { l.style.background = '#81EBC5'; l.style.boxShadow = '0 0 12px rgba(129,235,197,.45)'; }, 159 160 lineOff: function(l) { l.style.background = (l.dataset.k !== undefined) ? '#DDD9F1' : '#E7E5F2'; l.style.boxShadow = 'none'; }, 161 162 stampOn: function(el, rot) { el.style.opacity = '1'; el.style.transform = 'scale(1) rotate(' + rot + 'deg)'; }, 163 164 stampOff: function(el, scale, rot) { el.style.opacity = '0'; el.style.transform = 'scale(' + scale + ') rotate(' + rot + 'deg)'; }, 165 166 fillSlot: function(slot, v) { 167 const val = slot.querySelector('[data-slot-v]'); 168 val.textContent = v; val.style.color = '#F4F2FF'; slot.style.opacity = '1'; 169 }, 170 171 emptySlot: function(slot) { 172 const val = slot.querySelector('[data-slot-v]'); 173 val.textContent = '\u00b7\u00b7\u00b7'; val.style.color = 'rgba(244,242,255,.82)'; slot.style.opacity = '.55'; 174 }, 175 176 litDot: function(d, on) { 177 d.style.borderColor = on ? '#81EBC5' : 'rgba(190,181,246,.5)'; 178 d.style.color = on ? '#81EBC5' : 'rgba(244,242,255,.82)'; 179 d.style.boxShadow = on ? '0 0 22px rgba(129,235,197,.35)' : 'none'; 180 }, 181 182 fillField: function(v) { 183 v.textContent = v.dataset.v; 184 v.style.color = '#0A0046'; v.style.fontWeight = '600'; 185 v.style.background = 'rgba(129,235,197,.28)'; 186 v.style.borderColor = 'rgba(129,235,197,.75)'; v.style.borderStyle = 'solid'; 187 v.style.boxShadow = 'none'; 188 }, 189 190 emptyField: function(v) { 191 const ph = { 'Loyer annuel': '\u2039 loyer \u203a', 'Indexation': '\u2039 indexation \u203a', 'Prochaine \u00e9ch\u00e9ance': '\u2039 \u00e9ch\u00e9ance \u203a', 'Pr\u00e9avis': '\u2039 pr\u00e9avis \u203a' }; 192 v.textContent = ph[v.dataset.k] || '\u2039 \u2026 \u203a'; 193 v.style.color = '#6D5DD3'; v.style.fontWeight = '600'; 194 v.style.background = 'rgba(190,181,246,.18)'; 195 v.style.borderColor = 'rgba(190,181,246,.75)'; v.style.borderStyle = 'dashed'; 196 v.style.boxShadow = 'none'; 197 }, 198 199 fieldGlow: function(v) { 200 v.style.boxShadow = '0 0 14px rgba(129,235,197,.6)'; 201 }, 202 203 stepState: function(el, state) { 204 const fill = el.querySelector('[data-stepfill]'), num = el.querySelector('[data-stepnum]'), lab = el.querySelector('[data-steplabel]'); 205 const c = state === 'active' ? '#81EBC5' : '#F4F2FF'; 206 fill.style.width = state === 'upcoming' ? '0%' : '100%'; 207 num.style.color = c; lab.style.color = c; 208 }, 209 210 applyStatic: function() { 211 const card = this.q('[data-contract]'); 212 if (card) { 213 [...card.querySelectorAll('[data-field]')].forEach((f) => { this.fillField(f); this.fieldGlow(f); }); 214 [...card.querySelectorAll('[data-m]')].forEach((l) => this.lineOff(l)); 215 this.qa('[data-slot]').forEach((s) => { 216 const src = card.querySelector('[data-field][data-k="' + s.dataset.slot + '"]'); 217 if (src) this.fillSlot(s, src.dataset.v); 218 }); 219 const stamp = this.q('[data-stamp]'), cstamp = this.q('[data-cstamp]'), beam = this.q('[data-beam]'), ph = this.q('[data-cphase]'), fiche = this.q('[data-ficheind]'); 220 if (stamp) this.stampOn(stamp, -4); 221 if (cstamp) this.stampOn(cstamp, -6); 222 if (beam) beam.style.opacity = '0'; 223 if (ph) ph.textContent = 'Document sign\u00e9 \u00b7 PDF'; 224 if (fiche) fiche.style.opacity = '1'; 225 const chips = this.q('[data-scan]'); 226 if (chips) chips.querySelectorAll('[data-chip]').forEach((c) => c.remove()); 227 } 228 { const steps = [...this.qa('[data-step]')]; steps.forEach((el, k) => this.stepState(el, k < steps.length - 1 ? 'done' : 'active')); } 229 this.qa('[data-dot]').forEach((d) => this.litDot(d, true)); 230 const pulse = this.q('[data-pulse]'); 231 if (pulse) pulse.style.opacity = '0'; 232 const pairs = this.askPairs(); 233 const qEl = this.q('[data-asktext]'), ans = this.q('[data-askanswer]'), caret = this.q('[data-caret]'); 234 if (qEl) { qEl.textContent = pairs[0].q; ans.innerHTML = pairs[0].a; ans.style.opacity = '1'; caret.style.display = 'none'; } 235 }, 236 237 startHero: function() { 238 const gen = this.gen, self = this; 239 const scan = this.q('[data-scan]'), card = this.q('[data-contract]'), beam = this.q('[data-beam]'), 240 stamp = this.q('[data-stamp]'), cstamp = this.q('[data-cstamp]'), cPhase = this.q('[data-cphase]'), 241 fiche = this.q('[data-ficheind]'); 242 if (!scan || !card) return; 243 const fields = [...card.querySelectorAll('[data-field]')]; 244 const mLines = [...card.querySelectorAll('[data-m]')]; 245 const steps = [...this.qa('[data-step]')]; 246 const slots = {}; 247 this.qa('[data-slot]').forEach((s) => { slots[s.dataset.slot] = s; });
248 const sp = this.sp(), DUR = 18000 / sp; 249 let start = null; 250 const fired = new Set(), events = new Set(); 251 const geom = { h: 0, tops: [] }; 252 beam.style.willChange = 'transform'; 253 254 const setStep = (i) => steps.forEach((el, k) => self.stepState(el, k < i ? 'done' : k === i ? 'active' : 'upcoming')); 255 256 function reset() { 257 fired.clear(); events.clear(); 258 fields.forEach((f) => self.emptyField(f)); 259 mLines.forEach((l) => self.lineOff(l)); 260 Object.values(slots).forEach((s) => self.emptySlot(s)); 261 self.stampOff(stamp, .85, -4); self.stampOff(cstamp, 1.4, -6); 262 cPhase.textContent = 'Mod\u00e8le \u00b7 Champs \u00e0 compl\u00e9ter'; 263 if (fiche) fiche.style.opacity = '.4'; 264 beam.style.opacity = '0'; 265 beam.style.transform = 'translateY(0px)'; 266 geom.h = card.offsetHeight; 267 geom.tops = fields.map((f) => f.offsetTop); 268 setStep(0); 269 scan.querySelectorAll('[data-chip]').forEach((c) => c.remove()); 270 } 271 function mkChip(text, left, top) { 272 const chip = document.createElement('div'); 273 chip.setAttribute('data-chip', ''); 274 chip.style.cssText = 'position:absolute;z-index:5;font-family:Montserrat,sans-serif;font-size:11px;background:#0A0046;color:#81EBC5;border:1px solid rgba(129,235,197,.5);padding:5px 10px;border-radius:4px;white-space:nowrap;transition:transform 1s cubic-bezier(.3,.7,.2,1),opacity .3s;box-shadow:0 8px 30px rgba(0,0,0,.5)'; 275 chip.textContent = text; 276 chip.style.left = left + 'px'; chip.style.top = top + 'px'; 277 scan.appendChild(chip); 278 return chip; 279 } 280 function chipAt(line, text, hold) { 281 const sR = scan.getBoundingClientRect(), lR = line.getBoundingClientRect(); 282 const chip = mkChip(text, lR.left - sR.left, lR.top - sR.top - 10); 283 requestAnimationFrame(() => requestAnimationFrame(() => { chip.style.transform = 'translateY(-14px)'; })); 284 self.after(hold, () => { chip.style.opacity = '0'; self.after(350, () => chip.remove()); }); 285 } 286 function fieldExtract(row) { 287 self.fieldGlow(row); 288 const k = row.dataset.k, v = row.dataset.v, slot = slots[k]; 289 if (!slot) return; 290 const sR = scan.getBoundingClientRect(), lR = row.getBoundingClientRect(), tR = slot.getBoundingClientRect(); 291 const chip = mkChip(k + ' : ' + v, lR.left - sR.left, lR.top - sR.top - 4); 292 requestAnimationFrame(() => requestAnimationFrame(() => { 293 chip.style.transform = 'translate(' + (tR.left - lR.left) + 'px,' + (tR.top - lR.top) + 'px)'; 294 })); 295 self.after(1000 / sp, () => { chip.style.opacity = '0'; self.fillSlot(slot, v); self.after(350, () => chip.remove()); }); 296 } 297 function once(key, fn) { if (!events.has(key)) { events.add(key); fn(); } } 298 function loop(now) { 299 if (gen !== self.gen) return; 300 if (start == null) start = now; 301 const t = ((now - start) % DUR) * sp; 302 if (t < 40 && events.size > 0) reset(); 303 /* --- Phase 1 : remplissage du modèle --- */ 304 if (t > 800) once('f0', () => self.fillField(fields[0])); 305 if (t > 1700) once('f1', () => self.fillField(fields[1])); 306 if (t > 2600) once('f2', () => self.fillField(fields[2])); 307 if (t > 3500) once('f3', () => self.fillField(fields[3])); 308 /* --- Phase 2 : négociation IA --- */ 309 if (t > 4600) once('p2', () => { setStep(1); cPhase.textContent = 'N\u00e9gociation \u00b7 Version 3'; }); 310 if (t > 5200) once('m0', () => { self.lineMk(mLines[0]); chipAt(mLines[0], mLines[0].dataset.m, 2400 / sp); }); 311 if (t > 6600) once('m1', () => { self.lineMk(mLines[1]); chipAt(mLines[1], mLines[1].dataset.m, 2400 / sp); }); 312 /* --- Phase 3 : signature --- */ 313 if (t > 8600) once('p3', () => setStep(2)); 314 if (t > 9100) once('sig', () => { self.stampOn(cstamp, -6); cPhase.textContent = 'Document sign\u00e9 \u00b7 PDF'; mLines.forEach((l) => self.lineOff(l)); }); 315 /* --- Phase 4 : extraction / analyse --- */ 316 if (t > 10800 && t < 15800) { 317 once('p4', () => { setStep(3); if (fiche) fiche.style.opacity = '1'; }); 318 const y = -50 + (geom.h + 70) * ((t - 10800) / 5000); 319 beam.style.transform = 'translateY(' + (y + 60) + 'px)'; beam.style.opacity = '1'; 320 for (let i = 0; i < fields.length; i++) { 321 const f = fields[i]; 322 if (!fired.has(f) && geom.tops[i] < y + 22) { fired.add(f); fieldExtract(f); } 323 } 324 } else if (t >= 15800) { 325 beam.style.opacity = '0'; 326 if (t > 16600) once('valid', () => self.stampOn(stamp, -4)); 327 } 328 requestAnimationFrame(loop); 329 } 330 reset(); 331 requestAnimationFrame(loop); 332 }, 333 334 startPipe: function() { 335 const gen = this.gen, self = this; 336 const pulse = this.q('[data-pulse]'), steps = this.qa('[data-dot]'); 337 if (!pulse || !steps.length) return; 338 pulse.style.opacity = '1'; 339 pulse.style.willChange = 'transform'; 340 const host = pulse.parentElement; 341 let W = host.clientWidth; 342 const onRz = () => { W = host.clientWidth; }; 343 addEventListener('resize', onRz); 344 this.cleanup.push(() => removeEventListener('resize', onRz)); 345 const D = 7000 / this.sp(); 346 let start = null; 347 function loop(now) { 348 if (gen !== self.gen) return; 349 if (start == null) start = now; 350 const u = ((now - start) % D) / D; 351 pulse.style.transform = 'translateX(' + (u * 0.92 * W) + 'px)'; 352 steps.forEach((s, i) => { 353 const su = (i + 0.5) / steps.length; 354 self.litDot(s, u > su - 0.5 / steps.length); 355 }); 356 requestAnimationFrame(loop); 357 } 358 requestAnimationFrame(loop); 359 }, 360 361 askPairs: function() { 362 const P = (t) => '<p style="font-size:15.5px;color:rgba(244,242,255,.82);margin:0 0 16px">' + t + '</p>'; 363 const B = (t) => '<b style="color:#81EBC5;font-weight:600">' + t + '</b>'; 364 const S = (t) => '<span style="display:inline-flex;align-items:center;gap:8px;font-family:Montserrat,sans-serif;font-size:12px;color:#BEB5F6;border:1px solid rgba(190,181,246,.5);padding:6px 11px;margin:0 8px 8px 0"><i style="color:#81EBC5;font-style:normal">\u00a7</i>' + t + '</span>'; 365 return [ 366 { q: 'Quels contrats ont une indexation \u00e0 appliquer ce trimestre ?', 367 a: P(B('3 contrats concern\u00e9s') + " : le bail Rivoli, le contrat cadre Nexa et l'avenant n\u00b03 Meridian. Indexation \u00e0 appliquer avant le 30 septembre.") + S('Bail Rivoli \u00b7 art. 4.2') + S('Contrat cadre Nexa \u00b7 art. 7.1') + S('Avenant n\u00b03 \u00b7 annexe B') }, 368 { q: 'Quelles \u00e9ch\u00e9ances arrivent dans les 90 proch
368ains jours ?', 369 a: P(B('5 \u00e9ch\u00e9ances identifi\u00e9es') + ' : 2 renouvellements tacites, 2 pr\u00e9avis \u00e0 notifier, 1 garantie bancaire \u00e0 renouveler. La plus proche est \u00e0 J-42.') + S('Bail commercial \u00b7 art. 3.4') + S('Pr\u00eat bancaire \u00b7 clause 9') }, 370 { q: 'Quels engagements de plafonnement de loyer avons-nous sign\u00e9s ?', 371 a: P(B('2 clauses de plafonnement actives') + ', toutes deux limit\u00e9es \u00e0 la variation de l\u2019indice avec un cap de 3,5% par an.') + S('Bail Rivoli \u00b7 art. 4.3') + S('Bail Haussmann \u00b7 art. 5.1') } 372 ]; 373 }, 374 375 startAsk: function() { 376 const gen = this.gen, self = this; 377 const qEl = this.q('[data-asktext]'), ans = this.q('[data-askanswer]'), caret = this.q('[data-caret]'); 378 if (!qEl) return; 379 caret.style.display = 'inline-block'; 380 const pairs = this.askPairs(); 381 const sp = this.sp(); 382 const CPS = 26 * sp; 383 let pi = 0; 384 function cycle() { 385 if (gen !== self.gen) return; 386 const p = pairs[pi]; pi = (pi + 1) % pairs.length; 387 qEl.textContent = ''; ans.style.opacity = '0'; 388 const t0 = performance.now(); 389 let shown = -1; 390 function frame(now) { 391 if (gen !== self.gen) return; 392 const n = Math.min(p.q.length, Math.floor((now - t0) / 1000 * CPS)); 393 if (n !== shown) { shown = n; qEl.textContent = p.q.slice(0, n); } 394 if (n < p.q.length) { requestAnimationFrame(frame); return; } 395 self.after(500, () => { ans.innerHTML = p.a; ans.style.opacity = '1'; }); 396 self.after(6500 / sp, cycle); 397 } 398 requestAnimationFrame(frame); 399 } 400 cycle(); 401 }, 402 403 setupReveals: function() { 404 if (matchMedia('(prefers-reduced-motion: reduce)').matches) return; 405 const els = this.qa('[data-reveal]'); 406 if (!('IntersectionObserver' in window)) { els.forEach((el) => { el.style.opacity = '1'; el.style.transform = 'none'; }); return; } 407 const groups = new Map(); 408 els.forEach((el) => { 409 const p = el.parentElement; 410 if (!groups.has(p)) groups.set(p, []); 411 groups.get(p).push(el); 412 }); 413 const io = new IntersectionObserver((es) => { 414 es.forEach((e) => { 415 if (e.isIntersecting) { e.target.style.opacity = '1'; e.target.style.transform = 'none'; io.unobserve(e.target); } 416 }); 417 }, { threshold: .12 }); 418 els.forEach((el) => { 419 const base = +(el.dataset.reveal || 0); 420 const sibs = groups.get(el.parentElement); 421 const i = Math.max(0, sibs.indexOf(el)); 422 const delay = (base * 0.1 + i * 0.08).toFixed(2); 423 el.style.opacity = '0'; 424 el.style.transform = 'translateY(30px)'; 425 el.style.transition = 'opacity .85s cubic-bezier(.2,.7,.3,1) ' + delay + 's,transform .85s cubic-bezier(.2,.7,.3,1) ' + delay + 's'; 426 io.observe(el); 427 }); 428 this.cleanup.push(() => io.disconnect()); 429 }, 430 431 setupClauses: function() { 432 const els = this.qa('[data-uline]'); 433 if (matchMedia('(prefers-reduced-motion: reduce)').matches || !('IntersectionObserver' in window)) { els.forEach((e) => e.classList.add('is-drawn')); return; } 434 const io = new IntersectionObserver((es) => { 435 es.forEach((e) => { if (e.isIntersecting) { e.target.classList.add('is-drawn'); io.unobserve(e.target); } }); 436 }, { threshold: .6 }); 437 els.forEach((e) => io.observe(e)); 438 this.cleanup.push(() => io.disconnect()); 439 }, 440 441 setupCounters: function() { 442 const els = this.qa('[data-count]'); 443 if (matchMedia('(prefers-reduced-motion: reduce)').matches || !('IntersectionObserver' in window)) { 444 els.forEach((el) => { el.textContent = el.dataset.count; }); 445 return; 446 } 447 const io = new IntersectionObserver((es) => { 448 es.forEach((e) => { 449 if (!e.isIntersecting) return; 450 io.unobserve(e.target); 451 const el = e.target, end = +el.dataset.count, t0 = performance.now(), D = 1400; 452 const up = (now) => { 453 const u = Math.min((now - t0) / D, 1); 454 el.textContent = Math.round(end * (1 - Math.pow(1 - u, 3))); 455 if (u < 1) requestAnimationFrame(up); 456 }; 457 requestAnimationFrame(up); 458 }); 459 }, { threshold: .5 });
460 els.forEach((el) => { el.textContent = '0'; io.observe(el); }); 461 this.cleanup.push(() => io.disconnect()); 462 }, 463 464 setupUseCases: function() { 465 const items = this.qa('[data-uc]'); 466 items.forEach((item, idx) => { 467 const btn = item.querySelector('[data-uc-q]'), a = item.querySelector('[data-uc-a]'), x = item.querySelector('[data-uc-x]'); 468 const set = (open) => { 469 item.dataset.open = open ? '1' : ''; 470 a.style.maxHeight = open ? (a.scrollHeight + 24) + 'px' : '0px'; 471 x.style.transform = 'rotate(' + (open ? 45 : 0) + 'deg)'; 472 item.style.borderColor = open ? 'rgba(129,235,197,.55)' : 'rgba(190,181,246,.5)'; 473 }; 474 item._ucSet = set; 475 item.addEventListener('mouseenter', () => { items.forEach((it) => it._ucSet(it === item)); }); 476 item.addEventListener('mouseleave', () => set(false)); 477 btn.addEventListener('click', () => { 478 const willOpen = !item.dataset.open; 479 items.forEach((it) => it._ucSet(it === item ? willOpen : false)); 480 }); 481 set(false); 482 }); 483 }, 484 485 setupFaq: function() { 486 this.qa('[data-faq]').forEach((item, idx) => { 487 const btn = item.querySelector('[data-faq-q]'), a = item.querySelector('[data-faq-a]'), x = item.querySelector('[data-faq-x]'); 488 const set = (open) => { 489 item.dataset.open = open ? '1' : ''; 490 a.style.maxHeight = open ? (a.scrollHeight + 24) + 'px' : '0px'; 491 x.style.transform = 'translateY(-50%) rotate(' + (open ? 45 : 0) + 'deg)'; 492 }; 493 btn.addEventListener('click', () => set(!item.dataset.open)); 494 set(idx === 0); 495 }); 496 } 497 }; 498 499 function boot() { 500 try { C.componentDidMount(); } 501 catch (err) { 502 /* filet de sécurité : jamais de contenu invisible si un script échoue */ 503 Array.prototype.forEach.call(document.querySelectorAll('[data-reveal]'), function (el) { 504 el.style.opacity = '1'; el.style.transform = 'none'; 505 }); 506 if (window.console && console.error) console.error(err); 507 } 508 } 509 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot); 510 else boot(); 511 window.FormalitySite = C; 512})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.