www/static/js/trr379-home.js

641 lines
26 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

gsap.registerPlugin(ScrollTrigger);
/* ─────────────────────────────────────────
UTILITIES
───────────────────────────────────────── */
/* ── Dark mode: invert near-black strokes/fills to light grey ──
Scans every SVG element after load. Elements whose computed stroke
or fill is "dark" (luminance < 0.25) get stored originals and a
light replacement. Runs again whenever Congo toggles .dark on <html>.
── */
const DARK_STROKE = '#d4d4d4';
const DARK_FILL = '#c8c8c8';
function luminance(r, g, b) {
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
}
function parseRgb(str) {
const m = str && str.match(/rgba?\((\d+)[, ]+(\d+)[, ]+(\d+)/);
return m ? [+m[1], +m[2], +m[3]] : null;
}
function adaptSvg(svgEl) {
const dark = document.documentElement.classList.contains('dark');
svgEl.querySelectorAll('path, line, rect, circle, ellipse, polyline, polygon').forEach(el => {
const cs = window.getComputedStyle(el);
// stroke
const sc = parseRgb(cs.stroke);
if (sc && luminance(...sc) < 0.25) {
if (!el.dataset.origStrokeColor) el.dataset.origStrokeColor = cs.stroke;
el.style.stroke = dark ? DARK_STROKE : el.dataset.origStrokeColor;
} else if (el.dataset.origStrokeColor && !dark) {
el.style.stroke = el.dataset.origStrokeColor;
}
// fill (non-transparent only)
if (cs.fill !== 'none' && cs.fill !== 'rgba(0, 0, 0, 0)') {
const fc = parseRgb(cs.fill);
if (fc && luminance(...fc) < 0.25) {
if (!el.dataset.origFillColor) el.dataset.origFillColor = cs.fill;
el.style.fill = dark ? DARK_FILL : el.dataset.origFillColor;
} else if (el.dataset.origFillColor && !dark) {
el.style.fill = el.dataset.origFillColor;
}
}
});
}
// Registry of all loaded SVGs re-adapt when dark mode toggles
const _allSvgEls = [];
new MutationObserver(() => _allSvgEls.forEach(svg => adaptSvg(svg)))
.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
function registerAndAdapt(svgEl) {
_allSvgEls.push(svgEl);
adaptSvg(svgEl);
}
function prepareDraw(svg) {
const els = svg.querySelectorAll('path, line, rect, circle, polyline, polygon, ellipse');
els.forEach(el => {
let len; try { len = el.getTotalLength(); } catch(e) { len = 1000; }
el.style.strokeDasharray = len;
el.style.strokeDashoffset = len;
const fill = getComputedStyle(el).fill;
if (fill && fill !== 'none' && fill !== 'rgba(0, 0, 0, 0)') { el.style.fillOpacity = 0; el.dataset.hasFill = '1'; }
});
return els;
}
function prepareDrawSubset(els) {
els.forEach(el => {
let len; try { len = el.getTotalLength(); } catch(e) { len = 1000; }
el.style.strokeDasharray = len;
el.style.strokeDashoffset = len;
const fill = getComputedStyle(el).fill;
if (fill && fill !== 'none' && fill !== 'rgba(0, 0, 0, 0)') { el.style.fillOpacity = 0; el.dataset.hasFill = '1'; }
});
}
function svgToScreen(svgEl, vbW, vbH, box) {
const r = svgEl.getBoundingClientRect();
return {
left: r.left + box.x * (r.width / vbW),
top: r.top + box.y * (r.height / vbH),
width: box.w * (r.width / vbW),
height: box.h * (r.height / vbH)
};
}
/* ─────────────────────────────────────────
STATE
───────────────────────────────────────── */
let heroSvg = null;
let heroCirclesGroup = null;
let fig2GreenGroup = null;
let fig2OrangeGroup = null;
let f2TextEls = [];
let fig2Flyer = null;
let fig2FlyerSrc = null;
let arrowClipRect = null, arrowClipH = 0;
let fig4DrawEls = [], fig4DrawDone = false;
let _createArrowTrigger = null;
let _createFig5Trigger = null;
const F4_VB = { w: 864.15, h: 479.55 };
const F4_GREEN = { x: 1, y: 123, w: 54, h: 52 };
const F4_ORANGE = { x: 1, y: 187, w: 54, h: 52 };
const F4_RED = { x: 773, y: 122, w: 90, h: 104 };
const F4_F2AREA = { x: 1, y: 100, w: 340, h: 160 };
/* ═══════════════════════════════════════
1) HERO Figure1 draw
═══════════════════════════════════════ */
gsap.set('#heroText', { opacity: 0, y: 40 });
fetch('/Figure1.svg').then(r => r.text()).then(svgText => {
const cnt = document.getElementById('heroFigure');
cnt.innerHTML = svgText;
heroSvg = cnt.querySelector('svg');
heroSvg.style.cssText = 'width:100%;height:auto;overflow:visible';
registerAndAdapt(heroSvg);
const labGroup = heroSvg.querySelector('[data-name="Ebene 2"]');
heroCirclesGroup = heroSvg.querySelector('[data-name="Ebene 3"]');
const labEls = labGroup ? labGroup.querySelectorAll('path, line, rect, circle') : [];
const circleEls = heroCirclesGroup ? heroCirclesGroup.querySelectorAll('path') : [];
[...labEls, ...circleEls].forEach(el => {
let len; try { len = el.getTotalLength(); } catch(e) { len = 1000; }
el.style.strokeDasharray = len; el.style.strokeDashoffset = len;
const fill = getComputedStyle(el).fill;
if (fill && fill !== 'none' && fill !== 'rgba(0, 0, 0, 0)') { el.style.fillOpacity = 0; el.dataset.hasFill = '1'; }
});
circleEls.forEach(el => {
try {
const bb = el.getBBox(), cx = bb.x + bb.width/2, cy = bb.y + bb.height/2;
if (cx > 850 && cy < 200) { el.dataset.flyAway = '1'; el.dataset.flyColor = 'green'; }
else if (cx > 690 && cx < 850 && cy > 100 && cy < 250) { el.dataset.flyAway = '1'; el.dataset.flyColor = 'orange'; }
} catch(e) {}
});
const tl = gsap.timeline({ delay: 0.3 });
tl.to('#heroText', { opacity: 1, y: 0, duration: 0.9, ease: 'power3.out' }, 0);
tl.to(labEls, { strokeDashoffset: 0, duration: 3, stagger: { amount: 2.5, from: 'start' }, ease: 'power1.inOut' }, 0.2);
tl.to(circleEls, { strokeDashoffset: 0, duration: 2, stagger: { amount: 1.0, from: 'start' }, ease: 'power1.inOut' }, 1.5);
const cf = Array.from(circleEls).filter(e => e.dataset.hasFill === '1');
if (cf.length) tl.to(cf, { fillOpacity: 1, duration: 0.8, stagger: 0.1, ease: 'power1.inOut' }, '-=0.5');
const onEarlyScroll = () => { if (tl.progress() < 1) tl.progress(1, false); window.removeEventListener('scroll', onEarlyScroll); };
window.addEventListener('scroll', onEarlyScroll, { passive: true });
tl.call(() => {
[...labEls, ...circleEls].forEach(el => { el.style.strokeDashoffset = '0'; });
gsap.to('.hero-figure', { y: -80, scrollTrigger: { trigger: '.hero', start: 'top top', end: 'bottom top', scrub: true } });
gsap.to('#heroText', { opacity: 0, y: -40, scrollTrigger: { trigger: '.hero', start: '30% top', end: '80% top', scrub: 1 } });
initCircleFadeIn();
});
});
/* ═══════════════════════════════════════
2) FLYING CIRCLES: Figure1 → Figure2
═══════════════════════════════════════ */
function initCircleFadeIn() {
const poll = () => {
const f2svg = document.querySelector('#fig2Wrap svg');
if (f2svg && heroSvg && heroCirclesGroup) {
fig2GreenGroup = f2svg.querySelector('[data-circle-group="green"]');
fig2OrangeGroup = f2svg.querySelector('[data-circle-group="orange"]');
buildFlyingCircles(f2svg);
} else requestAnimationFrame(poll);
};
poll();
}
function buildFlyingCircles(f2svg) {
const ns = 'http://www.w3.org/2000/svg';
const overlay = document.createElementNS(ns, 'svg');
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:60;pointer-events:none;overflow:visible;display:none;';
document.body.appendChild(overlay);
const wrapG = document.createElementNS(ns, 'g');
const wrapO = document.createElementNS(ns, 'g');
overlay.appendChild(wrapG);
overlay.appendChild(wrapO);
const greenEls = heroSvg.querySelectorAll('[data-fly-color="green"]');
const orangeEls = heroSvg.querySelectorAll('[data-fly-color="orange"]');
function populateWrap(wrapGroup, els) {
wrapGroup.innerHTML = '';
els.forEach(el => {
let bb, sr;
try { bb = el.getBBox(); sr = el.getBoundingClientRect(); } catch(e) { return; }
if (!bb || !sr || bb.width === 0 || sr.width === 0) return;
const sx = sr.width / bb.width;
const sy = sr.height / bb.height;
const tx = sr.left - bb.x * sx;
const ty = sr.top - bb.y * sy;
const clone = el.cloneNode(true);
clone.style.fillOpacity = '1';
clone.style.opacity = '1';
clone.removeAttribute('data-fly-color');
clone.removeAttribute('data-fly-away');
const g = document.createElementNS(ns, 'g');
g.setAttribute('transform', `matrix(${sx},0,0,${sy},${tx},${ty})`);
g.appendChild(clone);
wrapGroup.appendChild(g);
});
}
function screenRect(els) {
let l=Infinity, t=Infinity, r=-Infinity, b=-Infinity;
els.forEach(el => {
try {
const rect = el.getBoundingClientRect();
l=Math.min(l,rect.left); t=Math.min(t,rect.top);
r=Math.max(r,rect.right); b=Math.max(b,rect.bottom);
} catch(e) {}
});
return { left:l, top:t, width:r-l, height:b-t };
}
const F2 = { w:500.49, h:653.39 };
const F2G = { x:1, y:222, w:96, h:93 };
const F2O = { x:1, y:338, w:96, h:94 };
const lerp = gsap.utils.interpolate;
let ep = 0;
function applyTransform(wrap, src, tgt) {
if (!src || !isFinite(src.left)) return;
const sx = lerp(1, tgt.width / src.width, ep);
const sy = lerp(1, tgt.height / src.height, ep);
const tx = lerp(0, tgt.left - src.left * (tgt.width / src.width), ep);
const ty = lerp(0, tgt.top - src.top * (tgt.height / src.height), ep);
wrap.setAttribute('transform', `translate(${tx},${ty}) scale(${sx},${sy})`);
}
ScrollTrigger.create({
trigger: '#middleWrapper', start: 'top 100%', end: 'top 30%', scrub: 0.4,
onUpdate: (self) => {
const p = self.progress;
const flyEls = heroSvg ? heroSvg.querySelectorAll('[data-fly-away="1"]') : [];
if (p <= 0) {
overlay.style.display = 'none';
flyEls.forEach(el => el.style.opacity = '1');
if (fig2GreenGroup) fig2GreenGroup.style.opacity = 0;
if (fig2OrangeGroup) fig2OrangeGroup.style.opacity = 0;
return;
}
const srcG = screenRect(greenEls);
const srcO = screenRect(orangeEls);
populateWrap(wrapG, greenEls);
populateWrap(wrapO, orangeEls);
overlay.style.display = 'block';
flyEls.forEach(el => el.style.opacity = '0');
const tgtG = svgToScreen(f2svg, F2.w, F2.h, F2G);
const tgtO = svgToScreen(f2svg, F2.w, F2.h, F2O);
ep = gsap.parseEase('power2.inOut')(p);
applyTransform(wrapG, srcG, tgtG);
applyTransform(wrapO, srcO, tgtO);
const flyOp = p < 0.08 ? p/0.08 : p > 0.80 ? (1-p)/0.20 : 1;
overlay.style.opacity = flyOp;
const f2o = p > 0.88 ? Math.min(1,(p-0.88)/0.12) : 0;
if (fig2GreenGroup) fig2GreenGroup.style.opacity = f2o;
if (fig2OrangeGroup) fig2OrangeGroup.style.opacity = f2o;
if (heroCirclesGroup) heroCirclesGroup.style.opacity = 1 - p * 0.7;
},
onLeaveBack: () => {
overlay.style.display = 'none';
wrapG.innerHTML = ''; wrapO.innerHTML = '';
if (heroCirclesGroup) heroCirclesGroup.style.opacity = 1;
heroSvg?.querySelectorAll('[data-fly-away="1"]').forEach(el => el.style.opacity = '1');
if (fig2GreenGroup) fig2GreenGroup.style.opacity = 0;
if (fig2OrangeGroup) fig2OrangeGroup.style.opacity = 0;
},
onLeave: () => {
overlay.style.display = 'none';
wrapG.innerHTML = ''; wrapO.innerHTML = '';
if (fig2GreenGroup) fig2GreenGroup.style.opacity = 1;
if (fig2OrangeGroup) fig2OrangeGroup.style.opacity = 1;
}
});
}
/* ═══════════════════════════════════════
3) FIGURE 2 load, draw on scroll
═══════════════════════════════════════ */
fetch('/Figure2.svg').then(r => r.text()).then(svgText => {
const wrap = document.getElementById('fig2Wrap');
wrap.innerHTML = svgText.replace(/cls-/g, 'f2-');
const svg = wrap.querySelector('svg');
svg.style.width = '100%'; svg.style.height = 'auto';
registerAndAdapt(svg);
svg.querySelectorAll(':scope > g > g > g').forEach(g => {
g.querySelectorAll('path').forEach(p => {
const cls = p.getAttribute('class') || '';
if (cls.includes('f2-4')||cls.includes('f2-5')) { g.style.opacity='0'; g.dataset.circleGroup='green'; }
if (cls.includes('f2-7')||cls.includes('f2-6')) { g.style.opacity='0'; g.dataset.circleGroup='orange'; }
});
});
f2TextEls = Array.from(svg.querySelectorAll('text'));
const els = prepareDraw(svg);
ScrollTrigger.create({
trigger: '#rdocPanel', start: 'top 60%', once: true,
onEnter: () => {
const tl = gsap.timeline();
tl.to(els, { strokeDashoffset: 0, duration: 1.2, stagger: { amount: 0.6 }, ease: 'power1.inOut' }, 0);
const fills = Array.from(els).filter(e => e.dataset.hasFill==='1');
if (fills.length) tl.to(fills, { fillOpacity: 1, duration: 0.6, stagger: { amount: 0.2 }, ease: 'power1.inOut' }, '-=0.4');
}
});
_svgReady();
});
/* ═══════════════════════════════════════
4) FIGURE 4 load into fig4Wrap
═══════════════════════════════════════ */
function buildFig2Flyer() {
if (fig2Flyer) return;
const f2svg = document.querySelector('#fig2Wrap svg');
if (!f2svg) return;
const div = document.createElement('div');
div.id = 'fig2Flyer';
div.style.cssText = 'position:fixed;z-index:55;pointer-events:none;opacity:0;display:none;will-change:transform;overflow:visible;';
const ns = 'http://www.w3.org/2000/svg';
const s = document.createElementNS(ns, 'svg');
const vb = f2svg.viewBox.baseVal;
s.setAttribute('viewBox', `${vb.x} ${vb.y} ${vb.width} ${vb.height}`);
s.setAttribute('preserveAspectRatio', 'xMidYMid meet');
s.style.cssText = 'width:100%;height:100%;display:block;overflow:visible;';
Array.from(f2svg.children).forEach(child => {
const clone = child.cloneNode(true);
clone.querySelectorAll('text, tspan').forEach(t => t.remove());
clone.querySelectorAll('*').forEach(el => {
el.style.strokeDasharray = 'none';
el.style.strokeDashoffset = '0';
el.style.fillOpacity = '1';
el.style.opacity = '1';
});
s.appendChild(clone);
});
div.appendChild(s);
document.body.appendChild(div);
fig2Flyer = div;
}
fetch('/Figure4.svg').then(r => r.text()).then(svgText => {
const wrap = document.getElementById('fig4Wrap');
wrap.innerHTML = svgText.replace(/cls-/g, 'f4-');
const svg = wrap.querySelector('svg');
svg.style.cssText = 'width:100%;height:auto;overflow:visible';
registerAndAdapt(svg);
const arrowEls = Array.from(svg.querySelectorAll('path')).filter(p => {
const cls=p.getAttribute('class')||'', d=p.getAttribute('d')||'';
return cls.includes('f4-13') && d.startsWith('m602');
});
if (arrowEls.length) {
let x0=Infinity,y0=Infinity,x1=-Infinity,y1=-Infinity;
arrowEls.forEach(el => { try { const b=el.getBBox(); x0=Math.min(x0,b.x); y0=Math.min(y0,b.y); x1=Math.max(x1,b.x+b.width); y1=Math.max(y1,b.y+b.height); } catch(e){} });
const pad=4, cW=x1-x0+pad*2;
arrowClipH = y1-y0+pad*2;
const ns = 'http://www.w3.org/2000/svg';
const defs = svg.querySelector('defs') || svg.insertBefore(document.createElementNS(ns,'defs'), svg.firstChild);
const clip = document.createElementNS(ns,'clipPath'); clip.setAttribute('id','f4ArrowClip');
arrowClipRect = document.createElementNS(ns,'rect');
arrowClipRect.setAttribute('x',x0-pad); arrowClipRect.setAttribute('y',y0-pad);
arrowClipRect.setAttribute('width',cW); arrowClipRect.setAttribute('height',0);
clip.appendChild(arrowClipRect); defs.appendChild(clip);
const ag = document.createElementNS(ns,'g'); ag.setAttribute('clip-path','url(#f4ArrowClip)');
arrowEls[0].parentNode.insertBefore(ag, arrowEls[0]);
arrowEls.forEach(el => ag.appendChild(el));
}
const arrowSet = new Set(arrowEls);
fig4DrawEls = Array.from(svg.querySelectorAll('rect, circle, ellipse, path, line'))
.filter(el => !arrowSet.has(el));
prepareDrawSubset(fig4DrawEls);
const lerp = gsap.utils.interpolate;
ScrollTrigger.create({
trigger: '#multiPanel',
start: 'center 80%',
end: 'center 20%',
scrub: 0.6,
invalidateOnRefresh: true,
onUpdate: (self) => {
const p = self.progress;
const f2wrap = document.getElementById('fig2Wrap');
const f4svg = svg;
buildFig2Flyer();
if (!fig2FlyerSrc && fig2Flyer) {
const r = f2wrap.getBoundingClientRect();
fig2FlyerSrc = {
left: r.left, top: r.top,
width: r.width, height: r.height,
aspect: r.height / r.width
};
}
f2TextEls.forEach(el => el.style.opacity = '0');
f2wrap.style.opacity = 0;
if (fig2Flyer && fig2FlyerSrc) {
const src = fig2FlyerSrc;
const tgtArea = svgToScreen(f4svg, F4_VB.w, F4_VB.h, F4_F2AREA);
const tgtW = tgtArea.width;
const tgtH = tgtW * src.aspect;
const tgtL = tgtArea.left;
const tgtT = tgtArea.top + (tgtArea.height - tgtH) / 2;
const ep = gsap.parseEase('power2.inOut')(p);
const flyOp = p > 0.92 ? Math.max(0,(1-p)/0.08) : 1;
fig2Flyer.style.display = 'block';
fig2Flyer.style.opacity = flyOp;
fig2Flyer.style.left = lerp(src.left, tgtL, ep) + 'px';
fig2Flyer.style.top = lerp(src.top, tgtT, ep) + 'px';
fig2Flyer.style.width = lerp(src.width, tgtW, ep) + 'px';
fig2Flyer.style.height = lerp(src.height, tgtH, ep) + 'px';
}
wrap.style.opacity = p < 0.50 ? 0 : Math.min(1, (p - 0.50) / 0.40);
if (p > 0.52 && !fig4DrawDone) {
fig4DrawDone = true;
const tl = gsap.timeline();
tl.to(fig4DrawEls, { strokeDashoffset: 0, duration: 1.4, stagger: { amount: 0.8 }, ease: 'power1.inOut' }, 0);
const fills = fig4DrawEls.filter(e => e.dataset.hasFill==='1');
if (fills.length) tl.to(fills, { fillOpacity: 1, duration: 0.7, stagger: { amount: 0.3 }, ease: 'power1.inOut' }, '-=0.5');
}
},
onLeaveBack: () => {
const f2wrap = document.getElementById('fig2Wrap');
f2wrap.style.opacity = 1;
f2TextEls.forEach(el => el.style.opacity = '1');
if (fig2Flyer) { fig2Flyer.style.display = 'none'; fig2Flyer.style.opacity = 0; }
fig2FlyerSrc = null;
wrap.style.opacity = 0;
fig4DrawDone = false;
fig4DrawEls.forEach(el => {
let len; try { len = el.getTotalLength(); } catch(e) { len = 1000; }
el.style.strokeDasharray = len; el.style.strokeDashoffset = len;
if (el.dataset.hasFill==='1') el.style.fillOpacity = 0;
});
}
});
_createArrowTrigger = () => {
ScrollTrigger.create({
trigger: '#adolPanel',
start: 'center 65%',
end: 'center 50%',
scrub: 0.6,
onUpdate: (self) => {
if (arrowClipRect) arrowClipRect.setAttribute('height', self.progress * arrowClipH);
},
onLeaveBack: () => {
if (arrowClipRect) arrowClipRect.setAttribute('height', 0);
}
});
};
_svgReady();
});
/* ═══════════════════════════════════════
5) SCROLL PROGRESS BAR
═══════════════════════════════════════ */
window.addEventListener('scroll', () => {
const p = document.documentElement.scrollTop / (document.documentElement.scrollHeight - window.innerHeight) * 100;
document.getElementById('scrollProgress').style.width = p + '%';
}, { passive: true });
/* ═══════════════════════════════════════
6) FIGURE 5 Venn, circles fly from Figure4
═══════════════════════════════════════ */
fetch('/Figure5.svg').then(r => r.text()).then(svgText => {
const cnt = document.getElementById('vennFigure');
cnt.innerHTML = svgText.replace(/cls-/g, 'f5-');
const svg = cnt.querySelector('svg');
svg.style.width = '100%'; svg.style.height = 'auto';
registerAndAdapt(svg);
const greyCircles = svg.querySelectorAll('.f5-1');
const allGroups = svg.querySelectorAll('#Ebene_3 > g > g');
const f5O = allGroups[0]||null, f5R = allGroups[1]||null, f5G = allGroups[2]||null;
greyCircles.forEach(c => c.style.opacity = 0);
[f5O,f5R,f5G].filter(Boolean).forEach(g => g.style.opacity = 0);
const F5 = { w:432.94, h:462.7 };
const F5G = { x:206, y:3, w:225, h:222 };
const F5O = { x:2, y:123, w:226, h:222 };
const F5R = { x:206, y:237, w:225, h:224 };
function emptyFlyer(id) {
const d=document.createElement('div'); d.id=id;
d.style.cssText='position:fixed;z-index:60;pointer-events:none;opacity:0;display:none;will-change:transform;';
document.body.appendChild(d); return d;
}
function buildF4Flyer(div, f4svg, srcBox) {
if (div.querySelector('svg')) return;
const ns='http://www.w3.org/2000/svg', s=document.createElementNS(ns,'svg');
s.setAttribute('viewBox',`${srcBox.x} ${srcBox.y} ${srcBox.w} ${srcBox.h}`);
s.style.cssText='width:100%;height:100%;overflow:visible';
f4svg.querySelectorAll('path, circle, ellipse, rect').forEach(el => {
try {
const bb=el.getBBox(), cx=bb.x+bb.width/2, cy=bb.y+bb.height/2;
if (cx>=srcBox.x-2&&cx<=srcBox.x+srcBox.w+2&&cy>=srcBox.y-2&&cy<=srcBox.y+srcBox.h+2) {
const cs=parseFloat(window.getComputedStyle(el).getPropertyValue('stroke-width'))||2.5;
const c=el.cloneNode(true); c.dataset.origStroke=cs;
c.style.setProperty('stroke-width',cs.toString());
c.style.setProperty('stroke-dasharray','none');
c.style.setProperty('stroke-dashoffset','0');
c.style.setProperty('fill-opacity','1');
c.style.setProperty('opacity','1');
s.appendChild(c);
}
} catch(e) {}
});
div.appendChild(s);
}
const fG=emptyFlyer('fly5G'), fO=emptyFlyer('fly5O'), fR=emptyFlyer('fly5R');
let f4svg=null, f4SP=null, f5SI=false;
const lerp=gsap.utils.interpolate;
_createFig5Trigger = () => {
ScrollTrigger.create({
trigger: '#researchAreas', start: 'top 80%', end: 'bottom 80%', scrub: 0.4,
onUpdate: (self) => {
const p=self.progress;
if (!f4svg) f4svg=document.querySelector('#fig4Wrap svg');
if (p<=0||!f4svg) { [fG,fO,fR].forEach(f=>f.style.display='none'); f4SP=null; return; }
buildF4Flyer(fG,f4svg,F4_GREEN); buildF4Flyer(fO,f4svg,F4_ORANGE); buildF4Flyer(fR,f4svg,F4_RED);
if (!f4SP) {
f4SP={ G:svgToScreen(f4svg,F4_VB.w,F4_VB.h,F4_GREEN),
O:svgToScreen(f4svg,F4_VB.w,F4_VB.h,F4_ORANGE),
R:svgToScreen(f4svg,F4_VB.w,F4_VB.h,F4_RED) };
}
[fG,fO,fR].forEach(f=>f.style.display='block');
const ep=gsap.parseEase('power2.inOut')(p);
const f4Scale=f4svg.getBoundingClientRect().width/F4_VB.w;
function pos(div,srcBox,f5Box,start) {
const end=svgToScreen(svg,F5.w,F5.h,f5Box);
const cW=lerp(start.width,end.width,ep);
div.style.left=lerp(start.left,end.left,ep)+'px'; div.style.top=lerp(start.top,end.top,ep)+'px';
div.style.width=cW+'px'; div.style.height=lerp(start.height,end.height,ep)+'px';
const cs=cW/srcBox.w;
if (cs>0) { const sf=f4Scale/cs; div.querySelectorAll('path,circle,ellipse,rect').forEach(el=>{ const os=parseFloat(el.dataset.origStroke)||2.5; el.style.setProperty('stroke-width',(os*sf).toString()); }); }
}
pos(fG,F4_GREEN, F5G,f4SP.G);
pos(fO,F4_ORANGE,F5O,f4SP.O);
pos(fR,F4_RED, F5R,f4SP.R);
const fOp=p<0.08?p/0.08:p>0.80?(1-p)/0.20:1;
fG.style.opacity=fO.style.opacity=fR.style.opacity=fOp;
if (!f5SI) {
f5SI=true;
const f5Sc=svg.getBoundingClientRect().width/F5.w;
const se=fG.querySelector('path,circle'); const os=se?(parseFloat(se.dataset.origStroke)||2.5):2.5;
const ts=(os*f4Scale)/f5Sc*2.0;
[f5G,f5O,f5R].filter(Boolean).forEach(g=>g.querySelectorAll('path,circle,ellipse').forEach(el=>el.style.setProperty('stroke-width',ts.toString())));
}
const f5o=p>0.75?Math.min(1,(p-0.75)/0.15):0;
[f5G,f5O,f5R].filter(Boolean).forEach(g=>g.style.opacity=f5o);
greyCircles.forEach(c=>c.style.opacity=f5o);
['vennLabelA','vennLabelB','vennLabelC','vennLabelZ'].forEach((id,i)=>{
const delay=0.50+i*0.05, lp=Math.max(0,Math.min((p-delay)/0.15,1));
const el=document.getElementById(id); el.style.opacity=lp; el.style.transform=`translateY(${(1-lp)*20}px)`;
});
const cs=document.getElementById('vennConnectors');
if (cs) cs.style.opacity=Math.max(0,Math.min((p-0.50)/0.15,1));
const fp=Math.max(0,Math.min((p-0.70)/0.15,1));
const vf=document.getElementById('vennFooter'); vf.style.opacity=fp; vf.style.transform=`translateY(${(1-fp)*15}px)`;
},
onLeaveBack: () => {
f4SP=null; f5SI=false;
[fG,fO,fR].forEach(f=>{f.style.display='none';f.style.opacity=0;});
[f5G,f5O,f5R].filter(Boolean).forEach(g=>g.style.opacity=0);
greyCircles.forEach(c=>c.style.opacity=0);
['vennLabelA','vennLabelB','vennLabelC','vennLabelZ'].forEach(id=>{ const el=document.getElementById(id); el.style.opacity=0; el.style.transform='translateY(20px)'; });
const cs=document.getElementById('vennConnectors'); if(cs)cs.style.opacity=0;
document.getElementById('vennFooter').style.opacity=0;
},
onLeave: () => {
[fG,fO,fR].forEach(f=>{f.style.display='none';f.style.opacity=0;});
[f5G,f5O,f5R].filter(Boolean).forEach(g=>g.style.opacity=1);
greyCircles.forEach(c=>c.style.opacity=1);
}
});
};
_svgReady();
});
/* ═══════════════════════════════════════
7) PANEL SNAP / DWELL
═══════════════════════════════════════ */
let _svgCount = 0;
function _svgReady() {
if (++_svgCount < 3) return;
const DWELL = Math.round(window.innerHeight * 0.20);
gsap.utils.toArray('.text-panel').forEach(panel => {
ScrollTrigger.create({
trigger: panel,
start: 'center center',
end: `+=${DWELL}`,
pin: true,
pinSpacing: true,
anticipatePin: 1,
});
});
ScrollTrigger.refresh();
_createArrowTrigger?.();
_createFig5Trigger?.();
}