`:'';
return `
`;
}
function animMobBlock(id){
const obs=new IntersectionObserver((es)=>{es.forEach(e=>{ if(!e.isIntersecting)return;
e.target.querySelectorAll('.ssp-mc').forEach((c,i)=>setTimeout(()=>{ c.classList.add('in');
const f=c.querySelector('.ssp-mc-fill'); if(f) setTimeout(()=>f.style.width=f.dataset.w+'%',150); }, i*120));
obs.unobserve(e.target);
});},{threshold:.1});
const el=document.getElementById(id); if(el)obs.observe(el);
}
/* ============ VIZ 01 — WAVE (retained) ============ */
(function(){
const svg=d3.select('#sspWave'); const W=1100,H=520,m={t:40,r:140,b:50,l:60};
const years=[2019,2020,2021,2022,2023,2024,2025,2026];
const gdp=[100,93,99,101.5,103.0,103.8,104.4,104.9];
const cap=[100,92,95,95.6,95.4,95.0,94.8,94.6];
const x=d3.scalePoint().domain(years).range([m.l,W-m.r]);
const y=d3.scaleLinear().domain([88,108]).range([H-m.b,m.t]);
[90,95,100,105].forEach(v=>{ svg.append('line').attr('x1',m.l).attr('x2',W-m.r).attr('y1',y(v)).attr('y2',y(v)).attr('stroke',C.ink).attr('stroke-opacity',.08);
svg.append('text').attr('x',m.l-10).attr('y',y(v)+4).attr('text-anchor','end').attr('font-family',MONO).attr('font-size',10).attr('fill',C.mute).text(v); });
svg.append('line').attr('x1',m.l).attr('x2',W-m.r).attr('y1',y(100)).attr('y2',y(100)).attr('stroke',C.blueSoft).attr('stroke-dasharray','2 5').attr('stroke-opacity',.5);
svg.append('text').attr('x',W-m.r).attr('y',y(100)-6).attr('text-anchor','end').attr('font-family',MONO).attr('font-size',9).attr('fill',C.blueSoft).text('2019 LEVEL');
years.forEach(yr=> svg.append('text').attr('x',x(yr)).attr('y',H-m.b+20).attr('text-anchor','middle').attr('font-family',MONO).attr('font-size',10).attr('fill',C.mute).text(yr) );
const lineG=d3.line().x((d,i)=>x(years[i])).y(d=>y(d)).curve(d3.curveMonotoneX);
const areaCap=d3.area().x((d,i)=>x(years[i])).y0(y(100)).y1(d=>y(d)).curve(d3.curveMonotoneX);
const clip=svg.append('clipPath').attr('id','sspWaveClip'); const clipRect=clip.append('rect').attr('x',m.l).attr('y',m.t).attr('width',0).attr('height',H);
svg.append('path').datum(cap).attr('d',areaCap).attr('fill',C.red).attr('opacity',.10).attr('clip-path','url(#sspWaveClip)');
const pGdp=svg.append('path').datum(gdp).attr('fill','none').attr('stroke',C.gold).attr('stroke-width',3).attr('d',lineG);
const pCap=svg.append('path').datum(cap).attr('fill','none').attr('stroke',C.red).attr('stroke-width',3).attr('d',lineG);
[pGdp,pCap].forEach(p=>{ const L=p.node().getTotalLength(); p.attr('stroke-dasharray',L).attr('stroke-dashoffset',L); });
const lgG=svg.append('text').attr('x',x(2026)+8).attr('y',y(gdp[7])).attr('font-family',SERIF).attr('font-style','italic').attr('font-size',14).attr('font-weight',600).attr('fill',C.goldDeep).attr('opacity',0).text('Headline GDP');
const lgC=svg.append('text').attr('x',x(2026)+8).attr('y',y(cap[7])+4).attr('font-family',SERIF).attr('font-style','italic').attr('font-size',14).attr('font-weight',600).attr('fill',C.red).attr('opacity',0).text('GDP per capita');
onView('#sspWave',()=>{
pGdp.transition().duration(1800).ease(d3.easeCubicInOut).attr('stroke-dashoffset',0);
pCap.transition().duration(1800).delay(300).ease(d3.easeCubicInOut).attr('stroke-dashoffset',0);
clipRect.transition().duration(2000).delay(400).attr('width',W);
lgG.transition().delay(1900).duration(500).attr('opacity',1);
lgC.transition().delay(2100).duration(500).attr('opacity',1);
});
document.getElementById('mobWave').innerHTML =
mcCard('Headline GDP · 2026','104.9 (base 100 = 2019)','Official growth of ~2.6% stems chiefly from a statistical catch-up effect on depressed bases.',C.gold,98)+
mcCard('Real GDP per capita · 2026','94.6 — below the 2019 level','Per-capita income has never regained its pre-pandemic level: the “recovery” masks a deep stagnation.',C.red,76);
animMobBlock('mobWave');
})();
/* ============ VIZ 02 — BASKET (animated diverging bars) ============ */
(function(){
const svg=d3.select('#sspBasket'); const W=1100,H=480,m={t:60,r:60,b:50,l:240};
const rows=[
{name:'Animal protein',a:42,b:27,col:C.red},
{name:'Cereals & starches',a:28,b:46,col:C.gold},
{name:'Fruit, vegetables, other',a:30,b:27,col:C.blueSoft}
];
const x=d3.scaleLinear().domain([0,50]).range([m.l,W-m.r]);
const bh=46, gap=40;
svg.append('text').attr('x',m.l).attr('y',34).attr('font-family',MONO).attr('font-size',11).attr('letter-spacing','.15em').attr('fill',C.mute).text('SHARE OF BASKET (%) — ◇ BEFORE ● UNDER CONSTRAINT 2026');
[0,10,20,30,40,50].forEach(v=>{ svg.append('line').attr('x1',x(v)).attr('x2',x(v)).attr('y1',m.t-8).attr('y2',H-m.b).attr('stroke',C.ink).attr('stroke-opacity',.07);
svg.append('text').attr('x',x(v)).attr('y',H-m.b+20).attr('text-anchor','middle').attr('font-family',MONO).attr('font-size',10).attr('fill',C.mute).text(v); });
rows.forEach((r,i)=>{
const yc=m.t+i*(bh+gap)+bh/2;
svg.append('text').attr('x',m.l-16).attr('y',yc+5).attr('text-anchor','end').attr('font-family',SERIF).attr('font-style','italic').attr('font-size',18).attr('font-weight',600).attr('fill',C.ink).text(r.name);
// ghost "before"
svg.append('rect').attr('x',m.l).attr('y',yc-bh/2).attr('height',bh).attr('width',x(r.a)-m.l).attr('fill','none').attr('stroke',r.col).attr('stroke-dasharray','4 4').attr('stroke-opacity',.6);
svg.append('text').attr('x',x(r.a)+6).attr('y',yc-12).attr('font-family',MONO).attr('font-size',11).attr('fill',C.mute).attr('class','bk-a-'+i).attr('opacity',0).text('◇ '+r.a+'%');
// animated "after" bar
const bar=svg.append('rect').attr('x',m.l).attr('y',yc-bh/2).attr('height',bh).attr('width',0).attr('fill',r.col).attr('opacity',.9).attr('class','bk-bar-'+i);
const val=svg.append('text').attr('x',m.l+6).attr('y',yc+5).attr('font-family',MONO).attr('font-size',13).attr('font-weight',700).attr('fill',C.paper).attr('opacity',0).attr('class','bk-v-'+i).text(r.b+'%');
const delta=r.b-r.a;
svg.append('text').attr('x',W-m.r).attr('y',yc+5).attr('text-anchor','end').attr('font-family',SERIF).attr('font-style','italic').attr('font-size',16).attr('font-weight',600).attr('fill',delta>=0?C.gold:C.red).attr('opacity',0).attr('class','bk-d-'+i).text((delta>=0?'+':'')+delta+' pts');
onView('#sspBasket',()=>{
bar.transition().duration(1400).delay(i*250).ease(d3.easeCubicInOut).attr('width',x(r.b)-m.l);
svg.select('.bk-v-'+i).transition().delay(i*250+700).duration(400).attr('opacity',1);
svg.select('.bk-a-'+i).transition().delay(i*250+300).duration(400).attr('opacity',1);
svg.select('.bk-d-'+i).transition().delay(i*250+1100).duration(400).attr('opacity',1);
});
});
document.getElementById('mobBasket').innerHTML =
mcCard('Animal protein','42% → 27%','The consumption of meat and dairy products declines under budgetary pressure.',C.red,27)+
mcCard('Cereals & starches','28% → 46%','Households fall back on cheaper but less nutritious products.',C.gold,46)+
mcCard('Fruit, vegetables, other','30% → 27%','An anthropological shift in the diet, not merely an economic one.',C.blueSoft,27);
animMobBlock('mobBasket');
})();
/* ============ VIZ 03 — CROWDING (stacked bars: where the credit goes) ============ */
(function(){
const svg=d3.select('#sspCrowd'); const W=1100,H=500,m={t:56,r:40,b:56,l:60};
const years=[2018,2020,2022,2024,2026];
const state=[42,50,58,65,71];
const x=d3.scalePoint().domain(years).range([m.l+40,W-m.r-120]).padding(0.5);
const y=d3.scaleLinear().domain([0,100]).range([H-m.b,m.t]);
const bw=72;
// axes
[0,25,50,75,100].forEach(v=>{ svg.append('line').attr('x1',m.l).attr('x2',W-m.r-120).attr('y1',y(v)).attr('y2',y(v)).attr('stroke',C.ink).attr('stroke-opacity',.08);
svg.append('text').attr('x',m.l-10).attr('y',y(v)+4).attr('text-anchor','end').attr('font-family',MONO).attr('font-size',10).attr('fill',C.mute).text(v+'%'); });
svg.append('text').attr('x',m.l).attr('y',32).attr('font-family',MONO).attr('font-size',11).attr('letter-spacing','.15em').attr('fill',C.mute).text('ALLOCATION OF BANK CREDIT (%)');
years.forEach((yr,i)=>{
const cxb=x(yr);
svg.append('text').attr('x',cxb).attr('y',H-m.b+22).attr('text-anchor','middle').attr('font-family',MONO).attr('font-size',11).attr('fill',C.ink).attr('font-weight',i===years.length-1?700:400).text(yr);
// SME segment (bottom) + state segment (top), animated
const prodPct=100-state[i];
const rProd=svg.append('rect').attr('x',cxb-bw/2).attr('y',y(0)).attr('width',bw).attr('height',0).attr('fill',C.goldSoft).attr('opacity',.85);
const rState=svg.append('rect').attr('x',cxb-bw/2).attr('y',y(0)).attr('width',bw).attr('height',0).attr('fill',C.red).attr('opacity',.9);
const tState=svg.append('text').attr('x',cxb).attr('y',y(0)).attr('text-anchor','middle').attr('font-family',MONO).attr('font-size',12).attr('font-weight',700).attr('fill',C.paper).attr('opacity',0).text(state[i]+'%');
onView('#sspCrowd',()=>{
rProd.transition().duration(1100).delay(i*150).ease(d3.easeCubicOut)
.attr('y',y(prodPct)).attr('height',y(0)-y(prodPct));
rState.transition().duration(1100).delay(i*150+250).ease(d3.easeCubicOut)
.attr('y',y(100)).attr('height',y(prodPct)-y(100));
tState.transition().delay(i*150+900).duration(400).attr('opacity',1).attr('y',y(100)+ (y(prodPct)-y(100))/2 +4);
});
});
// side legend
const lg=svg.append('g').attr('transform',`translate(${W-m.r-100},${m.t+10})`);
[['State & public enterprises',C.red],['Productive economy (SMEs)',C.goldSoft]].forEach((d,i)=>{
const gy=i*34; lg.append('rect').attr('y',gy).attr('width',16).attr('height',16).attr('fill',d[1]).attr('opacity',.9);
lg.append('text').attr('x',22).attr('y',gy+9).attr('dominant-baseline','middle').attr('font-family',SERIF).attr('font-style','italic').attr('font-size',15).attr('font-weight',600).attr('fill',C.ink).text(d[0]);
});
lg.append('text').attr('y',92).attr('font-family',SANS).attr('font-size',11).attr('fill',C.mute).text('42% → 71% captured');
lg.append('text').attr('y',108).attr('font-family',SANS).attr('font-size',11).attr('fill',C.mute).text('by the state in 8 years');
document.getElementById('mobCrowd').innerHTML =
mcCard('2018 · State & PEs','42% of bank credit','The banking system still largely finances the productive economy.',C.red,42)+
mcCard('2026 · State & PEs','71% of bank credit','The state absorbs a growing share of resources: a crowding-out effect on SMEs.',C.red,71)+
mcCard('2026 · Productive economy','Just 29%','SMEs come up against an implicit rationing of credit.',C.goldSoft,29);
animMobBlock('mobCrowd');
})();
/* ============ VIZ 04 — CASCADE (shockwave) ============ */
(function(){
const svg=d3.select('#sspCascade'); const W=1100,H=520;
const steps=[
['Energy price','shock',C.gold],
['Fiscal','deficit',C.goldDeep],
['Pressure on','reserves',C.blueSoft],
['Rising','industrial costs',C.blue],
['Food','inflation',C.redSoft],
['Social','tension',C.red]
];
const n=steps.length, cw=(W-120)/n, top=120;
const defs=svg.append('defs');
defs.append('marker').attr('id','sspCasArrow').attr('viewBox','0 0 10 10').attr('refX',9).attr('refY',5).attr('markerWidth',7).attr('markerHeight',7).attr('orient','auto').append('path').attr('d','M0,0 L10,5 L0,10 Z').attr('fill',C.mute);
steps.forEach((s,i)=>{
const xx=60+cw*i+cw/2, yStep=top+i*46;
const g=svg.append('g').attr('class','cas-'+i).attr('transform',`translate(${xx},${yStep})`).attr('opacity',0);
g.append('rect').attr('x',-cw*0.42).attr('y',-32).attr('width',cw*0.84).attr('height',64).attr('rx',4).attr('fill',C.paper).attr('stroke',s[2]).attr('stroke-width',2);
g.append('rect').attr('x',-cw*0.42).attr('y',-32).attr('width',5).attr('height',64).attr('fill',s[2]);
g.append('text').attr('text-anchor','middle').attr('y',-4).attr('font-family',SERIF).attr('font-style','italic').attr('font-size',15).attr('font-weight',600).attr('fill',C.ink).text(s[0]);
g.append('text').attr('text-anchor','middle').attr('y',15).attr('font-family',SERIF).attr('font-style','italic').attr('font-size',15).attr('font-weight',600).attr('fill',C.ink).text(s[1]);
g.append('text').attr('text-anchor','middle').attr('y',-44).attr('font-family',MONO).attr('font-size',11).attr('fill',s[2]).attr('font-weight',700).text((i+1).toString().padStart(2,'0'));
if(i{
steps.forEach((s,i)=>{
svg.select('.cas-'+i).transition().duration(500).delay(i*320).attr('opacity',1);
if(i
mcCard('Link '+(i+1).toString().padStart(2,'0'), s[0]+' '+s[1].toLowerCase(),
i===0?'The external trigger: the sustained rise in the price of imported crude and gas.':
i===n-1?'The end of the chain: fuel, transport, and purchasing power under strain.':
'An intermediate link in the propagation, which the state budget can no longer cushion.', s[2], null)
).join('');
animMobBlock('mobCascade');
})();
/* ============ VIZ 05 — WEB (radial network of mediation breaking down) ============ */
(function(){
const svg=d3.select('#sspWeb'); const W=1100,H=560,cx=W/2,cy=H/2-10,R=190;
const ring=['UGTT','Parties','Employers','Civil society','Local gov.','Media','Associations'];
const N=ring.length;
const defs=svg.append('defs');
const glow=defs.append('radialGradient').attr('id','sspHubGlow');
glow.append('stop').attr('offset','0%').attr('stop-color',C.red).attr('stop-opacity',.35);
glow.append('stop').attr('offset','100%').attr('stop-color',C.red).attr('stop-opacity',0);
svg.append('circle').attr('cx',cx).attr('cy',cy).attr('r',R*0.9).attr('fill','url(#sspHubGlow)');
const nodes=ring.map((l,i)=>{ const a=i/N*6.283-Math.PI/2; return {l,a,x0:cx+Math.cos(a)*R,y0:cy+Math.sin(a)*R}; });
// links: spoke State→node + arcs between neighbours (the social mesh)
const links=[];
nodes.forEach((n,i)=>{ links.push({type:'spoke',i}); links.push({type:'arc',i,j:(i+1)%N}); });
const linkSel=svg.append('g').selectAll('path').data(links).enter().append('path')
.attr('fill','none').attr('stroke',C.gold).attr('stroke-width',2).attr('stroke-opacity',0);
// central hub (State)
const hub=svg.append('g').attr('transform',`translate(${cx},${cy})`).attr('opacity',0);
hub.append('circle').attr('r',26).attr('fill',C.red).attr('stroke',C.paper).attr('stroke-width',3);
hub.append('text').attr('text-anchor','middle').attr('dominant-baseline','middle').attr('font-family',SERIF).attr('font-style','italic').attr('font-weight',700).attr('font-size',18).attr('fill',C.paper).text('State');
const nodeSel=svg.append('g').selectAll('g').data(nodes).enter().append('g').attr('opacity',0).attr('transform',d=>`translate(${d.x0},${d.y0})`);
nodeSel.append('circle').attr('r',12).attr('fill',C.gold).attr('stroke',C.paper).attr('stroke-width',2);
nodeSel.append('text').attr('text-anchor','middle').attr('y',d=>Math.sin(d.a)<0?-22:30).attr('font-family',SERIF).attr('font-style','italic').attr('font-weight',600).attr('font-size',15).attr('fill',C.ink).text(d=>d.l);
function geom(decay){
// decay 0 → cohesion ; 1 → breakdown (nodes pushed apart, links stretched)
const yMax = H-46; // safeguard: do not descend onto the bottom caption
return nodes.map(n=>{ const r=R*(1+decay*0.34); const drift=(Math.sin(n.a*3)*decay*22);
const x=cx+Math.cos(n.a)*r+drift;
const y=Math.min(yMax, cy+Math.sin(n.a)*r);
return {x, y}; });
}
function render(decay){
const pos=geom(decay);
nodeSel.attr('transform',(d,i)=>`translate(${pos[i].x},${pos[i].y})`);
linkSel
.attr('d',d=>{
if(d.type==='spoke'){ const p=pos[d.i];
const mx=(cx+p.x)/2+Math.sin(d.i)*decay*30, my=(cy+p.y)/2;
return `M ${cx} ${cy} Q ${mx} ${my} ${p.x} ${p.y}`; }
const a=pos[d.i], b=pos[d.j];
const mx=(a.x+b.x)/2, my=(a.y+b.y)/2 - (1-decay)*30; // curvature that flattens
return `M ${a.x} ${a.y} Q ${mx} ${my} ${b.x} ${b.y}`;
})
.attr('stroke-opacity',d=> (d.type==='arc'?0.7:0.55)*(1-decay*0.85))
.attr('stroke-width',2*(1-decay*0.6))
.attr('stroke-dasharray',d=> decay>0.4 ? `${2+decay*4} ${decay*9}` : 'none');
}
render(0);
onView('#sspWeb',()=>{
hub.transition().duration(700).attr('opacity',1);
nodeSel.transition().duration(700).delay((d,i)=>i*70).attr('opacity',1);
linkSel.transition().duration(900).delay(500).attr('stroke-opacity',d=>d.type==='arc'?0.7:0.55);
if(!reduced){
ScrollTrigger.create({trigger:'#sspWeb',start:'top 65%',end:'bottom 25%',scrub:true,
onUpdate:self=>render(self.progress)});
}
});
svg.append('text').attr('x',cx).attr('y',H-14).attr('text-anchor','middle').attr('font-family',MONO).attr('font-size',10).attr('letter-spacing','.12em').attr('fill',C.mute).attr('opacity',0).attr('class','web-cap').text('COHESIVE MESH → STRETCHED TIES, AS YOU SCROLL');
onView('#sspWeb',()=>svg.select('.web-cap').transition().delay(1200).duration(600).attr('opacity',1));
document.getElementById('mobWeb').innerHTML =
mcCard('Yesterday','A cohesive mesh','The state at the centre, linked to robust intermediary institutions that turned tensions into negotiations.',C.gold,82)+
mcCard('Today','Stretched ties','A diminished UGTT, discredited parties, weakened local authorities, civil society under pressure.',C.red,32)+
mcCard('Consequence','Eroding governability','Frustrations are no longer absorbed: they accumulate silently.',C.redSoft,58);
animMobBlock('mobWeb');
})();
/* ============ VIZ 06 — RADAR ============ */
(function(){
const svg=d3.select('#sspRadar'); const W=900,H=620,cx=W/2,cy=H/2+10,maxR=190;
const data=[
{axis:'Stagnation',val:.82},{axis:'Energy',val:.88},{axis:'Food',val:.60},
{axis:'Banking',val:.78},{axis:'Currency',val:.72},{axis:'Public services',val:.70},{axis:'Mediation',val:.83}
];
const N=data.length;
svg.append('text').attr('x',cx).attr('y',26).attr('text-anchor','middle').attr('font-family',MONO).attr('font-size',10).attr('letter-spacing','.25em').attr('fill',C.mute).text('VULNERABILITY SURFACE');
[.25,.5,.75,1].forEach(t=>{ const pts=[]; for(let i=0;i<=N;i++){const a=(i%N)/N*6.283-Math.PI/2; pts.push([cx+Math.cos(a)*maxR*t,cy+Math.sin(a)*maxR*t]);}
svg.append('path').attr('d',d3.line()(pts)).attr('fill','none').attr('stroke',C.ink).attr('stroke-opacity',.1).attr('stroke-dasharray','2 4'); });
data.forEach((d,i)=>{ const a=i/N*6.283-Math.PI/2;
svg.append('line').attr('x1',cx).attr('y1',cy).attr('x2',cx+Math.cos(a)*maxR).attr('y2',cy+Math.sin(a)*maxR).attr('stroke',C.ink).attr('stroke-opacity',.1);
const lr=maxR+30, lx=cx+Math.cos(a)*lr, ly=cy+Math.sin(a)*lr;
svg.append('text').attr('x',lx).attr('y',ly).attr('text-anchor','middle').attr('dominant-baseline','middle').attr('font-family',SERIF).attr('font-style','italic').attr('font-size',15).attr('font-weight',600).attr('fill',C.ink).text(d.axis); });
function poly(s){ return data.map((d,i)=>{const a=i/N*6.283-Math.PI/2,r=d.val*maxR*s; return `${i?'L':'M'} ${cx+Math.cos(a)*r} ${cy+Math.sin(a)*r}`;}).join(' ')+' Z'; }
const path=svg.append('path').attr('d',poly(0)).attr('fill',C.red).attr('fill-opacity',.22).attr('stroke',C.red).attr('stroke-width',2.5);
const dots=data.map(()=>svg.append('circle').attr('r',4).attr('fill',C.gold).attr('cx',cx).attr('cy',cy));
onView('#sspRadar',()=>{
const o={s:0}; gsap.to(o,{s:1,duration:1.6,ease:'power3.out',onUpdate:()=>{ path.attr('d',poly(o.s));
data.forEach((d,i)=>{const a=i/N*6.283-Math.PI/2,r=d.val*maxR*o.s; dots[i].attr('cx',cx+Math.cos(a)*r).attr('cy',cy+Math.sin(a)*r);}); }});
});
document.getElementById('mobRadar').innerHTML = data.map(d=>
mcCard('Axis of fragility', d.axis, 'Estimated intensity of vulnerability along this systemic axis.', d.val>.8?C.red:(d.val>.7?C.redSoft:C.gold), Math.round(d.val*100))
).join('') + mcCard('Overall reading','The risk is systemic','No single axis is sufficient in isolation to trigger rupture; it is their superimposition that erodes resilience.',C.ink,90);
animMobBlock('mobRadar');
})();
setTimeout(()=>{ if(window.ScrollTrigger) ScrollTrigger.refresh(); }, 800);
})();