(function () { 'use strict'; var BLUE = [22, 107, 199]; var BLUE_LIGHT = [82, 157, 222]; var GREEN = [105, 181, 62]; var DOMAIN_ORDER = ['food', 'beauty', 'medical', 'specialty']; var DOMAIN_ALIASES = { food: 'food', 'food-beverage': 'food', beauty: 'beauty', 'beauty-care': 'beauty', medical: 'medical', 'medical-outer': 'medical', specialty: 'specialty', 'special-performance': 'specialty' }; function rgba(color, alpha) { return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + alpha + ')'; } function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } function randomBetween(min, max) { return min + Math.random() * (max - min); } function toDomainIndex(value) { var normalized = DOMAIN_ALIASES[value] || value; var index = DOMAIN_ORDER.indexOf(normalized); return index < 0 ? 0 : index; } function anchorLayout(mode) { if (mode === 'hero') { return [ [.58, .20], [.85, .18], [.70, .47], [.90, .70], [.58, .78] ]; } if (mode === 'domains') { return [ [.13, .58], [.38, .58], [.62, .58], [.87, .58], [.50, .22] ]; } if (mode === 'workflow') { return [ [.08, .62], [.29, .51], [.50, .60], [.71, .49], [.92, .58] ]; } if (mode === 'detail') { return [ [.58, .22], [.82, .18], [.67, .51], [.90, .69], [.60, .80] ]; } return [ [.12, .24], [.42, .15], [.73, .28], [.88, .69], [.53, .77], [.22, .68] ]; } function edgeLayout(mode) { if (mode === 'domains') return [[4, 0], [4, 1], [4, 2], [4, 3], [0, 1], [1, 2], [2, 3]]; if (mode === 'workflow') return [[0, 1], [1, 2], [2, 3], [3, 4]]; if (mode === 'portal') return [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 0], [1, 4]]; return [[0, 1], [0, 2], [2, 1], [2, 3], [2, 4], [3, 4]]; } function modeParticleCount(mode, width) { var base = mode === 'hero' ? 58 : mode === 'workflow' ? 44 : mode === 'domains' ? 42 : 38; if (width < 760) base = Math.round(base * .58); return base; } function ParticleField(canvas, mode, options) { this.canvas = canvas; this.ctx = canvas.getContext('2d', { alpha: true }); this.host = canvas.parentElement; this.mode = mode; this.motionAllowed = options.motionAllowed; this.reducedData = options.reducedData; this.dpr = 1; this.width = 0; this.height = 0; this.anchors = []; this.edges = edgeLayout(mode); this.particles = []; this.pointer = { x: 0, y: 0, active: false }; this.active = true; this.rafId = 0; this.lastTime = 0; this.startTime = performance.now(); this.domainIndex = this.readDomainIndex(); this.boundFrame = this.frame.bind(this); this.resizeObserver = null; this.visibilityObserver = null; this.domainObserver = null; this.init(); } ParticleField.prototype.readDomainIndex = function () { if (this.mode === 'domains') { return toDomainIndex(this.host.getAttribute('data-active-domain') || 'food'); } return toDomainIndex(document.body.getAttribute('data-domain') || 'food'); }; ParticleField.prototype.init = function () { var self = this; if (!this.ctx) return; this.resize(); if ('ResizeObserver' in window) { this.resizeObserver = new ResizeObserver(function () { self.resize(); }); this.resizeObserver.observe(this.host); } else { window.addEventListener('resize', function () { self.resize(); }, { passive: true }); } if ('IntersectionObserver' in window) { this.visibilityObserver = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { self.active = entry.isIntersecting; if (self.active) self.start(); else self.stop(); }); }, { rootMargin: '160px 0px', threshold: 0 }); this.visibilityObserver.observe(this.host); } if (this.mode === 'domains' && 'MutationObserver' in window) { this.domainObserver = new MutationObserver(function () { self.domainIndex = self.readDomainIndex(); if (!self.motionAllowed) self.draw(performance.now()); }); this.domainObserver.observe(this.host, { attributes: true, attributeFilter: ['data-active-domain'] }); } if (this.motionAllowed && !window.matchMedia('(pointer: coarse)').matches) { this.host.addEventListener('pointermove', function (event) { var rect = self.host.getBoundingClientRect(); self.pointer.x = event.clientX - rect.left; self.pointer.y = event.clientY - rect.top; self.pointer.active = true; }, { passive: true }); this.host.addEventListener('pointerleave', function () { self.pointer.active = false; }, { passive: true }); } if (this.motionAllowed) this.start(); else this.draw(performance.now()); }; ParticleField.prototype.resize = function () { var rect = this.host.getBoundingClientRect(); var width = Math.max(1, Math.round(rect.width)); var height = Math.max(1, Math.round(rect.height)); var nextDpr = Math.min(window.devicePixelRatio || 1, this.reducedData ? 1 : 1.5); if (width === this.width && height === this.height && nextDpr === this.dpr) return; this.width = width; this.height = height; this.dpr = nextDpr; this.canvas.width = Math.round(width * nextDpr); this.canvas.height = Math.round(height * nextDpr); this.canvas.style.width = width + 'px'; this.canvas.style.height = height + 'px'; this.ctx.setTransform(nextDpr, 0, 0, nextDpr, 0, 0); this.buildScene(); this.draw(performance.now()); }; ParticleField.prototype.buildScene = function () { var self = this; var normalizedAnchors = anchorLayout(this.mode); this.anchors = normalizedAnchors.map(function (point) { return { x: point[0] * self.width, y: point[1] * self.height }; }); var count = modeParticleCount(this.mode, this.width); if (this.reducedData) count = Math.round(count * .55); this.particles = []; for (var i = 0; i < count; i += 1) { var anchorIndex = i % this.anchors.length; var radiusLimit = this.mode === 'workflow' ? 54 : Math.min(this.width, this.height) * .12; this.particles.push({ anchorIndex: anchorIndex, radiusX: randomBetween(14, Math.max(30, radiusLimit)), radiusY: randomBetween(10, Math.max(24, radiusLimit * .72)), angle: randomBetween(0, Math.PI * 2), speed: randomBetween(.000055, .00012) * (Math.random() > .5 ? 1 : -1), phase: randomBetween(0, Math.PI * 2), size: randomBetween(.9, 2.25), color: i % 7 === 0 ? GREEN : i % 3 === 0 ? BLUE_LIGHT : BLUE, x: 0, y: 0 }); } }; ParticleField.prototype.start = function () { if (!this.motionAllowed || !this.active || this.rafId || document.hidden) return; this.rafId = requestAnimationFrame(this.boundFrame); }; ParticleField.prototype.stop = function () { if (!this.rafId) return; cancelAnimationFrame(this.rafId); this.rafId = 0; }; ParticleField.prototype.frame = function (time) { this.rafId = 0; if (!this.active || document.hidden) return; if (!this.lastTime || time - this.lastTime > 16) { this.lastTime = time; this.draw(time); } this.rafId = requestAnimationFrame(this.boundFrame); }; ParticleField.prototype.isHighlightedEdge = function (edge) { if (this.mode !== 'domains') return false; return edge[0] === this.domainIndex || edge[1] === this.domainIndex; }; ParticleField.prototype.drawEdges = function (time) { var ctx = this.ctx; var self = this; this.edges.forEach(function (edge, edgeIndex) { var from = self.anchors[edge[0]]; var to = self.anchors[edge[1]]; if (!from || !to) return; var highlighted = self.isHighlightedEdge(edge); var gradient = ctx.createLinearGradient(from.x, from.y, to.x, to.y); gradient.addColorStop(0, rgba(BLUE, highlighted ? .34 : .13)); gradient.addColorStop(1, rgba(GREEN, highlighted ? .38 : .15)); ctx.beginPath(); ctx.moveTo(from.x, from.y); ctx.lineTo(to.x, to.y); ctx.strokeStyle = gradient; ctx.lineWidth = highlighted ? 1.5 : 1; ctx.stroke(); var flowCount = self.mode === 'workflow' ? 3 : 2; for (var i = 0; i < flowCount; i += 1) { var speed = self.motionAllowed ? .000055 : 0; var progress = self.motionAllowed ? ((time * speed + edgeIndex * .17 + i / flowCount) % 1) : (i + 1) / (flowCount + 1); var x = from.x + (to.x - from.x) * progress; var y = from.y + (to.y - from.y) * progress; ctx.beginPath(); ctx.arc(x, y, highlighted ? 2.25 : 1.65, 0, Math.PI * 2); ctx.fillStyle = rgba(i % 2 ? GREEN : BLUE, highlighted ? .82 : .48); ctx.fill(); } }); }; ParticleField.prototype.updateParticles = function (time) { var self = this; this.particles.forEach(function (particle) { var anchor = self.anchors[particle.anchorIndex]; var motionTime = self.motionAllowed ? time : 0; var angle = particle.angle + motionTime * particle.speed; var wave = Math.sin(motionTime * .00042 + particle.phase) * 5; particle.x = anchor.x + Math.cos(angle) * (particle.radiusX + wave); particle.y = anchor.y + Math.sin(angle * 1.13) * particle.radiusY; if (self.pointer.active) { var dx = particle.x - self.pointer.x; var dy = particle.y - self.pointer.y; var distance = Math.sqrt(dx * dx + dy * dy) || 1; if (distance < 110) { var force = (1 - distance / 110) * 14; particle.x += dx / distance * force; particle.y += dy / distance * force; } } }); }; ParticleField.prototype.drawParticleLinks = function () { var ctx = this.ctx; var maxDistance = this.width < 760 ? 82 : 108; var maxDistanceSquared = maxDistance * maxDistance; for (var i = 0; i < this.particles.length; i += 1) { var a = this.particles[i]; for (var j = i + 1; j < this.particles.length; j += 1) { var b = this.particles[j]; if (a.anchorIndex !== b.anchorIndex && Math.abs(a.anchorIndex - b.anchorIndex) > 1) continue; var dx = a.x - b.x; var dy = a.y - b.y; var distanceSquared = dx * dx + dy * dy; if (distanceSquared > maxDistanceSquared) continue; var alpha = (1 - distanceSquared / maxDistanceSquared) * .11; ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.strokeStyle = rgba(BLUE, alpha); ctx.lineWidth = .8; ctx.stroke(); } } }; ParticleField.prototype.drawAnchors = function (time) { var self = this; this.anchors.forEach(function (anchor, index) { var active = self.mode === 'domains' && index === self.domainIndex; var pulse = self.motionAllowed ? Math.sin(time * .002 + index) * .5 + .5 : .4; ctxCircle(self.ctx, anchor.x, anchor.y, active ? 9 + pulse * 2 : 6 + pulse, rgba(active ? GREEN : BLUE, active ? .12 : .065)); ctxCircle(self.ctx, anchor.x, anchor.y, active ? 3.2 : 2.4, rgba(active ? GREEN : BLUE, active ? .9 : .62)); }); }; function ctxCircle(ctx, x, y, radius, fillStyle) { ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); ctx.fillStyle = fillStyle; ctx.fill(); } ParticleField.prototype.draw = function (time) { if (!this.ctx || !this.width || !this.height) return; this.ctx.clearRect(0, 0, this.width, this.height); this.drawEdges(time); this.updateParticles(time); this.drawParticleLinks(); for (var i = 0; i < this.particles.length; i += 1) { var particle = this.particles[i]; ctxCircle(this.ctx, particle.x, particle.y, particle.size, rgba(particle.color, particle.color === GREEN ? .72 : .56)); } this.drawAnchors(time); }; function addParticleCanvas(host, mode, modifier, options) { if (!host || host.querySelector(':scope > [data-dv22-particles]')) return null; var canvas = document.createElement('canvas'); canvas.className = 'dv22-particle-field ' + modifier; canvas.setAttribute('data-dv22-particles', mode); canvas.setAttribute('aria-hidden', 'true'); host.insertBefore(canvas, host.firstChild); return new ParticleField(canvas, mode, options); } function initParticles() { var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; var reducedData = Boolean(connection && connection.saveData); var options = { motionAllowed: !reducedMotion && !reducedData, reducedData: reducedData }; var fields = []; fields.push(addParticleCanvas(document.querySelector('.dv21-hero'), 'hero', 'dv22-particle-field--hero', options)); fields.push(addParticleCanvas(document.querySelector('.dv21-domain-explorer'), 'domains', 'dv22-particle-field--domains', options)); fields.push(addParticleCanvas(document.querySelector('.dv21-progressive'), 'workflow', 'dv22-particle-field--workflow', options)); fields.push(addParticleCanvas(document.querySelector('.dv21-domain-hero'), 'detail', 'dv22-particle-field--detail', options)); fields.push(addParticleCanvas(document.querySelector('.dv22-challenges-page .dv2-page-intro'), 'portal', 'dv22-particle-field--detail', options)); fields = fields.filter(Boolean); document.body.setAttribute('data-dv22-particle-fields', String(fields.length)); document.addEventListener('visibilitychange', function () { fields.forEach(function (field) { if (document.hidden) field.stop(); else field.start(); }); }); } function initSectionReveals() { if (window.matchMedia('(prefers-reduced-motion: reduce)').matches || !('IntersectionObserver' in window)) return; var selectors = [ 'main > section:not(:first-child) .dv2-section__head', 'main > section:not(:first-child) .dv21-section-intro', '.dv21-domain-panels', '.dv21-domain-hero__layout', '.dv2-page-intro__layout' ]; var targets = Array.prototype.slice.call(document.querySelectorAll(selectors.join(','))); if (!targets.length) return; targets.forEach(function (target) { target.classList.add('dv22-reveal'); }); document.body.classList.add('dv22-motion-ready'); var observer = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (!entry.isIntersecting) return; entry.target.classList.add('is-visible'); observer.unobserve(entry.target); }); }, { rootMargin: '0px 0px -8% 0px', threshold: .1 }); requestAnimationFrame(function () { targets.forEach(function (target) { var rect = target.getBoundingClientRect(); if (rect.top < window.innerHeight * .9) target.classList.add('is-visible'); else observer.observe(target); }); }); } function init() { if (!document.body.classList.contains('dv22-page')) return; initParticles(); initSectionReveals(); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init(); }());