1(()=>{ 2/* URL helpers: ?t=<sec> fast-forward the intros; ?static reduced motion; ?freeze no animation loop; ?f=<phase> jump to a 3 point of the landing breakdown (0 whole, 1..5 pieces, 6 big, 7 last screen); ?scroll=<px|N%|Nvh>; ?quiz=q2|q3|suite|team|team51; ?fit=q1.q2a-q2b.q3; ?menu */ 4const Q=new URLSearchParams(location.search); 5const RM=matchMedia('(prefers-reduced-motion: reduce)').matches||Q.has('static'); 6if(RM)document.documentElement.classList.add('rm'); // CSS hook, so ?static also stops the CSS loops (QA 29 Sep 2026) 7const FREEZE=Q.has('freeze'); 8const FT=Q.has('t')?parseFloat(Q.get('t'))||0:0; 9const VIEW=Q.get('view'); 10if(Q.get('vh')) document.documentElement.style.setProperty('--vh',Q.get('vh')+'px'); 11 12const C30=Math.cos(Math.PI/6), TAU=Math.PI*2; 13const clamp=(v,a,b)=>v<a?a:v>b?b:v; 14const lerp=(a,b,t)=>a+(b-a)*t; 15const ss=(a,b,x)=>{const t=clamp((x-a)/(b-a),0,1);return t*t*(3-2*t)}; 16const easeIO=p=>p<.5?4*p*p*p:1-Math.pow(-2*p+2,3)/2; 17const hash=(i,j)=>{const s=Math.sin(i*127.1+j*311.7)*43758.5453;return s-Math.floor(s)}; 18// seeded random so renders are stable 19let SEED=7; const srnd=()=>{SEED=(SEED*16807)%2147483647;return (SEED-1)/2147483646}; 20 21const hex=h=>{h=h.replace('#','');return [parseInt(h.slice(0,2),16),parseInt(h.slice(2,4),16),parseInt(h.slice(4,6),16)]}; 22const rgb=c=>`rgb(${c[0]|0},${c[1]|0},${c[2]|0})`; 23const mixC=(a,b,k)=>{const A=hex(a),B=hex(b);return rgb([lerp(A[0],B[0],k),lerp(A[1],B[1],k),lerp(A[2],B[2],k)])}; 24const pal=(t,l,r)=>({top:rgb(hex(t)),left:rgb(hex(l)),right:rgb(hex(r))}); 25const P_HI =pal('#8AA59C','#6E8C83','#57756C'); // L: a highlighted block, light sage 26// 1 Oct 2026, founder: "just a small touch of orange" (J's orange, used sparingly): the chosen piece of the mark, a few 27// agents in every swarm, the moving or approved block in each small scene, a few cubes in the dividers 28const ORANGE='#EE6A33',ORG=[238,106,51],OR_FRAC=.07; 29const P_OR =pal('#F47F4A','#E0602B','#B24A1E'); 30const P_LIFT_OR=pal('#F6DECE','#EBC9B2','#DDB59A'); // the cursor's lifted world cells, at the very centre 31const P_COB =pal('#46675F','#324E48','#26403B'); 32// L: the world on cream, tone on tone 33const P_GROUND=pal('#EAE2D3','#DDD4C2','#D0C6B2'); 34const P_LIFT =pal('#F5F0E7','#E3DACA','#D4CAB6'); 35const P_PLINTH=pal('#EFE7DA','#EFE7DA','#EFE7DA'); 36const P_FLOOR =pal('#DCD4C2','#C8BEA8','#AFA48E'); 37const P_INK =pal('#3A5650','#253B36','#121D1A'); 38const P_FOG =pal('#B7C2BB','#95A49C','#74857D'); 39const INK='#121D1A', BONE='#E4DCCA', COBALT='#324E48', CREAM='#EFE7DA', WORLD_BG='#EFE7DA'; 40// L: highlights: dark sage on the light grounds, a bright cream on the dark ones 41const HI_L='#324E48', HI_D='#F6F1E7'; 42const FONT='"Switzer", system-ui, sans-serif'; 43 44// isometric box (from "Isometric World"), with optional face culling 45function box(ctx,P,x,y,z,w,d,h,p,stroke,skipL,skipR){ 46 const u=P.u,ox=P.ox,oy=P.oy; 47 const X=(a,b)=>ox+(a-b)*u*C30, Y=(a,b,c)=>oy+(a+b)*u*.5-c*u; 48 const x2=x+w,y2=y+d,z2=z+h; 49 const ax=X(x,y),ay=Y(x,y,z2),bx=X(x2,y),by=Y(x2,y,z2),cx=X(x2,y2),cy=Y(x2,y2,z2),dx=X(x,y2),dy=Y(x,y2,z2); 50 const ey=Y(x2,y,z),fy=Y(x2,y2,z),gy=Y(x,y2,z); 51 if(stroke) ctx.strokeStyle=stroke; 52 if(!skipR){ctx.beginPath();ctx.moveTo(bx,by);ctx.lineTo(cx,cy);ctx.lineTo(cx,fy);ctx.lineTo(bx,ey);ctx.closePath();ctx.fillStyle=p.right;ctx.fill();if(stroke)ctx.stroke();} 53 if(!skipL){ctx.beginPath();ctx.moveTo(dx,dy);ctx.lineTo(cx,cy);ctx.lineTo(cx,fy);ctx.lineTo(dx,gy);ctx.closePath();ctx.fillStyle=p.left;ctx.fill();if(stroke)ctx.stroke();} 54 ctx.beginPath();ctx.moveTo(ax,ay);ctx.lineTo(bx,by);ctx.lineTo(cx,cy);ctx.lineTo(dx,dy);ctx.closePath();ctx.fillStyle=p.top;ctx.fill();if(stroke)ctx.stroke(); 55} 56const projPt=(P,x,y,z)=>[P.ox+(x-y)*P.u*C30,P.oy+(x+y)*P.u*.5-z*P.u]; 57// canvas sizing: DPR capped at 2, and the backing store capped (~7 MP) so large retina screens stay fast and never hit canvas limits 58const MAXPX=7e6; 59function setupCanvas(cv){ 60 const r=cv.getBoundingClientRect();const W=Math.max(1,r.width),H=Math.max(1,r.height); 61 const dpr=Math.max(1,Math.min(window.devicePixelRatio||1,2,Math.sqrt(MAXPX/(W*H)))); 62 cv.width=Math.max(1,Math.round(W*dpr));cv.height=Math.max(1,Math.round(H*dpr)); 63 const ctx=cv.getContext('2d');ctx.setTransform(dpr,0,0,dpr,0,0); 64 return {ctx,W,H,dpr}; 65} 66function fit(W,H,b,pad,shiftY){ 67 let minx=1e9,maxx=-1e9,miny=1e9,maxy=-1e9; 68 for(const x of [b[0],b[1]])for(const y of [b[2],b[3]])for(const z of [b[4],b[5]]){ 69 const sx=(x-y)*C30,sy=(x+y)*.5-z;minx=Math.min(minx,sx);maxx=Math.max(maxx,sx);miny=Math.min(miny,sy);maxy=Math.max(maxy,sy);} 70 const u=Math.min((W-2*pad)/(maxx-minx),(H-2*pad)/(maxy-miny)); 71 return {u,ox:W/2-(minx+maxx)/2*u,oy:H/2-(miny+maxy)/2*u+(shiftY||0)}; 72} 73function label(ctx,text,x,y,color,align,size){ 74 ctx.font=`500 ${size||10}px ${FONT}`;ctx.fillStyle=color;ctx.textAlign=align||'left';ctx.textBaseline='middle'; 75 ctx.fillText(text.toUpperCase(),x,y); 76} 77// a label on a small solid tag, so it stays readable over any surface
78function chip(ctx,text,x,y,align,size,bg,fg){ 79 const T=text.toUpperCase();ctx.font=`500 ${size||10}px ${FONT}`;const w=ctx.measureText(T).width,px=5,h=(size||10)+8; 80 const x0=align==='right'?x-w-px*2:align==='center'?x-w/2-px:x;ctx.fillStyle=bg;ctx.fillRect(x0,y-h/2,w+px*2,h); 81 ctx.fillStyle=fg;ctx.textAlign='left';ctx.textBaseline='middle';ctx.fillText(T,x0+px,y+.5); 82} 83// a label with a halo in the background colour 84function haloLabel(ctx,text,x,y,color,halo,align,size){ 85 ctx.font=`500 ${size||10}px ${FONT}`;ctx.textAlign=align||'left';ctx.textBaseline='middle'; 86 ctx.lineWidth=4;ctx.lineJoin='round';ctx.strokeStyle=halo;ctx.strokeText(text.toUpperCase(),x,y);ctx.fillStyle=color;ctx.fillText(text.toUpperCase(),x,y); 87} 88// curl-noise flow (from "Living System") 89function psi(x,y,t){return Math.sin(x*.0031+t*.13)*Math.cos(y*.0043-t*.09)+Math.sin((x+y)*.0017-t*.07+1.3)*.8+Math.cos(x*.0071-y*.0023+t*.19)*.35} 90function field(x,y,t){const e=2,dx=psi(x+e,y,t)-psi(x-e,y,t),dy=psi(x,y+e,t)-psi(x,y-e,t);return Math.atan2(-dx,dy)} 91function chev(c,x,y,a,r){const cc=Math.cos(a),s=Math.sin(a);c.moveTo(x+cc*r,y+s*r);c.lineTo(x-cc*r*.8-s*r*.75,y-s*r*.8+cc*r*.75);c.lineTo(x-cc*r*.3,y-s*r*.3);c.lineTo(x-cc*r*.8+s*r*.75,y-s*r*.8-cc*r*.75);c.closePath()} 92const angWrap=d=>((d%TAU)+TAU*1.5)%TAU-Math.PI; 93const lineWrap=d=>{d=((d%Math.PI)+Math.PI*1.5)%Math.PI-Math.PI/2;return d}; 94 95/* ============================================================ 96 THE MARK (Website L): the OFFICIAL ADROLAB icon, traced to vectors from Adrolab_Icon_black.png (1083x1111) with potrace, 97 in mark units (946 wide, y 25..975). ll/logo/ has the tracing scripts and the fidelity check. The mark is split along its 98 own outlines into five pieces; together they give back the official mark exactly. Each piece: ink (the outline, filled, 99 even-odd), face (its enclosed faces, for the subtle tint of the active piece) and poly (its silhouette, for hit tests and 100 label placement). caps: the short closing edges where a piece was cut free of its neighbour (the wing strips and the stem); 101 they are drawn only while the pieces are apart, so the whole mark never shows them. 102 ============================================================ */ 103const MARK_D={"parts":{"core":{"ink":"M467.7 386.6C452 384.7 441.3 381.4 431.1 375.2C428.7 373.7 418.1 367.3 407.6 360.9C397.1 354.5 386.6 348.1 384.3 346.7C375.4 341.2 371.9 339.1 361.5 332.8C351.4 326.6 349 325.2 338.6 318.8C336.4 317.4 330.2 313.6 324.9 310.4C319.6 307.2 312.5 302.8 309.2 300.8C305.9 298.8 299.6 294.9 295.2 292.2C290.7 289.5 284.3 285.6 280.9 283.5C277.5 281.4 267.3 275.1 258.3 269.6C249.3 264.1 239.2 257.9 235.7 255.8C232.3 253.7 225.9 249.8 221.4 247.1C217 244.4 210.6 240.4 207.2 238.3C203.7 236.2 197.3 232.3 192.9 229.6C188.4 226.8 182 222.9 178.6 220.8C175.2 218.7 165 212.5 156 207C147 201.5 136.9 195.3 133.5 193.2C130 191.1 123.6 187.1 119.2 184.4C114.7 181.7 108.3 177.8 104.9 175.7C101.5 173.6 91.3 167.3 82.3 161.8C73.3 156.3 63.2 150.1 59.7 148C56.3 145.9 49.8 141.9 45.2 139.1C33.3 131.9 29.4 128.8 25.8 124.1C23.9 121.6 22.8 119.6 22.6 118.5C22.3 116.9 22.2 116.8 21.1 117.1C19.3 117.6 16.4 116.8 15.1 115.4C11.6 111.6 18 96.5 27.2 86.8C37.3 76.3 105 33.3 118.2 29C127.7 25.9 141.9 24.3 148.6 25.4C150 25.7 152.6 26.1 154.4 26.4C159.3 27.3 166.8 29.5 170.5 31.2C173.3 32.4 188.9 41.9 212.2 56.3C215.4 58.3 224 63.6 231.2 68C238.5 72.5 250.3 79.7 257.5 84.1C264.7 88.5 276.4 95.8 283.6 100.2C290.9 104.6 302.8 111.9 310.1 116.4C317.5 120.9 326.1 126.2 329.3 128.2C347.5 139.5 361.7 148.1 362.7 148.5C364.7 149.2 366.8 148.5 373.4 144.9C378.8 141.8 394.8 131.5 402.6 125.9C427.5 108.2 445.2 98.5 457.3 96C462.4 94.9 477.1 94.6 482 95.6C488.8 96.9 498.6 99.4 499.5 100.2C499.7 100.4 500.2 100.6 500.5 100.6C501.8 100.6 511.6 105.4 518.9 109.6C523.2 112 534.6 119.6 549.9 130.1C561 137.7 572 144.1 579.4 147.3C584.1 149.4 582.2 150.1 597.9 140.4C604.3 136.4 610.5 132.6 611.7 131.9C612.8 131.2 618.2 127.9 623.7 124.5C633.4 118.5 655.8 104.7 667 97.8C670.1 95.8 677.6 91.2 683.7 87.5C689.7 83.8 699.5 77.7 705.5 74C711.6 70.3 719.1 65.6 722.2 63.7C725.4 61.8 735.2 55.7 743.9 50.4C752.6 45 762.9 38.6 766.8 36.2C775.3 31 778.1 29.8 787.2 27.5C803.7 23.5 820 24.9 833.5 31.5C847.5 38.3 869.2 51.4 895 68.7C920.4 85.7 926.7 92.6 931.8 108.4C932.6 110.9 932.5 113.6 931.4 114.9C930.5 116.1 926.2 117.4 924.9 116.9C924.3 116.7 924.1 117 923.9 118.2C923.4 120.7 918.2 127.2 914.3 130.1C910.7 132.7 909.9 133.2 884 149.2C875 154.7 861.7 162.8 854.6 167.2C847.4 171.6 835.7 178.8 828.4 183.3C821.2 187.7 8
10309.4 194.9 802.2 199.4C795 203.8 783.1 211.1 775.8 215.6C768.5 220 760 225.3 756.8 227.3C753.6 229.2 746.4 233.6 740.8 237.1C735.2 240.6 725.9 246.2 720.3 249.7C714.7 253.1 707.4 257.5 704.3 259.5C701.1 261.5 692.6 266.7 685.3 271.2C671.4 279.7 652.5 291.3 635.6 301.7C621.2 310.5 611.5 316.5 590.1 329.5C559.9 347.9 534.1 363.7 521.5 371.3C509.7 378.5 505.4 380.8 500.3 382.5C490.4 385.7 475.7 387.6 467.7 386.6ZM482 358C485.6 357.4 492.9 355.1 495.2 353.9C496.2 353.4 503.7 348.9 511.8 343.9C520 338.9 532.6 331.1 539.8 326.7C558.2 315.4 576.9 303.9 592.4 294.4C599.7 289.9 611.6 282.6 618.8 278.2C626 273.7 637.9 266.5 645.1 262C652.3 257.6 664.2 250.3 671.5 245.9C678.7 241.4 687.3 236.1 690.5 234.2C693.6 232.2 700.8 227.8 706.5 224.3C712.1 220.9 721.3 215.2 727 211.8C732.6 208.3 739.8 203.9 743 201.9C746.1 200 754.7 194.7 762 190.2C769.3 185.8 781.1 178.5 788.2 174.1C809.9 160.8 827.1 150.3 840.8 141.9C847.9 137.5 861.1 129.4 870.1 123.8C879.1 118.3 889 112.2 892.1 110.3C895.2 108.4 897.8 106.6 897.8 106.2C897.8 105.2 885.8 97 860.3 80.6C844 70.1 825.9 60 817.6 56.8C815.5 56 813.3 55.1 812.8 54.9C807 52.4 794.7 54 787.7 58C785.8 59.1 673.5 128.3 552.7 202.6C503.1 233.2 507 230.8 502.7 232.5C497.1 234.8 493.8 235.7 486.6 237.2C482.1 238.1 466.7 238.2 462.6 237.4C447.8 234.4 440.9 231.7 427.8 223.5C423.7 220.8 417.9 217.2 414.9 215.4C412 213.7 397.5 204.7 382.7 195.5C367.9 186.3 353.2 177.1 350 175.2C346.8 173.2 336.5 166.8 327 160.9C317.5 155 307.1 148.6 303.9 146.6C300.7 144.7 286 135.6 271.2 126.4C256.4 117.2 241.7 108 238.5 106.1C235.3 104.1 225 97.7 215.5 91.8C206 85.9 195.6 79.5 192.4 77.5C189.3 75.6 184.2 72.4 181.1 70.5C160.7 57.7 158.4 56.6 150.3 55C148.7 54.7 146.5 54.2 145.3 54C141.5 53.3 136.5 53.7 133.2 55C132.7 55.2 130.5 56 128.4 56.8C117.1 61.2 97.5 72.7 68.3 92C54 101.5 48.6 105.5 48.8 106.6C48.9 107.3 51.8 109.4 59 113.8C64.6 117.2 71.6 121.5 74.5 123.2C77.4 125 87.1 131 96.1 136.5C105.1 142 115.3 148.2 118.7 150.3C122.1 152.4 128.6 156.4 133 159.1C137.4 161.8 143.9 165.7 147.3 167.8C150.7 169.9 160.9 176.1 169.8 181.6C178.8 187.1 189 193.4 192.4 195.5C195.8 197.6 202.3 201.5 206.7 204.2C211.1 206.9 217.6 210.9 221 213C224.4 215.1 230.8 219 235.3 221.7C239.7 224.4 246.1 228.4 249.6 230.5C253 232.6 263.1 238.8 272.1 244.3C281.1 249.8 291.3 256 294.7 258.1C298.1 260.2 304.6 264.2 309 266.9C313.4 269.6 319.8 273.5 323.3 275.6C326.7 277.7 336.8 283.9 345.8 289.5C354.8 295 365 301.2 368.4 303.3C371.8 305.4 378.3 309.3 382.7 312C387.1 314.7 393.6 318.7 397 320.8C400.4 322.9 410.6 329.1 419.6 334.6C428.6 340.1 438.7 346.3 442.1 348.4C448.1 352.1 452 354 458 356.2C463.6 358.3 475.7 359.2 482 358ZM483.4 208.3C487.7 207.3 490.1 206.5 490.7 206C491 205.8 491.4 205.6 491.8 205.6C492.9 205.6 501.9 201.1 507.2 197.9C513 194.5 517.7 191.4 527.1 184.8C535.7 178.9 544.2 173.5 549.4 170.8C556.3 167.2 556.3 165.9 549.4 162.5C544 159.9 536.2 155.1 527.4 148.9C514.8 140.2 512.8 138.9 508 135.9C481.2 119.4 467.2 119.1 441.2 134.7C436.9 137.3 431.3 141 413.8 152.9C407 157.5 400 161.7 395.4 164C393.5 164.9 391.8 166 391.7 166.4C391.4 167.2 392.5 169 393.9 169.8C394.4 170.2 400.7 174.2 407.8 178.7C439.3 198.9 450.7 205.3 460.8 208.2C465.9 209.6 477.8 209.7 483.4 208.3Z","face":"M466.5 358.1C466.2 357.9 465.3 357.6 464.5 357.6C463.8 357.6 462.4 357.4 461.5 357.1C460.6 356.9 459.1 356.4 458.3 356.2C457.4 355.9 456.2 355.6 455.7 355.4C450.3 353.1 447.4 351.6 442.1 348.4C438.7 346.3 428.6 340.1 419.6 334.6C410.6 329.1 400.4 322.9 397 320.8C393.6 318.7 387.1 314.7 382.7 312C378.3 309.3 371.8 305.4 368.4 303.3C365 301.2 354.8 295 345.8 289.5C336.8 283.9 326.7 277.7 323.3 275.6C319.8 273.5 313.4 269.6 309 266.9C304.6 264.2 298.1 260.2 294.7 258.1C291.3 256 281.1 249.8 272.1 244.3C263.1 238.8 253 232.6 249.6 230.5C246.1 228.4 239.7 224.4 235.3 221.7C230.8 219 224.4 215.1 221 213C217.6 210.9 211.1 206.9 206.7 204.2C202.3 201.5 195.8 197.6 192.4 195.5C189 193.4 178.8 187.1 169.8 181.6C160.9 176.1 150.7 169.9 147.3 167.8C143.9 165.7 137.4 161.8 133 159.1C128.6 156.4 122.1 152.4 118.7 150.3C115.3 148.2 105.1 142 96.1 136.5C87.1 131 77.4 125 74.5 123.2C71.6 121.5 64.6 117.2 59 113.8C51.8 109.4 48.9 107.3 48.8 106.6C48.6 105.5 54 101.5 68.3 92C97.5 72.7 117.1 61.2 128.4 56.8C130.5 56 132.7 55.1 133.2 54.9C134.2 54.5 136 54.2 139.6 53.7C143.1 53.3 150.7 54.8 158.6 57.4C161.1 58.2 166.3 61.3 181.1 70.5C184.2 72.4 189.3 75.6 192.4 77.5C195.6 79.5 206 85.9 215.5 91.8C225 97.7 235.3 104.1 238.5 106.1C241.7 108 256.4 117.2 271.2 126.4C286 135.6 300.7 144.7 303.9 146.6C307.1 148.6 317.5 155 327 160.9C336.5 166.8 346.8 173.2 350 175.2C353.2 177.1 367.9 186.3 382.7 195.5C397.5 204.7 412 213.7 414.9 215.4C417.9 217.2 423.7 220.8 427.8 223.5C439 230.4 448.7 234.7 457.6 236.4C459.1 236.7 461.3 237.2 462.5 237.5C465.4 238.1 483.9 238.1 485.6 237.4C486.3 237.2 487.4 236.9 488.1 236.9C488.7 236.9 489.9 236.7 490.7 236.5C491.5 236.3 493.1 235.8 494.2 235.5C495.3 235.2 496.7 234.8 497.2 234.6C497.7 234.4 499.9 233.6 502 232.7C507.1 230.8 500.9 234.5 566.5 194.1C735.3 90.2 788.3 57.6 789.6 57.1C790.4 56.7 792.1 56.2 793.2 55.9C794.3 55.6 795.8 55.1 796.4 54.9C797.1 54.8 798.4 54.3 799.4 54C802.2 53.2 809.9 53.7 812.8 54.9C813.3 55.1 815.5 56 817.6 56.8C825.9 60 844 70.1 860.3 80.6C885.8 97 897.8 105.2 897.8 106.2C897.8 106.6 895.2 108.4 892.1 110.3C889 112.2 879.1 118.3 870.1 123.8C861.1 129.4 847.9 137.5 840.8 141.9C827.1 150.3 809.9 160.8 788.2 174.1C781.1 178.5 769.3 185.8 762 190.2C754.7 194.7 746.1 200 743 201.9C739.8 203.9 732.6 208.3 727 211.8C721.3 215.2 712.1 220.9 706.5 224.3C700.8 227.8 693.6 232.2 690.5 234.2C687.3 236.1 678.7 241.4 671.5 245.9C664.2 250.3 652.3 257.6 645.
1031 262C637.9 266.5 626 273.7 618.8 278.2C611.6 282.6 599.7 289.9 592.4 294.4C576.9 303.9 558.2 315.4 539.8 326.7C532.6 331.1 520 338.9 511.8 343.9C494 354.8 493.5 355.1 487.3 356.8C482.3 358.2 468 359.1 466.5 358.1ZM463.3 208.8C462.7 208.6 461.2 208.2 460.1 207.9C451.3 205.6 438.6 198.5 407.8 178.7C400.7 174.2 394.4 170.2 393.9 169.8C392.5 169 391.4 167.2 391.7 166.4C391.8 166 393.5 164.9 395.4 164C400 161.7 407 157.5 413.8 152.9C446.4 130.7 449.9 128.7 465.2 124C467.8 123.2 479 123.2 481.8 124C482.9 124.3 484.6 124.8 485.4 125C491.3 126.7 499.5 130.7 508 135.9C512.8 138.9 514.8 140.2 527.4 148.9C536.2 155.1 544 159.9 549.4 162.5C556.3 165.9 556.3 167.2 549.4 170.8C544.2 173.5 535.7 178.9 527.1 184.8C510.4 196.5 494.9 205.6 491.7 205.6C491.3 205.6 490.9 205.8 490.7 206.1C490.6 206.3 490 206.5 489.4 206.5C488.8 206.5 488.1 206.7 488 206.9C487.9 207.2 487.2 207.5 486.5 207.6C485.8 207.7 484.2 208.1 482.9 208.4C480.2 209.1 465 209.4 463.3 208.8Z","poly":[[14.4,111.4],[38.3,134.9],[446.0,382.3],[471.4,386.9],[511.0,377.7],[910.9,132.6],[924.3,116.5],[932.1,113.7],[925.7,94.8],[904.9,75.4],[837.7,33.5],[809.6,25.2],[772.7,32.6],[583.8,149.2],[509.6,104.5],[480.1,95.3],[460.8,95.3],[443.3,100.8],[363.6,149.2],[174.2,33.1],[147.0,25.2],[115.7,29.8],[30.9,83.3],[19.4,97.1]]},"frame":{"ink":"M465.9 527.8C461.6 526.9 460.1 526.4 459.1 525.5C458.4 524.8 456.7 523.9 455.3 523.5C452.3 522.5 453.1 522.9 415.5 499.7C412.9 498.1 408.7 495.5 406.1 493.9C403.5 492.3 393.5 486 383.9 480C374.2 473.9 364.2 467.7 361.5 466C358.8 464.4 354.8 461.8 352.5 460.4C342.2 453.8 339.1 451.9 332 447.5C327.8 444.9 321.1 440.6 317 438.1C313 435.5 308.2 432.5 306.5 431.4C302.9 429.3 303.1 429.4 286.2 418.7C279.8 414.7 268.3 407.5 260.6 402.7C252.9 397.8 245.8 393.4 244.9 392.9C244.1 392.3 240.3 390 236.7 387.8C233 385.6 227.2 382.1 223.8 380C220.3 377.9 204.5 368.3 188.5 358.8C172.5 349.2 155.8 339.1 151.3 336.4C146.9 333.7 142.5 331.3 141.7 331.1C139.9 330.8 138.6 330.1 123.8 321.4C117.6 317.7 112.2 314.6 111.8 314.4C111.4 314.2 110.8 313.1 110.4 312.1C109.7 310.1 108.9 309.5 92.2 299.1C90.7 298.2 77.2 289.8 62.3 280.4C47.3 271 33.6 262.4 31.9 261.3C26.4 258 23.5 254.3 19 245.3C13.1 233.6 12.6 229.6 11.8 197C10.9 159.1 12.3 117.8 14.5 115.1C15.8 113.6 20.4 114 21.7 115.8C22.3 116.8 22.4 116.7 25.4 114.4C27.1 113.1 28.7 112.1 28.8 112.1C29 112.1 31.9 111.5 35.4 110.7L41.8 109.4L48.3 110.8C54.9 112.1 54.9 112.1 61.1 115.8C67 119.4 100.3 139.7 109.3 145.1C118.2 150.6 121.6 152.7 132.1 159.1C138 162.7 144.8 166.8 147 168.2C156.9 174.2 160 176.1 177.9 187C188.3 193.4 205.3 203.7 215.7 210.1C226.1 216.4 243.1 226.8 253.5 233.1C300.3 261.6 311.7 268.6 336.2 283.6C350.5 292.3 365 301.2 368.4 303.3C371.8 305.4 378.3 309.3 382.7 312C387.1 314.7 393.6 318.7 397 320.8C402.4 324.1 422.8 336.6 442.8 348.8C459.3 358.9 473.2 361.1 489.1 356.2C494.7 354.4 494.5 354.5 511.8 343.9C520 338.9 532.6 331.1 539.9 326.6C547.2 322.1 559.1 314.8 566.3 310.4C573.5 306 585.3 298.8 592.4 294.4C599.6 290 611.7 282.6 619.3 277.9C632.3 270 634.9 268.4 645.1 262.2C647.4 260.8 654.1 256.7 660.1 253.1C666 249.4 672.7 245.4 674.9 244C677.1 242.7 680.8 240.4 683 239.1C685.2 237.7 691.9 233.6 697.8 230C703.8 226.4 710.5 222.3 712.7 221C714.9 219.6 718.5 217.4 720.8 216C723 214.7 729.7 210.6 735.6 207C745.9 200.7 749 198.8 758.4 193.1C760.7 191.7 767.4 187.6 773.4 183.9C783.7 177.7 786.8 175.8 796.2 170C798.5 168.6 805.2 164.5 811.2 160.9C817.1 157.3 823.8 153.2 826 151.9C828.2 150.5 831.9 148.3 834.1 146.9C836.3 145.6 843 141.5 848.9 137.9C859.2 131.7 861.8 130 871.8 124C874 122.6 879.3 119.3 883.5 116.8L891.1 112.1L897.9 110.7L904.7 109.3L911.5 110.7C915.2 111.5 918.5 112.3 918.7 112.4C919 112.6 920.3 113.5 921.6 114.5L924.1 116.4L925.1 115.4C927.4 113.3 930.8 112.7 932.3 114.1C934.1 115.9 934.8 133.4 934.8 175.7C934.8 226.6 934.5 229.5 927.7 243.7C920.5 258.8 914.2 264.7 887.9 280.9C867.5 293.5 841.8 308.5 839.3 309.3C837.6 309.8 836.7 310.4 836.2 311.3C835.9 311.9 835.1 312.7 834.5 313C833.4 313.6 815.7 324.4 809.3 328.4C807.1 329.8 805 330.9 804.6 330.9C802.9 330.9 727.7 377.1 670.2 413.5C663.1 417.9 652.6 424.5 635.2 435.1C619.8 444.6 607.3 452.2 597.4 458.3C591.7 461.8 585.3 465.8 583.1 467.1C581 468.4 576.4 471.2 573 473.3C569.6 475.4 563.1 479.3 558.7 482C554.3 484.7 547.8 488.7 544.4 490.8
103C541 492.9 534.6 496.8 530.1 499.5C525.7 502.3 519.4 506.1 516.1 508.1C498.2 519.1 491 523.4 489.6 524C488.7 524.3 487.5 525 487 525.4C486.1 526.3 474.2 529.1 472.2 529C471.7 529 468.9 528.4 465.9 527.8ZM480.5 496.2C487 492.1 490.3 490.1 509.2 478.6C513.1 476.2 519.5 472.2 523.4 469.8C527.4 467.4 533.8 463.5 537.7 461.1C541.7 458.7 548.1 454.7 552 452.3C559.1 448 563.4 445.3 583.6 433C595 425.9 610.5 416.5 621.2 409.9C625.1 407.5 631.5 403.6 635.4 401.2C639.3 398.8 645.8 394.8 649.7 392.4C653.6 390 660 386.1 664 383.7C667.9 381.3 674.3 377.3 678.2 374.9C682.2 372.5 688.6 368.6 692.5 366.2C696.5 363.8 702.9 359.8 706.9 357.3C718.1 350.5 727.8 344.6 735.4 339.9C743 335.2 752.7 329.3 763.8 322.5C767.8 320 774.2 316.1 778.1 313.7C790.8 305.9 799.2 300.8 813.7 291.9C821.6 287.1 834 279.4 841.3 274.9C848.7 270.4 857.8 264.8 861.6 262.5C865.4 260.2 873.2 255.4 878.9 251.9C892.8 243.4 896.3 240.5 901.6 233.3C905.9 227.5 905.6 231.4 905.6 181.6C905.6 143.6 905.5 137.9 904.9 137.9C904.3 137.9 899.9 140.5 877.7 154.1C875.5 155.5 868.7 159.6 862.8 163.2C852.4 169.5 849.3 171.4 840 177.1C837.7 178.5 830.9 182.6 825 186.3C819 189.9 812.3 194 810 195.4C800.6 201.1 797.5 203 787.2 209.3C781.3 212.9 774.5 217 772.2 218.4C762.8 224.2 759.8 226 749.4 232.3C743.5 235.9 736.8 240 734.6 241.4C732.4 242.7 728.7 245 726.5 246.3C724.3 247.7 717.6 251.7 711.7 255.4C705.7 259 699 263.1 696.7 264.5C687.3 270.2 684.2 272.1 673.9 278.4C667.9 282 661.2 286.1 658.9 287.5C649.8 293.1 646.5 295.1 636.1 301.4C630.1 305.1 621.6 310.2 617.2 313C612.8 315.7 600.6 323.1 590.3 329.4C564.4 345.2 535.2 363 522.8 370.6C517.1 374 511.4 377.5 510.1 378.2C500.4 383.9 480.7 387.8 468.3 386.6C459.1 385.8 453.2 384.6 446.3 382.4C438.9 380 440.2 380.8 402.3 357.7C389.2 349.7 386.5 348 376.5 341.9C374.2 340.5 367.5 336.4 361.5 332.8C355.6 329.1 348.9 325.1 346.6 323.7C344.4 322.4 340.8 320.1 338.6 318.8C336.4 317.4 326 311.1 315.5 304.7C305.1 298.3 288 287.9 277.5 281.5C267.1 275.2 250.1 264.8 239.8 258.5C229.4 252.2 212.5 241.9 202.2 235.6C191.9 229.3 174.8 218.9 164.3 212.5C146.2 201.5 143.1 199.6 133.2 193.5C130.9 192.1 124.2 188 118.2 184.4C112.3 180.8 105.6 176.7 103.4 175.4C101.2 174 97.5 171.8 95.3 170.4C90.9 167.7 57 147 47.6 141.3C43.8 139 41.5 137.9 41.3 138.2C41 138.5 40.9 155.2 40.9 183.3L41.1 227.9L42.4 230.4C44.3 233.7 47.9 237.6 52.7 241.4C56.3 244.2 59.3 246.4 73.6 256C86.4 264.8 109.7 279.3 131.8 292.5C149.7 303.1 152.2 304.6 174.9 317.8C191.3 327.3 191.5 327.4 210.9 338.7C226.1 347.7 244 358.5 265.9 372.2C270.7 375.1 276.7 378.9 279.3 380.4C283.6 383.1 308.3 398.6 314.3 402.4C315.8 403.3 318.5 405 320.3 406.1C322 407.2 326.8 410.2 330.9 412.7C337.8 417.1 354.2 427.4 362 432.3C369.2 436.8 377.2 441.8 382.9 445.4C386.2 447.5 396.2 453.7 405 459.2C413.9 464.7 422.7 470.2 424.6 471.4C426.5 472.6 432.5 476.4 438 479.7C456.9 491.4 459.9 493.3 465.6 496.8C468.8 498.8 471.9 500.4 472.5 500.4C473 500.5 476.7 498.5 480.5 496.2Z","face":"M465.6 496.8C459.9 493.3 456.9 491.4 438 479.7C432.5 476.4 426.5 472.6 424.6 471.4C422.7 470.2 413.9 464.7 405 459.2C396.2 453.7 386.2 447.5 382.9 445.4C377.2 441.8 369.2 436.8 362 432.3C354.2 427.4 337.8 417.1 330.9 412.7C326.8 410.2 322 407.2 320.3 406.1C318.5 405 315.8 403.3 314.3 402.4C308.3 398.6 283.6 383.1 279.3 380.4C276.7 378.9 270.7 375.1 265.9 372.2C244 358.5 226.1 347.7 210.9 338.7C191.5 327.4 191.3 327.3 174.9 317.8C152.2 304.6 149.7 303.1 131.8 292.5C109.7 279.3 86.4 264.8 73.6 256C59.3 246.4 56.3 244.2 52.7 241.4C47.9 237.6 44.3 233.7 42.4 230.4L41.1 227.9L40.9 183.3C40.9 155.2 41 138.5 41.3 138.2C41.5 137.9 43.8 139 47.6 141.3C57 147 90.9 167.7 95.3 170.4C97.5 171.8 101.2 174 103.4 175.4C105.6 176.7 112.3 180.8 118.2 184.4C124.2 188 130.9 192.1 133.2 193.5C143.1 199.6 146.2 201.5 164.3 212.5C174.8 218.9 191.9 229.3 202.2 235.6C212.5 241.9 229.4 252.2 239.8 258.5C250.1 264.8 267.1 275.2 277.5 281.5C288 287.9 305.1 298.3 315.5 304.7C326 311.1 336.4 317.4 338.6 318.8C340.8 320.1 344.4 322.4 346.6 323.7C348.9 325.1 355.6 329.1 361.5 332.8C367.5 336.4 374.2 340.5 376.5 341.9C378.8 343.3 382.4 345.5 384.5 346.8C390.2 350.3 425.2 371.6 432 375.7C442.2 381.9 453.6 385.5 466.5 386.5C481.6 387.8 499.7 384.3 510.1 378.2C511.4 377.5 517.1 374 522.8 370.6C535.2 363 564.4 345.2 590.3 329.4C600.6 323.1 612.8 315.7 617.2 313C621.6 310.2 630.1 305.1 636.1 301.4C646.5 295.
1031 649.8 293.1 658.9 287.5C661.2 286.1 667.9 282 673.9 278.4C684.2 272.1 687.3 270.2 696.7 264.5C699 263.1 705.7 259 711.7 255.4C717.6 251.7 724.3 247.7 726.5 246.3C728.7 245 732.4 242.7 734.6 241.4C736.8 240 743.5 235.9 749.4 232.3C759.8 226 762.8 224.2 772.2 218.4C774.5 217 781.3 212.9 787.2 209.3C797.5 203 800.6 201.1 810 195.4C812.3 194 819 189.9 825 186.3C830.9 182.6 837.7 178.5 840 177.1C849.3 171.4 852.4 169.5 862.8 163.2C868.7 159.6 875.5 155.5 877.7 154.1C899.9 140.5 904.3 137.9 904.9 137.9C905.5 137.9 905.6 143.6 905.6 181.6C905.6 231.5 905.9 227.6 901.5 233.5C896.1 240.6 892.1 243.8 878.9 251.9C873.2 255.4 865.4 260.2 861.6 262.5C857.8 264.8 848.7 270.4 841.3 274.9C834 279.4 821.6 287.1 813.7 291.9C799.2 300.8 790.8 305.9 778.1 313.7C774.2 316.1 767.8 320 763.8 322.5C752.7 329.3 743 335.2 735.4 339.9C727.8 344.6 718.1 350.5 706.9 357.3C702.9 359.8 696.5 363.8 692.5 366.2C688.6 368.6 682.2 372.5 678.2 374.9C674.3 377.3 667.9 381.3 664 383.7C660 386.1 653.6 390 649.7 392.4C645.8 394.8 639.3 398.8 635.4 401.2C631.5 403.6 625.1 407.5 621.2 409.9C610.5 416.5 595 425.9 583.6 433C563.4 445.3 559.1 448 552 452.3C548.1 454.7 541.7 458.7 537.7 461.1C533.8 463.5 527.4 467.4 523.4 469.8C519.5 472.2 513.1 476.2 509.2 478.6C490.3 490.1 487 492.1 480.5 496.2C476.7 498.5 473 500.5 472.5 500.4C471.9 500.4 468.8 498.8 465.6 496.8Z","poly":[[13.0,121.1],[13.0,227.0],[28.6,259.3],[112.0,314.6],[244.3,392.0],[444.7,517.7],[471.4,528.8],[492.1,522.8],[899.4,273.6],[920.6,256.0],[933.9,223.8],[933.0,115.5],[923.8,116.5],[905.4,109.5],[891.1,112.3],[497.2,353.3],[467.2,358.8],[447.9,352.3],[56.7,113.2],[41.5,109.5],[22.6,116.9],[15.3,114.6]]},"wingL":{"ink":"M333.7 798.6C333 798.5 328 795.7 286.9 772.8C278.1 767.9 263.9 759.9 255.3 755.1C214.7 732.4 207.7 728.5 192.9 720.2C169.6 707.2 170.8 707.8 168.7 707.8C165.6 707.8 144 720 122.8 733.8C89.6 755.5 71 765.6 59 768.6C53.5 770 45.2 769.9 39.9 768.4C26.7 764.8 16.7 754.1 13.5 740.5C12.9 737.9 12.9 728.5 13.6 695.8C14.2 663.6 13.6 613.7 11.8 553.2C11 526.1 10.9 436.5 11.7 421C13.1 395.1 13.5 391.6 16.3 383.3C19.8 373.2 25.1 366.6 34.4 361C40.5 357.3 42.1 356.3 80.6 332.6C100.7 320.2 110.1 314.7 111.1 314.6C112 314.5 115.6 312.8 119.3 310.8C128.6 305.5 128 305.8 135.6 304.2L142.5 302.8L147.8 303.9C152.2 304.8 153.7 305.4 156.9 307.3C159.1 308.6 167.2 313.3 174.9 317.8C191.2 327.3 191.4 327.4 210.9 338.7C226.1 347.7 244 358.5 265.9 372.2C270.7 375.1 276.7 378.9 279.3 380.4C285.9 384.5 294.9 390.2 298.4 392.4C309.5 399.6 312.8 401.7 330.4 413.1C337.2 417.5 347.9 424.4 354 428.4C360.2 432.4 365.6 436.2 366.1 436.8C367.5 438.5 367.5 465.2 366.2 466.8C364.8 468.5 362.5 468.2 358.9 465.8C357.1 464.7 349.7 459.9 342.3 455.1C334.8 450.3 323.2 442.7 316.5 438.4C301.2 428.5 297 425.7 291.7 422.4C289.4 420.9 286.2 418.8 284.6 417.8C277.9 413.5 265.8 405.8 256.5 400C251 396.7 245.8 393.4 244.9 392.9C244.1 392.3 240.4 390.1 236.9 387.9C233.3 385.8 227.5 382.3 224 380.1C220.4 378 204.5 368.3 188.5 358.8C172.5 349.2 155.8 339.1 151.3 336.4C146.8 333.6 142.7 331.4 142.3 331.4C141.6 331.4 128.6 338.5 102.1 353.5C98.2 355.7 86.2 362.4 75.4 368.5C49.8 382.8 50.8 382.3 50.8 382.9C50.8 383.3 57.3 387.6 65.3 392.5C73.2 397.5 82.9 403.4 86.7 405.8C90.5 408.1 98.1 412.8 103.5 416.1C113.3 422.2 119.5 426 130.9 433.1C134.2 435.1 141.4 439.5 146.8 442.9C152.3 446.2 159.3 450.6 162.5 452.5C165.6 454.5 172.7 458.9 178.1 462.2C187.8 468.2 193.5 471.7 205.6 479.1C208.8 481.2 216 485.6 221.4 488.9C226.9 492.3 233.9 496.7 237.1 498.6C240.3 500.6 250 506.6 258.8 512C267.5 517.4 277.3 523.4 280.4 525.3C283.6 527.3 290.6 531.7 296.1 535C301.5 538.4 308.6 542.7 311.7 544.7C314.9 546.6 324 552.3 332 557.2C347.8 567 348.4 567.4 354.4 575.8C359.7 583.2 364.1 592.9 366.1 601.5C366.4 603 366.6 628.5 366.6 687.2C366.6 768.9 366.6 770.9 365.7 775.3C364.6 780.4 364.9 779.8 361.5 784.3C358.2 788.6 352.7 792.9 349.3 793.7C348.1 794.1 345.1 794.7 342.8 795.1C339.5 795.8 338.5 796.2 337.5 797.2C336.4 798.5 335.2 799 333.7 798.6ZM338.9 686.7L338.9 606.3L337.9 603.5C336.8 600.3 335.1 597.4 331.8 592.8C327.7 587.4 324.6 584.8 315.4 579.2C305.6 573.2 297.8 568.5 254 541.9C227.5 525.7 219.4 520.7 192.6 504.4C187.7 501.5 183.4 499.1 183 499.1C181.8 499.1 181.9 682 183.1 683C183.5 683.4 188.8 686.4 194.7 689.6C200.7 692.8 207.7 696.6 210.4 698.1C213.1 699.6 221.2 704 228.5 708C235.8 711.9 247.7 718.4 255 722.3C262.3 726.3 270.4 730.7 273 732.2C277.6 734.7 312 753.4 326.3 761.1C329.8 763.1 333.9 765.3 335.4 766.1C337 767 338.4 767.5 338.6 767.4C338.8 767.3 338.9 731 338.9 686.7ZM57.3 738.2C74.3 730.7 114.3 707.5 146.8 686.4C149.2 684.8 151.8 683.2 152.5 682.8L153.7 682L153.7 581.8L153.7 481.6L151.8 479.6C150.6 478.4 146.3 475.5 141.3 472.5C123.5 462 93.4 444 89.2 441.5C86.8 440 76 433.6 65.3 427.2C54.5 420.8 44.9 415.1 44 414.5C43 413.9 41.9 413.4 41.5 413.4C40.4 413.4 40.5 735.2 41.6 736.7C42 737.3 43.4 738.5 44.7 739.4C48.2 741.7 49.7 741.5 57.3 738.2Z","face":"M335.4 766.1C333.9 765.3 329.8 763.1 326.3 761.1C312 753.4 277.6 734.7 273 732.2C270.4 730.7 262.3 726.3 255 722.3C247.7 718.4 235.8 711.9 228.5 708C221.2 704 213.1 699.6 210.4 698.1C207.7 696.6 200.7 692.8 194.7 689.6C188.8 686.4 183.5 683.4 183.1 683C181.9 682 181.8 499.1 183 499.1C183.4 499.1 187.7 501.5 192.6 504.4C219.4 520.7 227.5 525.7 254 541.9C297.8 568.5 305.6 573.2 315.4 579.2C324.6 584.8 327.7 587.4 331.8 592.8C335.1 597.4 336.8 600.3 337.9 603.5L338.9 606.3L338.9 686.7C338.9 731 338.8 767.3 338.6 767.4C338.4 767.5 337 767 335.4 766.1ZM44.7 739.4C43.4 738.5 42 737.3 41.6 736.7C40.5 735.2 40.4 413.4 41.5 413.4C41.9 413.4 43 413.9 44 414.5C44.9 415.1 54.5 420.8 65.3 427.2C76 433.6 86.8 440 89.2 441.5C93.1 443.8 117.8 458.5 141.3 472.5C146.5 475.6 150.6 478.4 151.8 479.6L153.7 481.7L153.7 581.8L153.7 682L152.5 682.8C151.8 683.2 149.2 684.8 146.8 686.4C114.3 707.5 74.3 730.7 57.3 738.2C49.7 741.5 48.2 741.7 44.7 739.4ZM350 570C347.2 566.5 350.4 568.6 311.7 544.7C308.6 542.7 301.5 538.4 296.1 535C290.6 531.7 283.6 527.3 280.4 525.3C277.3 523.4 267.5 517.4 258.8 512C250 506.6 240.3 500.6 237.1 498.6C233.9 496.7 226.9 492.3 221.4 488.9C216 485.6 208.8 481.2 205.6 479.1C193.5 471.7 187.8 468.2 178.1 462.2C172.7 458.9 165.6 454.5 162.5 452.5C159.3 450.6 152.3 446.2 146.8 442.9C141.4 439.5 134.3 435.1 131 433.1C127.8 431.1 122.5 427.9 119.3 425.9C116.1 423.9 109 419.5 103.5 416.1C98.1 412.8 90.5 408.1 86.7 405.8C82.9 403.4 73.2 397.5 65.3 392.5C57.3 387.6 50.8 383.3 50.8 382.9C50.8 382.3 49.8 382.8 75.4 368.5C86.2 362.4 98.2 355.7 102.1 353.5C128.6 338.5 141.6 331.4 142.3 331.4C142.7 331.4 146.8 333.6 151.3 336.4C155.8 339.1 172.5 349.2 188.5 358.8C204.5 368.3 220.4 377.9 223.9 380C227.4 382.2 233.2 385.7 236.8 387.9C240.4 390.1 244.1 392.3 244.9 392.9C245.8 393.4 251 396.7 256.5 400C265.8 405.8 277.9 413.5 284.6 417.8C286.2 418.8 289.4 420.9 291.7 422.4C297.2 425.9 301.8 428.9 316.6 438.4C323.4 442.9 334.4 449.9 340.9 454.2L352.8 461.9L352.8 517.6C352.8 548.2 352.7 573.3 352.6 573.2C352.6 573.2 351.4 571.8 350 570Z","poly":[[29.1,364.3],[14.8,388.3],[11.1,433.0],[13.0,738.0],[20.8,755.9],[39.7,768.4],[63.2,767.4],[170.5,707.6],[332.7,798.3],[357.6,788.6],[366.3,771.6],[366.3,603.0],[352.5,573.5],[352.5,462.5],[366.3,466.6],[366.3,437.1],[240.6,356.5],[150.3,304.4],[128.6,305.8]]},"wingR":{"ink":"M611.3 797.8C610.8 797.7 610 797.2 609.7 796.8C609.4 796.3 608.6 795.9 607.9 795.9C606.3 795.8 595.5 793.5 594.9 793.1C590 790.3 582.7 782.6 581.2 778.9C579.9 7
10375.6 579 746.2 578.7 700.9C578.4 664.4 579.2 607.7 579.9 604.6C580.2 603.4 580.6 601.7 580.9 600.7C583.1 591.4 588.1 581.1 594.5 572.5C598.4 567.3 599.1 566.8 612.6 558.5C619.3 554.4 629.1 548.4 634.3 545.2C645.9 537.9 658.3 530.2 678.9 517.5C687.7 512.1 699.1 505.1 704.3 501.8C709.5 498.6 720.9 491.6 729.6 486.2C749.5 473.9 762.7 465.7 772.5 459.7C776.6 457.1 788.9 449.5 799.6 442.9C810.4 436.2 822.7 428.6 826.9 426C831.2 423.4 838.1 419.1 842.4 416.4C846.6 413.8 858.9 406.2 869.7 399.6C880.4 392.9 890.8 386.5 892.6 385.4C894.5 384.3 895.9 383.1 895.8 382.8C895.7 382.5 893.5 381 890.8 379.5C888.2 377.9 882.7 374.7 878.5 372.2C874.4 369.8 867.7 365.8 863.7 363.4C859.6 361 853 357.1 848.9 354.6C844.9 352.2 838.2 348.3 834.1 345.8C811 332.1 811.2 332.2 808 331.6C806.3 331.2 804.7 330.9 804.4 330.9C802.8 330.9 721.9 380.7 667.4 415.2C661.7 418.8 653.1 424.3 648.3 427.3C643.5 430.3 634 436.3 627.1 440.7C580.1 470.3 581.8 469.3 579.8 466.8C578.6 465.4 578.5 438.6 579.7 437C580 436.5 585.6 432.7 592 428.7C598.3 424.7 608.5 418.3 614.5 414.5C632.4 403.3 656.1 388.5 664 383.7C670.9 379.4 675.1 376.9 692.5 366.2C696.5 363.8 702.9 359.8 706.8 357.4C710.7 355 717.2 351.1 721.1 348.7C725 346.2 731.5 342.3 735.5 339.8C739.5 337.4 745.9 333.4 749.8 331.1C756.8 326.8 762.2 323.5 781 311.9C786.6 308.5 791.5 305.5 792 305.2C792.6 305 795.7 304.2 799 303.5L805 302.3L814.2 304.2L823.4 306.1L829.5 309.6C832.9 311.5 835.8 313.4 835.9 313.7C836.2 314.4 844.4 319.9 851.5 324.1C857.4 327.6 860.4 329.3 877.3 338.7C919.8 362.2 927.8 370.1 932.8 393.6C933.2 395.5 933.3 399.3 933 407.7C932.4 427 932.6 514.4 933.2 535.5C935.8 617.2 936.3 662.2 935.2 695.6C933.9 730.9 933.4 736.8 930.6 745.9C929.5 749.3 925.7 755.8 923.1 758.6C914.2 768.3 901.4 771.5 885.8 767.9C883.9 767.5 870.1 759.9 830.5 737.5L777.8 707.7L776.2 708.5C775.3 708.9 763.1 715.6 749 723.4C734.9 731.2 713.6 743 701.6 749.6C689.7 756.3 670.1 767.1 658.1 773.7C646.1 780.4 631.6 788.4 625.7 791.6C615.4 797.4 613.5 798.2 611.3 797.8ZM614 764.5C616.9 762.9 621.5 760.4 624.1 758.9C629.2 756.1 663.3 737.6 681.5 727.7C687.3 724.5 694.2 720.8 696.9 719.3C699.6 717.8 713.7 710.2 728.2 702.2C762.8 683.5 763.6 683 763.9 682.2C764 681.8 764.2 640.5 764.2 590.5C764.2 510.7 764.1 499.5 763.5 499.2C762.7 498.9 762 499.3 748 507.8C737.8 514.1 730.6 518.4 705.7 533.6C699.7 537.3 693.9 540.8 692.8 541.5C691.6 542.1 685.9 545.6 680.1 549.2C674.3 552.7 664.7 558.5 658.9 562.1C653.1 565.6 643.4 571.5 637.5 575.1C623.8 583.4 623.7 583.5 619.5 587.8C613 594.5 608.9 601.2 607.7 606.9C607.2 609.3 607.1 623.9 607.1 688.2C607.1 765.3 607.1 768.2 608.4 767.5C608.6 767.4 611.1 766 614 764.5ZM900.5 740.1C901.4 739.6 903 738.4 903.9 737.4L905.6 735.7L905.6 574.5C905.6 432.2 905.5 413.4 904.9 413.4C904.3 413.4 896.8 417.9 873.4 432.3C869.9 434.4 859.7 440.7 850.6 446.2C829.7 459 827.5 460.3 819.9 465C816.5 467.1 812 469.8 810 471C808 472.3 803.2 475.2 799.4 477.5L792.5 481.8L792.4 581.4C792.3 675.7 792.3 681 793.1 681.6C793.5 682 816.7 695.6 844.6 711.9C900.8 744.7 896.1 742.3 900.5 740.1Z","face":"M607.6 767.1C606.8 766.3 606.8 611 607.7 606.9C608.9 601.2 613 594.5 619.5 587.8C623.7 583.5 623.8 583.4 637.5 575.1C643.4 571.5 653.1 565.6 658.9 562.1C664.7 558.5 674.3 552.7 680.1 549.2C685.9 545.6 691.6 542.1 692.8 541.5C693.9 540.8 699.7 537.3 705.7 533.6C730.6 518.4 737.8 514.1 748 507.8C762 499.3 762.7 498.9 763.5 499.2C764.1 499.5 764.2 510.7 764.2 590.5C764.2 640.5 764 681.8 763.9 682.2C763.6 683 762.8 683.5 728.2 702.2C713.7 710.2 699.6 717.8 696.9 719.3C694.2 720.8 687.3 724.5 681.5 727.7C663.3 737.6 629.2 756.1 624.1 758.9C620.3 761 609.9 766.7 608.4 767.5C608.3 767.6 607.9 767.4 607.6 767.1ZM844.6 711.9C816.7 695.6 793.5 682 793.1 681.6C792.3 681 792.3 675.7 792.4 581.4L792.5 481.8L799.4 477.5C803.2 475.2 808 472.3 810 471C812 469.8 816.5 467.1 819.9 465C827.5 460.3 829.7 459 850.6 446.2C859.7 440.7 869.9 434.4 873.4 432.3C896.8 417.9 904.3 413.4 904.9 413.4C905.5 413.4 905.6 432.2 905.6 574.5L905.6 735.7L903.9 737.4C901.9 739.5 899.5 740.8 897 741.2C895.3 741.5 892.9 740.1 844.6 711.9ZM593.2 517.8L593.2 461.9L605.1 454.5C611.6 450.4 622.1 443.8 628.3 439.9C634.5 436 643.5 430.3 648.3 427.3C653.1 424.3 661.7 418.8 667.4 415.2C706.2 390.7 760 357.3 787 341.1C802.9 331.5 804.1 330.9 805.6 331.1C806.5 331.3 808.1 331.6 809.1 331.8
103C810.2 332 814.2 334 818.8 336.7C823.1 339.3 830 343.4 834.1 345.8C838.2 348.3 844.9 352.2 848.9 354.6C853 357.1 859.6 361 863.7 363.4C867.7 365.8 874.4 369.8 878.5 372.2C882.7 374.7 888.2 377.9 890.8 379.5C893.5 381 895.7 382.5 895.8 382.8C895.9 383.1 894.5 384.3 892.6 385.4C890.8 386.5 880.4 392.9 869.7 399.6C858.9 406.2 846.6 413.8 842.4 416.4C838.1 419.1 831.2 423.4 826.9 426C822.7 428.6 810.4 436.2 799.6 442.9C788.9 449.5 776.6 457.1 772.5 459.7C762.7 465.7 749.5 473.9 729.6 486.2C720.9 491.6 709.5 498.6 704.3 501.8C699.1 505.1 687.7 512.1 678.9 517.5C658 530.4 645.8 538 634.3 545.2C629.1 548.4 619.2 554.5 612.4 558.7C598.9 567 598.4 567.4 595.5 571.2C594.5 572.6 593.5 573.7 593.4 573.7C593.3 573.7 593.2 548.6 593.2 517.8Z","poly":[[823.4,306.3],[805.9,302.6],[791.1,305.8],[579.2,438.0],[579.7,466.6],[593.5,462.5],[593.5,574.4],[582.4,595.1],[579.2,615.9],[581.5,779.4],[595.3,793.2],[615.1,797.4],[777.3,707.6],[883.7,767.4],[901.2,769.3],[914.1,765.6],[930.3,746.7],[935.8,677.6],[932.1,390.1],[926.1,374.9],[914.6,362.0]]},"spine":{"ink":"M372.1 975C355.1 971.2 343.9 959.4 339.4 940.5C338.5 936.5 338.6 567.5 339.6 566.1C341.3 563.5 344.9 564.4 348.6 568.5C357 577.8 363.4 590 366.1 601.5C366.4 603 366.6 651.2 366.6 769C366.6 910.8 366.7 934.8 367.2 936.9C368.4 941.6 370.2 943.9 374 946C376.7 947.4 377.1 947.5 381.6 947.5C388.5 947.5 386 948.7 413.8 932.3C419.4 929 431.2 922.1 440 916.9C452.9 909.4 456.4 907.1 457.4 905.7L458.7 903.9L458.7 714.4C458.7 526.5 458.7 524.9 457.8 524.4C457.3 524.2 455.7 523.6 454.2 523.1C448.3 521.2 394.6 489.1 361.5 467.5C359.9 466.4 354.6 463 349.8 459.9C338.1 452.5 338.9 453.6 338.9 445.2C338.9 436.4 338.6 436.8 346.9 435.1L353.2 433.8L359.8 435.1C367.1 436.6 365.8 436 380.9 445.7C404.7 461 436.4 480.4 456 491.5C461.7 494.7 467.5 498 468.9 498.9C472.1 500.8 473.2 500.8 475.5 499.2C476.5 498.6 478.2 497.7 479.4 497.2C482.6 496.1 527.5 468.9 568.8 443.2L579.7 436.5L586.2 435.1L592.8 433.8L599.1 435.1C607.4 436.8 607.1 436.3 607.1 445.3C607.1 449 606.9 452.5 606.6 453C606.1 453.9 579.5 470.7 543.4 492.8C512.8 511.5 494.4 522.3 491 523.5L487.5 524.7L487.4 712.4L487.3 900.1L488.4 903.8C489.5 907.2 489.8 907.6 491.6 908.7C495.1 910.9 554.3 945.4 556.3 946.4C559.1 947.8 568.3 947.8 571.3 946.4C576.6 943.9 578.5 939.7 579.5 928.2C581.7 900.9 582.1 839.9 580.3 778.7C580.1 770.8 579.7 757.6 579.5 749.5C577.4 675.9 577.7 614.6 580.3 603.2C580.5 602.1 580.9 600.3 581.2 599.3C583.9 587.4 596.4 567 601.8 565.5C603.9 564.9 605.6 565.4 606.5 566.6C607.1 567.6 607.1 572.4 606.8 613.9C606.4 675.7 606.4 668.5 607 736.1C607.6 799.5 607.4 938.1 606.6 940.7C606.4 941.4 606 943 605.7 944.1C600.3 964.1 584.6 975.9 563.4 975.9C557.3 975.9 552.3 975.2 549.9 973.9C546 971.8 528 961.9 520.1 957.5C515 954.7 506.6 950.1 501.5 947.3C496.3 944.5 488.2 940 483.4 937.3C476.5 933.5 474.3 932.5 472.9 932.5C470 932.6 460.7 937.6 439.3 950.6C414.3 965.9 404.7 971.3 402.9 971.3C402.5 971.3 402 971.5 401.8 971.7C401.3 972.2 392.1 974.5 387.5 975.4C383.5 976.1 376.6 976 372.1 975Z","face":"M378.3 947.4C374.3 946.9 370.3 944.1 368.6 940.7C366.5 936.7 366.6 944.4 366.6 769C366.6 651.2 366.4 603 366.1 601.5C364.2 593.4 360.5 585 355.6 577.5L352.8 573.3L352.8 517.8C352.8 487.2 352.9 462.2 353.1 462.2C353.3 462.3 354.9 463.3 356.8 464.4C393.8 488.5 448.2 521.3 453.9 523C454.9 523.3 456.4 523.9 457.2 524.2L458.7 524.9L458.7 714.4L458.7 903.9L457.4 905.7C456.4 907.1 452.9 909.4 440 916.9C431.2 922.1 419.4 929 413.8 932.3C390.4 946.1 390.7 945.9 388 946.8C386.1 947.5 381.1 947.8 378.3 947.4ZM556.2 946.3C554.1 945.3 494.6 910.6 491.6 908.7C489.8 907.6 489.5 907.2 488.4 903.8L487.3 900.1L487.4 712.4L487.5 524.7L489.6 524.1C493.5 522.8 495.3 521.8 512.9 511.3C535.6 497.7 552.5 487.3 580.5 469.9C587.2 465.7 592.9 462.2 593 462.2C593.1 462.2 593.2 487.4 593.2 518.2L593.2 574.1L591.2 577.3C586.5 584.7 582.7 592.9 581.2 599.3C580 604.2 579.8 605.7 579.4 610.8C577.6 636 577.6 684.5 579.5 749.5C579.7 757.6 580.1 770.8 580.3 778.7C582.4 852.6 581.3 929.9 578.1 937.9C576.5 941.9 575.9 942.9 574.8 944.1C572.2 946.7 569.9 947.4 563.7 947.3C558.8 947.3 557.7 947.2 556.2 946.3Z","poly":[[340.5,436.7],[339.2,451.9],[353.0,462.0],[353.0,573.0],[344.7,565.2],[339.2,567.0],[339.2,939.3],[342.8,950.8],[360.8,971.1],[385.7,975.7],[407.8,969.2],[473.7,931.9],[550.6,974.3],[582.9,972.0],[598.1,960.0],[606.8,939.8],[606.8,567.5],[601.3,565.7],[593.0,573.5],[593.0,462.0],[606.4,453.2],[606.8,438.5],[593.0,433.9],[579.7,436.7],[471.4,500.7],[366.3,436.7]]}},"full":"M374.6 975.5C374.1 975.3 373.1 975.1 372.4 975C369.6 975 362.7 972.3 358.5 969.7C349.5 964.1 342.6 953.6 339.5 940.8C339.1 939.2 338.9 923.5 338.9 883.4C338.9 853 338.8 822.2 338.6 814.9L338.3 801.7L336.7 800.7C335.8 800.1 327.8 795.6 318.9 790.6C310 785.7 295.7 777.7 287 772.8C278.3 768 264.2 760.1 255.5 755.2C246.9 750.4 232.8 742.5 224.2 737.7C215.6 732.9 201.4 725 192.8 720.2C169.6 707.2 170.8 707.8 168.7 707.8C165.6 707.8 144 720 122.8 733.8C102.6 747 89 755.2 78.4 760.6C68.6 765.5 64.2 767.3 57.6 769C55.3 769.6 43.8 769.7 42 769.1C41.2 768.9 39.7 768.4 38.5 768.1C28.2 765.1 18.6 755.8 14.8 744.9C12.8 739.1 12.8 738 13.6 695.8C14.2 663.6 13.6 613.7 11.8 553.2C11 526.1 10.9 436.5 11.7 421C13.1 395.1 13.5 391.6 16.3 383.3C19.8 373.1 25.1 366.7 34.6 360.9C43.6 355.4 47.5 353 79 333.6C108 315.7 109.1 315 109.7 313.2C110.7 310.5 112.1 311.6 92.9 299.6C91 298.4 77.2 289.8 62.3 280.4C47.3 271 33.6 262.4 31.9 261.3
103C30.1 260.2 28 258.8 27.3 258.2C24.4 255.5 18 244.4 16.2 239C15.8 237.7 15.3 236.3 15.1 235.8C14.7 234.9 14.1 232.7 13.4 229.3C12 222.5 11.1 182.6 11.8 153.7C12.8 111.9 13.7 105.8 20.8 94.8C22.5 92.2 27 86.7 28.9 85C39.1 75.7 97.5 38.3 113.2 31.1C118.8 28.4 124.5 26.9 132.5 25.8C139.7 24.8 150.1 25.3 156.9 26.8C158.1 27 159.6 27.4 160.4 27.5C161.2 27.7 162.2 28 162.7 28.2C163.2 28.4 164.5 28.8 165.5 29.1C170.5 30.7 177.4 34.8 212.2 56.3C215.4 58.3 224 63.6 231.2 68C238.5 72.5 250.4 79.8 257.6 84.2C264.8 88.6 276.7 95.9 283.9 100.3C291.1 104.8 303 112 310.3 116.5C317.5 121 326.1 126.2 329.3 128.2C347.5 139.5 361.7 148.1 362.7 148.5C364.7 149.2 366.8 148.5 373.4 144.9C378.8 141.8 394.8 131.5 402.6 125.9C426.6 108.9 446.3 98 457.1 95.8C459.8 95.3 480.1 95.1 483.1 95.6C484.3 95.8 486 96.2 487.1 96.5C488.1 96.7 489.7 97.2 490.7 97.4C494.2 98.3 499 99.8 499.5 100.2C499.7 100.4 500.2 100.6 500.5 100.6C501.8 100.6 511.6 105.4 518.9 109.6C523.2 112 534.6 119.6 549.9 130.1C561 137.7 572 144.1 579.4 147.3C584.1 149.4 582.2 150.1 597.9 140.4C604.3 136.4 610.5 132.6 611.7 131.9C612.8 131.2 618.2 127.9 623.7 124.5C633.4 118.5 655.8 104.7 667 97.8C670.1 95.8 677.6 91.2 683.7 87.5C689.7 83.8 699.5 77.7 705.5 74C711.6 70.3 719.1 65.6 722.2 63.7C725.4 61.8 735.2 55.7 743.9 50.4C752.6 45 762.9 38.6 766.8 36.3C770.6 33.9 775.1 31.4 776.9 30.7C778.6 30.1 780.5 29.4 781 29.2C781.9 28.8 784.7 28 787.7 27.3C801.1 24.1 814.9 24.6 827.5 28.8C841.7 33.5 905.5 73.8 918.3 86.1C922.1 89.7 928.6 98.5 928.6 99.9C928.6 100 929 101 929.5 102.1C934 112.1 934.8 123.7 934.8 175.7C934.8 224.2 934.5 228.4 929.3 240.5C925.8 248.6 921.2 256 917 260.2C910.6 266.5 896.9 275.7 871.1 291.1C865.6 294.3 857 299.4 851.9 302.5C842.6 308.1 839.4 309.7 837.9 309.7C836.8 309.7 836.7 309.9 836.3 312.3L836 314.2L841.2 317.6C849.3 323 854.2 325.8 877.3 338.7C919.9 362.3 927.8 370.1 932.8 393.6C933.2 395.5 933.3 399.3 933 407.7C932.4 427 932.6 514.4 933.2 535.5C935.8 617.2 936.3 662.2 935.2 695.6C933.9 731.1 933.4 736.8 930.5 746C928.1 753.9 921.2 762 914.1 765.5C906 769.6 893.1 770.6 884.7 767.8C883.9 767.5 859.5 753.9 830.5 737.5L777.8 707.7L776.2 708.5C775.3 708.9 763.1 715.6 749 723.4C734.9 731.2 713.6 743.1 701.5 749.7C689.5 756.4 670.2 767 658.7 773.4C647.1 779.8 631.1 788.7 622.9 793.2L608.1 801.4L607.8 824C607.7 836.5 607.4 867.4 607.3 892.8C607.1 919.4 606.8 939.6 606.6 940.6C603.6 952.4 600.7 957.8 594.1 964.4C589.7 968.7 582.2 973.1 576.9 974.3C576.1 974.4 574.5 974.8 573.2 975.1C570.6 975.7 556.8 975.8 553.6 975.3C552.5 975.1 550.9 974.5 550.2 974.1C549.4 973.6 544.4 970.9 539.1 968C533.8 965.1 525.2 960.4 520.1 957.5C515 954.7 506.6 950.1 501.5 947.3C496.3 944.5 488.2 940 483.4 937.3C476.5 933.5 474.3 932.5 472.9 932.5C470 932.6 460.7 937.6 439.3 950.6C414 966 404.7 971.3 402.8 971.3C402.4 971.3 402 971.5 401.9 971.7C401.7 971.9 401.1 972.2 400.6 972.4C400 972.5 398.7 972.9 397.7 973.2C394.6 974 392.3 974.6 389.6 975.1C386 975.7 375.7 976 374.6 975.5ZM388 946.8C390.7 945.9 390.4 946.1 413.8 932.3C419.4 929 431.2 922.1 440 916.9C452.9 909.4 456.4 907.1 457.4 905.7L458.7 903.9L458.7 714.4L458.7 524.9L457.2 524.2C456.4 523.9 454.9 523.3 453.9 523C448.4 521.3 395.2 489.3 360.4 466.8C357.8 465.1 349.7 459.9 342.3 455.1C304.4 430.5 298.2 426.5 291.7 422.4C289.4 420.9 286.2 418.8 284.6 417.8C277.9 413.5 265.8 405.8 256.5 400C251 396.7 245.8 393.4 244.9 392.9C244.1 392.3 240.4 390.1 236.8 387.9C233.2 385.7 227.4 382.2 223.9 380C220.4 377.9 204.5 368.3 188.5 358.8C172.5 349.2 155.8 339.1 151.3 336.4C146.8 333.6 142.7 331.4 142.3 331.4C141.6 331.4 128.6 338.5 102.1 353.5C98.2 355.7 86.2 362.4 75.4 368.5C49.8 382.8 50.8 382.3 50.8 382.9C50.8 383.3 57.3 387.6 65.3 392.5C73.2 397.5 82.9 403.4 86.7 405.8C90.5 408.1 98.1 412.8 103.5 416.1C109 419.5 116.1 423.9 119.4 426C131.5 433.4 137.1 436.9 146.8 442.9C152.3 446.2 159.3 450.6 162.5 452.5C165.6 454.5 172.7 458.9 178.1 462.2C183.6 465.6 190.7 470 193.9 472C197.2 473.9 202.4 477.2 205.7 479.2C208.9 481.2 216 485.6 221.4 488.9C226.9 492.3 233.9 496.7 237.1 498.6C240.3 500.6 250 506.6 258.8 512C267.5 517.4 277.3 523.4 280.4 525.3C283.6 527.3 290.6 531.7 296.1 535C306.3 541.3 307.2 541.9 332.5 557.5C347.3 566.7 347.5 566.9 350.4 570.5C358.5 580.7 363.6 590.8 366.1 601.5C366.4 603 366.6 651.2 366.6 769C366.6 944.4 366.5 936.7 368.6 940.7C370.3 944.1 374.3 946.9 378.3 947.4C381.1 947.8 386.1 947.5 388 946.8ZM571.4 946.4C572.5 945.9 574 944.8 574.8 944.1C575.9 942.9 576.5 941.9 578.1 937.9C581.3 929.9 582.4 852.6 580.3 778.7C580.1 770.8 579.7 757.6 579.5 749.5C577.4
103675.9 577.7 614.6 580.3 603.2C581.4 598.2 581.7 597.3 582.4 595.1C585.1 587.4 589.7 578.9 594.9 572C598.6 567.1 597.9 567.6 612.6 558.6C619.3 554.4 629.1 548.4 634.3 545.2C645.8 538 658 530.4 678.9 517.5C687.7 512.1 699.1 505.1 704.3 501.8C709.5 498.6 720.9 491.6 729.6 486.2C749.5 473.9 762.7 465.7 772.5 459.7C776.6 457.1 788.9 449.5 799.6 442.9C810.4 436.2 822.7 428.6 826.9 426C831.2 423.4 838.1 419.1 842.4 416.4C846.6 413.8 858.9 406.2 869.7 399.6C880.4 392.9 890.8 386.5 892.6 385.4C894.5 384.3 895.9 383.1 895.8 382.8C895.7 382.5 893.5 381 890.8 379.5C888.2 377.9 882.7 374.7 878.5 372.2C874.4 369.8 867.7 365.8 863.7 363.4C859.6 361 853 357.1 848.9 354.6C844.9 352.2 838.2 348.3 834.1 345.8C830 343.4 823.1 339.3 818.8 336.7C814.2 334 810.2 332 809.1 331.8C808.1 331.6 806.5 331.3 805.6 331.1C804.1 330.9 802.9 331.5 787 341.1C760 357.3 706.2 390.7 667.4 415.2C661.7 418.8 653.1 424.3 648.3 427.3C643.5 430.3 634.5 436 628.3 439.9C580.7 469.9 543.5 492.9 512.9 511.3C495.3 521.8 493.5 522.8 489.6 524.1L487.5 524.7L487.4 712.4L487.3 900.1L488.4 903.8C489.5 907.2 489.8 907.6 491.6 908.7C494.6 910.6 554.1 945.3 556.2 946.3C558.9 947.8 568.3 947.8 571.4 946.4ZM338.9 686.7L338.9 606.3L337.9 603.5C336.8 600.3 335.1 597.4 331.8 592.8C327.7 587.4 324.6 584.8 315.4 579.2C305.6 573.2 297.8 568.5 254 541.9C227.5 525.7 219.4 520.7 192.6 504.4C187.7 501.5 183.4 499.1 183 499.1C181.8 499.1 181.9 682 183.1 683C183.5 683.4 188.8 686.4 194.7 689.6C200.7 692.8 207.7 696.6 210.4 698.1C213.1 699.6 221.2 704 228.5 708C235.8 711.9 247.7 718.4 255 722.3C262.3 726.3 270.4 730.7 273 732.2C277.6 734.7 312 753.4 326.3 761.1C329.8 763.1 333.9 765.3 335.4 766.1C337 767 338.4 767.5 338.6 767.4C338.8 767.3 338.9 731 338.9 686.7ZM614 764.5C616.9 762.9 621.5 760.4 624.1 758.9C629.2 756.1 663.3 737.6 681.5 727.7C687.3 724.5 694.2 720.8 696.9 719.3C699.6 717.8 713.7 710.2 728.2 702.2C762.8 683.5 763.6 683 763.9 682.2C764 681.8 764.2 640.5 764.2 590.5C764.2 510.7 764.1 499.5 763.5 499.2C762.7 498.9 762 499.3 748 507.8C737.8 514.1 730.6 518.4 705.7 533.6C699.7 537.3 693.9 540.8 692.8 541.5C691.6 542.1 685.9 545.6 680.1 549.2C674.3 552.7 664.7 558.5 658.9 562.1C653.1 565.6 643.4 571.5 637.5 575.1C623.8 583.4 623.7 583.5 619.5 587.8C613 594.5 608.9 601.2 607.7 606.9C607.2 609.3 607.1 623.9 607.1 688.2C607.1 765.3 607.1 768.2 608.4 767.5C608.6 767.4 611.1 766 614 764.5ZM900.5 740.1C901.4 739.6 903 738.4 903.9 737.4L905.6 735.7L905.6 574.5C905.6 432.2 905.5 413.4 904.9 413.4C904.3 413.4 896.8 417.9 873.4 432.3C869.9 434.4 859.7 440.7 850.6 446.2C829.7 459 827.5 460.3 819.9 465C816.5 467.1 812 469.8 810 471C808 472.3 803.2 475.2 799.4 477.5L792.5 481.8L792.4 581.4C792.3 675.7 792.3 681 793.1 681.6C793.5 682 816.7 695.6 844.6 711.9C900.8 744.7 896.1 742.3 900.5 740.1ZM57.3 738.2C74.3 730.7 114.3 707.5 146.8 686.4C149.2 684.8 151.8 683.2 152.5 682.8L153.7 682L153.7 581.8L153.7 481.7L151.8 479.6C150.6 478.4 146.5 475.6 141.3 472.5C117.8 458.5 93.1 443.8 89.2 441.5C86.8 440 76 433.6 65.3 427.2C54.5 420.8 44.9 415.1 44 414.5C43 413.9 41.9 413.4 41.5 413.4C40.4 413.4 40.5 735.2 41.6 736.7C42 737.3 43.4 738.5 44.7 739.4C48.2 741.7 49.7 741.5 57.3 738.2ZM480.5 496.2C487 492.1 490.3 490.1 509.2 478.6C513.1 476.2 519.5 472.2 523.4 469.8C527.4 467.4 533.8 463.5 537.7 461.1C541.7 458.7 548.1 454.7 552 452.3C559.1 448 563.4 445.3 583.6 433C595 425.9 610.5 416.5 621.2 409.9C625.1 407.5 631.5 403.6 635.4 401.2C639.3 398.8 645.8 394.8 649.7 392.4C653.6 390 660 386.1 664 383.7C667.9 381.3 674.3 377.3 678.2 374.9C682.2 372.5 688.6 368.6 692.5 366.2C696.5 363.8 702.9 359.8 706.9 357.3C718.1 350.5 727.8 344.6 735.4 339.9C743 335.2 752.7 329.3 763.8 322.5C767.8 320 774.2 316.1 778.1 313.7C790.8 305.9 799.2 300.8 813.7 291.9C821.6 287.1 834 279.4 841.3 274.9C848.7 270.4 857.8 264.8 861.6 262.5C865.4 260.2 873.2 255.4 878.9 251.9C892.1 243.8 896.1 240.6 901.5 233.5C905.9 227.6 905.6 231.5 905.6 181.6C905.6 143.6 905.5 137.9 904.9 137.9C904.3 137.9 899.9 140.5 877.7 154.1C875.5 155.5 868.7 159.6 862.8 163.2C852.4 169.5 849.3 171.4 840 177.1C837.7 178.5 830.9 182.6 825 186.3C819 189.9 812.3 194 810 195.4C800.6 201.1 797.5 203 787.2 209.3C781.3 212.9 774.5 217 772.2 218.4C762.8 224.2 759.8 226 749.4 232.3C743.5 235.9 736.8 240 734.6 241.4C732.4 242.7 728.7 245 726.5 246.3C724.3 247.7 717.6 251.7 711.7 255.4C705.7 259 699 263.1 696.7 264.5C687.3 270.2 684.2 272.1 673.9 278.4C667.9 282 661.2 286.1 658.9 287.5C649.8 293.1 646.5 295.
1031 636.1 301.4C630.1 305.1 621.6 310.2 617.2 313C612.8 315.7 600.6 323.1 590.3 329.4C564.4 345.2 535.2 363 522.8 370.6C508.7 379.1 508.2 379.4 504.6 380.9C487.4 387.8 467.1 388.7 449.3 383.4C442.8 381.4 437.6 379.1 432 375.7C425.2 371.6 390.2 350.3 384.5 346.8C382.4 345.5 378.8 343.3 376.5 341.9C374.2 340.5 367.5 336.4 361.5 332.8C355.6 329.1 348.9 325.1 346.6 323.7C344.4 322.4 340.8 320.1 338.6 318.8C336.4 317.4 326 311.1 315.5 304.7C305.1 298.3 288 287.9 277.5 281.5C267.1 275.2 250.1 264.8 239.8 258.5C229.4 252.2 212.5 241.9 202.2 235.6C191.9 229.3 174.8 218.9 164.3 212.5C146.2 201.5 143.1 199.6 133.2 193.5C130.9 192.1 124.2 188 118.2 184.4C112.3 180.8 105.6 176.7 103.4 175.4C101.2 174 97.5 171.8 95.3 170.4C90.9 167.7 57 147 47.6 141.3C43.8 139 41.5 137.9 41.3 138.2C41 138.5 40.9 155.2 40.9 183.3L41.1 227.9L42.4 230.4C44.3 233.7 47.9 237.6 52.7 241.4C56.3 244.2 59.3 246.4 73.6 256C86.4 264.8 109.7 279.3 131.8 292.5C149.7 303.1 152.2 304.6 174.9 317.8C191.3 327.3 191.5 327.4 210.9 338.7C226.1 347.7 244 358.5 265.9 372.2C270.7 375.1 276.7 378.9 279.3 380.4C283.6 383.1 308.3 398.6 314.3 402.4C315.8 403.3 318.5 405 320.3 406.1C322 407.2 326.8 410.2 330.9 412.7C337.8 417.1 354.2 427.4 362 432.3C369.2 436.8 377.2 441.8 382.9 445.4C386.2 447.5 396.2 453.7 405 459.2C413.9 464.7 422.7 470.2 424.6 471.4C426.5 472.6 432.5 476.4 438 479.7C456.9 491.4 459.9 493.3 465.6 496.8C468.8 498.8 471.9 500.4 472.5 500.4C473 500.5 476.7 498.5 480.5 496.2ZM483.1 357.7C491.6 356.1 492.6 355.7 511.8 343.9C520 338.9 532.6 331.1 539.8 326.7C558.2 315.4 576.9 303.9 592.4 294.4C599.7 289.9 611.6 282.6 618.8 278.2C626 273.7 637.9 266.5 645.1 262C652.3 257.6 664.2 250.3 671.5 245.9C678.7 241.4 687.3 236.1 690.5 234.2C693.6 232.2 700.8 227.8 706.5 224.3C712.1 220.9 721.3 215.2 727 211.8C732.6 208.3 739.8 203.9 743 201.9C746.1 200 754.7 194.7 762 190.2C769.3 185.8 781.1 178.5 788.2 174.1C809.9 160.8 827.1 150.3 840.8 141.9C847.9 137.5 861.1 129.4 870.1 123.8C879.1 118.3 889 112.2 892.1 110.3C895.2 108.4 897.8 106.6 897.8 106.2C897.8 105.2 885.8 97 860.3 80.6C844 70.1 825.9 60 817.6 56.8C815.5 56 813.3 55.1 812.8 54.9C809.9 53.7 802.2 53.2 799.4 54C798.4 54.3 797.1 54.8 796.4 54.9C795.8 55.1 794.3 55.6 793.2 55.9C792.1 56.2 790.4 56.7 789.6 57.1C788.3 57.6 735.3 90.2 566.5 194.1C500.9 234.5 507.1 230.8 502 232.7C499.9 233.6 497.7 234.4 497.2 234.6C496.7 234.8 495.3 235.2 494.2 235.5C493.1 235.8 491.5 236.3 490.7 236.5C489.9 236.7 488.7 236.9 488.1 236.9C487.4 236.9 486.3 237.2 485.6 237.4C483.9 238.1 465.4 238.1 462.5 237.5C461.3 237.2 459.1 236.7 457.6 236.4C448.7 234.7 439 230.4 427.8 223.5C423.7 220.8 417.9 217.2 414.9 215.4C412 213.7 397.5 204.7 382.7 195.5C367.9 186.3 353.2 177.1 350 175.2C346.8 173.2 336.5 166.8 327 160.9C317.5 155 307.1 148.6 303.9 146.6C300.7 144.7 286 135.6 271.2 126.4C256.4 117.2 241.7 108 238.5 106.1C235.3 104.1 225 97.7 215.5 91.8C206 85.9 195.6 79.5 192.4 77.5C189.3 75.6 184.2 72.4 181.1 70.5C166.3 61.3 161.1 58.2 158.6 57.4C150.7 54.8 143.1 53.3 139.6 53.7C136 54.2 134.2 54.5 133.2 54.9C132.7 55.1 130.5 56 128.4 56.8C117.1 61.2 97.5 72.7 68.3 92C54 101.5 48.6 105.5 48.8 106.6C48.9 107.3 51.8 109.4 59 113.8C64.6 117.2 71.6 121.5 74.5 123.2C77.4 125 87.1 131 96.1 136.5C105.1 142 115.3 148.2 118.7 150.3C122.1 152.4 128.6 156.4 133 159.1C137.4 161.8 143.9 165.7 147.3 167.8C150.7 169.9 160.9 176.1 169.8 181.6C178.8 187.1 189 193.4 192.4 195.5C195.8 197.6 202.3 201.5 206.7 204.2C211.1 206.9 217.6 210.9 221 213C224.4 215.1 230.8 219 235.3 221.7C239.7 224.4 246.1 228.4 249.6 230.5C253 232.6 263.1 238.8 272.1 244.3C281.1 249.8 291.3 256 294.7 258.1C298.1 260.2 304.6 264.2 309 266.9C313.4 269.6 319.8 273.5 323.3 275.6C326.7 277.7 336.8 283.9 345.8 289.5C354.8 295 365 301.2 368.4 303.3C371.8 305.4 378.3 309.3 382.7 312C387.1 314.7 393.6 318.7 397 320.8C400.4 322.9 410.6 329.1 419.6 334.6C428.6 340.1 438.7 346.3 442.1 348.4C447.4 351.6 450.3 353.1 455.7 355.4C456.2 355.6 457.4 355.9 458.3 356.2C459.1 356.4 460.6 356.9 461.5 357.1C462.4 357.4 463.8 357.6 464.5 357.6C465.3 357.6 466.2 357.9 466.5 358.1C467.4 358.7 479.6 358.4 483.1 357.7ZM482.9 208.4C484.2 208.1 485.8 207.7 486.5 207.6C487.2 207.5 487.9 207.2 488 206.9C488.1 206.7 488.8 206.5 489.4 206.5C490 206.5 490.6 206.3 490.7 206.1C490.9 205.8 491.3 205.6 491.7 205.6C494.9 205.6 510.4 196.5 527.1 184.8C535.7 178.9 544.2 173.5 549.4 170.8C556.3 167.2 556.3 165.9 549.4 162.5C544 159.9 536.2 155.1 527.4 148.9C514.8 140.2 512.8 138.9 508 135.9C499.5 130.7 491.3 126.7 485.4 125C484.6 124.8 482.9 124.3 481.8 124C479 123.2 467.8 123.2 465.2 124C458.4 126.1 454.3 127.6 450.4 129.6C442.3 133.7 438 136.4 413.8 152.9C407 157.5 400 161.7 395.4 164C393.5 164.9 391.8 166 391.7 166.4C391.4 167.2 392.5 169 393.9 169.8C394.4 170.2 400.7 174.2 407.8 178.7C438.6 198.5 451.3 205.6 460.1 207.9C461.2 208.2 462.7 208.6 463.3 208.8C465 209.4 480.2 209.1 482.9 208.4Z","caps":{"L":[338.9,443.3,366.6,637.8],"R":[578.5,444.7,607.1,637.8]}}; 104/* ---- THE SWARM MARK (Website L, round 3, 30 Sep 2026) ---- 105 J's style (agents trace the outlines and hatch the faces in orderly rows, and stream in), on the OFFICIAL geometry: SW is 106 made by ll/logo/swarm.py from the traced logo. SW.pieces[id]: e = the centre lines of the piece's strokes (rounded corners 107 and all), r = strokes another piece already traces, and the cuts through the wing strips (shown only while apart), 108 f = faces [tone, hatch angle, polygon], poly = silhouette. SW.loz = the lozenge in the top V band's notch (sharp corners 109 T R B L and the corner radii): flat and in place when the mark is whole; while apart it rises and extrudes into a cube. 110 Pieces (founder, round 3): core = the lozenge -> About; frame = the top and lower V bands -> Our rules; left panel -> 111 Agentic systems; right panel -> AI products; stem -> AI Audit. */ 112const SW={"pieces":{"frame":{"e":[[132.3,40.9,124.7,43.3,108.7,50.7,77.5,69.5,44.0,92.3,37.7,98.5,34.9,103.4,32.8,113.5,32.9,116.2,33.9,118.6,35.8,120.1,46.2,123.8,53.7,127.4,70.6,137.4,96.6,154.2,115.3,164.7,152.0,187.2,161.3,193.6,307.5,282.3,340.9,303.3,359.5,313.9,438.3,362.6,451.6,369.1,467.3,372.4,482.1,372.2,494.1,369.9,502.7,366.6,530.6,349.3,543.6,342.1,555.3,334.0,596.8,308.7,604.2,305.0,631.9,287.3,690.3,252.2,774.7,199.8,810.0,178.8,851.3,153.0,866.7,144.3,892.8,127.7,901.9,123.1,912.2,119.5,913.8,117.6,914.4,115.0,914.4,109.0,913.6,105.4,910.2,98.8,907.1,95.2,897.2,87.6,881.5,77.2,847.4,55.8,827.7,45.3,818.2,41.6,810.7,40.5,798.7,40.5,794.0,41.3,784.3,44.4,768.1,53.5,744.1,68.1,731.6,76.6,600.1,157.3,589.5,163.1,575.4,167.4,569.3,175.5,561.9,181.5,509.6,214.6,497.1,220.6,485.4,223.9,480.7,224.5,466.7,224.4,458.3,223.0,453.0,221.3,435.6,212.7,387.7,183.3,379.6,176.5,372.1,167.1,361.5,164.1,352.3,159.6,207.5,69.9,168.9,47.0,162.2,43.9,150.5,40.7],[30.7,124.7,28.3,131.8,27.5,137.4,27.5,226.4,30.3,237.3,34.8,244.4,38.1,248.0,51.9,258.1,77.9,275.1,114.5,297.7,122.6,304.6,130.3,313.8,145.6,317.3,158.0,323.6,278.1,395.7,285.0,400.8,376.0,458.8,422.1,487.5,460.7,510.2,474.1,515.8,477.4,514.8,487.3,509.8,533.0,482.4,650.8,409.5,659.3,403.5,672.3,396.2,713.4,370.0,730.4,360.2,744.5,350.9,789.3,323.7,803.2,316.4,817.5,312.6,824.2,304.8,829.4,300.3,878.1,270.7,894.7,259.9,906.7,250.6,912.8,243.5,916.8,236.1,919.2,228.1,920.4,217.6,921.5,200.0,921.5,149.1,919.2,131.0,916.9,124.9]],"r":[],"f":[["T",0.0,[49.5,107.5,444.5,350.5,459.5,357.5,484.5,358.5,496.5,354.5,522.5,337.5,533.5,332.5,548.5,321.5,573.5,307.5,590.5,295.5,598.5,292.5,626.5,273.5,668.5,249.5,675.5,243.5,684.5,239.5,766.5,187.5,873.5,123.5,898.5,106.5,873.5,88.5,822.5,58.5,811.5,54.5,798.5,54.5,791.5,56.5,546.5,206.5,518.5,225.5,503.5,233.5,485.5,238.5,463.5,238.5,453.5,236.5,428.5,224.5,424.5,220.5,384.5,197.5,319.5,155.5,305.5,148.5,159.5,57.5,148.5,54.5,135.5,54.5,120.5,60.5,64.5,94.5]],["U",0.0,[41.5,138.5,41.5,229.5,49.5,239.5,100.5,274.5,254.5,364.5,471.5,500.5,477.5,499.5,488.5,491.5,512.5,478.5,514.5,475.5,528.5,468.5,589.5,429.5,644.5,397.5,646.5,394.5,763.5,324.5,767.5,320.5,776.5,316.5,845.5,272.5,861.5,264.5,865.5,260.5,886.5,248.5,902.5,234.5,905.5,228.5,9
11205.5,138.5,784.5,212.5,770.5,219.5,696.5,266.5,675.5,277.5,609.5,319.5,529.5,366.5,515.5,376.5,503.5,382.5,492.5,385.5,467.5,387.5,447.5,383.5,432.5,376.5,306.5,298.5,162.5,212.5,122.5,186.5,88.5,167.5,65.5,151.5]]],"poly":[[12.5,134.5],[13.5,230.5],[21.5,250.5],[29.5,260.5],[106.5,307.5],[120.5,326.5],[149.5,335.5],[269.5,407.5],[451.5,522.5],[473.5,531.5],[497.5,521.5],[796.5,336.5],[809.5,329.5],[826.5,325.5],[838.5,310.5],[884.5,284.5],[918.5,260.5],[929.5,242.5],[934.5,224.5],[935.5,139.5],[925.5,94.5],[906.5,76.5],[850.5,40.5],[824.5,27.5],[810.5,25.5],[791.5,26.5],[774.5,32.5],[592.5,145.5],[566.5,154.5],[575.5,166.5],[567.5,177.5],[510.5,214.5],[484.5,224.5],[452.5,221.5],[414.5,200.5],[377.5,174.5],[372.5,166.5],[381.5,154.5],[363.5,149.5],[167.5,29.5],[137.5,25.5],[113.5,31.5],[36.5,79.5],[20.5,97.5]]},"spine":{"e":[[353.5,646.8,354.5,665.4,354.5,762.4,353.5,774.0,353.7,936.9,356.9,947.6,359.8,952.4,362.5,955.1,370.3,960.2,378.5,962.3,384.3,962.4,394.5,960.3,402.9,956.5,447.8,929.7,469.7,917.5,472.3,915.0,473.3,912.4,473.5,522.5],[478.0,918.3,532.6,948.3,544.5,955.7,555.5,960.6,563.6,961.9,571.2,961.3,577.6,959.7,584.6,955.2,590.1,947.5,592.1,941.4,592.5,936.5,592.5,645.5]],"r":[[355.1,454.6,353.9,457.0,353.5,461.8,353.5,646.8],[473.5,522.5,472.3,518.0,469.7,515.5,460.6,511.1,436.4,497.0,367.2,454.0,361.3,452.1,356.3,453.4],[592.5,645.5,592.3,458.6,590.8,455.3,588.6,453.8,585.3,453.0,579.4,454.7,533.2,483.3,488.4,510.2,477.8,515.8]],"f":[["L",-0.8472,[353.5,462.5,353.5,574.5,364.5,594.5,366.5,601.5,367.5,938.5,375.5,947.5,384.5,948.5,389.5,947.5,454.5,909.5,459.5,904.5,459.5,525.5,419.5,504.5]],["R",-2.2944,[592.5,463.5,501.5,519.5,488.5,525.5,487.5,902.5,489.5,907.5,558.5,947.5,570.5,947.5,576.5,943.5,579.5,934.5,581.5,911.5,581.5,793.5,578.5,710.5,579.5,607.5,582.5,595.5,592.5,576.5]]],"poly":[[351.5,438.5],[341.5,446.5],[338.5,453.5],[338.5,808.5],[341.5,947.5],[348.5,961.5],[360.5,971.5],[375.5,976.5],[387.5,976.5],[411.5,968.5],[473.5,932.5],[552.5,975.5],[568.5,976.5],[585.5,972.5],[599.5,959.5],[607.5,942.5],[606.5,451.5],[594.5,439.5],[575.5,439.5],[472.5,500.5],[374.5,440.5],[361.5,436.5]]},"wingL":{"e":[[33.7,396.3,28.7,406.5,27.7,413.1,26.5,430.6,26.5,526.6,27.5,536.2,27.5,621.2,28.5,631.8,28.5,697.8,27.5,707.4,27.5,731.3,29.5,742.5,31.1,745.8,33.4,748.5,41.9,754.2,47.4,755.4,53.4,755.5,58.9,754.3,70.7,749.1,93.6,736.6,150.0,701.6,165.7,693.5,168.0,690.9,168.5,686.1,168.5,498.1,172.1,482.8,169.4,477.1,166.3,473.5,156.3,466.1,139.7,455.5,60.4,408.0,49.4,400.8,37.3,396.1],[174.0,694.5,324.7,777.3,332.2,781.0,338.3,783.0,344.8,784.3,348.0,783.6],[347.8,593.7,343.9,586.3,339.2,580.3,334.9,576.2,325.5,569.9,244.0,520.4,200.4,492.9,185.5,485.4,178.2,483.8],[126.6,319.9,122.0,324.9,115.0,329.8,42.0,373.3,38.8,376.8,35.4,383.4,34.6,387.0,34.9,390.8]],"r":[[348.0,783.6,349.9,780.9,351.8,775.6,353.5,764.3,353.5,665.3,352.5,654.7,352.4,605.8,351.4,601.2,347.8,593.7],[348.6,581.2,351.7,578.2,352.5,573.5,352.5,450.5,352.0,446.7,350.8,444.3,339.4,436.1,284.7,401.6,277.8,396.5,157.7,324.4,147.0,318.9,135.3,316.0,130.1,316.9,126.6,319.9]],"f":[["L",-0.8472,[42.5,413.5,41.5,729.5,42.5,738.5,47.5,741.5,52.5,741.5,84.5,725.5,154.5,682.5,154.5,482.5,132.5,466.5,102.5,450.5,95.5,444.5,89.5,442.5]],["l",-0.8472,[183.5,499.5,183.5,683.5,314.5,755.5,339.5,767.5,339.5,606.5,335.5,597.5,329.5,589.5,318.5,580.5,304.5,573.5,192.5,503.5]],["T",0.0,[51.5,382.5,63.5,392.5,74.5,397.5,141.5,440.5,165.5,453.5,167.5,456.5,345.5,565.5,352.5,573.5,352.5,461.5,271.5,408.5,142.5,331.5]]],"poly":[[124.5,302.5],[105.5,318.5],[29.5,364.5],[20.5,378.5],[19.5,390.5],[13.5,402.5],[11.5,531.5],[14.5,744.5],[20.5,755.5],[31.5,765.5],[40.5,769.5],[59.5,769.5],[102.5,748.5],[158.5,712.5],[172.5,708.5],[327.5,795.5],[343.5,799.5],[353.5,798.5],[361.5,792.5],[368.5,770.5],[367.5,600.5],[362.5,589.5],[367.5,580.5],[366.5,440.5],[358.5,430.5],[287.5,384.5],[167.5,312.5],[140.5,301.5]]},"wingR":{"e":[[911.6,396.0,901.2,399.7,889.9,406.1,835.9,439.9,799.8,461.3,784.9,471.2,778.6,477.6,775.6,482.8,778.5,492.2,779.5,498.8,779.5,686.8,780.0,691.4,782.4,693.6,875.4,746.6,890.2,754.3,895.7,755.5,901.6,755.2,907.0,753.8,914.5,748.2,918.7,74
1120.9,920.4,728.7,920.5,711.7,921.5,701.1,921.5,588.1,920.5,577.5,920.5,439.5,919.6,431.9,919.0,408.1,915.7,398.5,913.9,396.6],[769.3,483.9,760.6,486.1,754.7,488.8,624.7,568.4,615.3,574.8,608.0,580.8,604.0,585.9,598.7,594.7],[599.6,783.6,602.1,784.4,605.8,784.0,613.1,782.2,618.1,779.9,755.8,704.2,775.5,694.0],[912.4,389.5,912.1,383.7,909.4,377.8,907.4,374.8,902.3,370.5,885.6,360.0,836.4,331.4,827.1,325.0,821.1,318.5]],"r":[[598.7,594.7,596.2,601.6,594.7,609.0,594.4,624.9,593.5,631.5,593.5,705.5,594.5,716.1,594.6,767.1,596.4,777.3,598.0,781.6,599.6,783.6],[821.1,318.5,819.1,316.6,815.9,315.3,809.0,315.8,800.2,318.7,789.6,324.5,744.1,352.1,730.7,361.0,713.0,371.2,672.7,397.0,658.9,404.7,651.2,410.3,607.3,437.0,595.6,444.8,593.9,448.0,593.5,451.8,593.5,575.8,594.9,581.2,596.8,582.9,599.4,583.6]],"f":[["R",-2.2944,[905.5,413.5,793.5,482.5,793.5,682.5,896.5,741.5,900.5,741.5,906.5,736.5]],["r",-2.2944,[764.5,499.5,728.5,519.5,708.5,533.5,623.5,584.5,609.5,601.5,607.5,610.5,608.5,768.5,764.5,683.5]],["T",0.0,[896.5,383.5,815.5,334.5,804.5,331.5,716.5,384.5,687.5,404.5,668.5,414.5,658.5,422.5,593.5,462.5,593.5,575.5,598.5,568.5,619.5,554.5,641.5,542.5,669.5,523.5,678.5,519.5,682.5,515.5,772.5,461.5,823.5,428.5,832.5,424.5,842.5,416.5,853.5,411.5]]],"poly":[[823.5,301.5],[803.5,301.5],[781.5,311.5],[587.5,431.5],[579.5,441.5],[578.5,582.5],[583.5,591.5],[579.5,604.5],[578.5,625.5],[579.5,772.5],[585.5,791.5],[594.5,798.5],[618.5,796.5],[778.5,708.5],[887.5,769.5],[905.5,769.5],[916.5,765.5],[927.5,754.5],[932.5,744.5],[936.5,689.5],[933.5,401.5],[927.5,388.5],[926.5,377.5],[916.5,363.5],[842.5,319.5]]}},"loz":{"T":[473.9,98.4],"R":[582.7,167.5],"B":[474.9,234.7],"L":[363.9,168.5],"rT":60.0,"rL":12.5,"loop":[374.6,167.2,379.8,175.7,388.0,182.4,436.6,212.3,450.7,219.3,457.6,221.8,467.0,223.4,483.8,223.2,494.7,220.4,510.6,213.0,562.9,179.8,569.3,174.2,572.9,168.8,573.5,166.5,570.2,161.2,562.1,154.3,550.8,147.7,519.6,126.6,510.7,121.4,498.3,115.2,486.9,111.3,478.5,109.7,472.6,109.5,462.1,110.8,454.0,113.1,445.6,116.9,427.0,127.6,402.9,144.2,383.7,155.8,378.1,161.2,375.3,164.9]}}; 113const XY=a=>{const o=[];for(let i=0;i<a.length;i+=2)o.push([a[i],a[i+1]]);return o}; 114const LZ=SW.loz,CUBE_H=Math.hypot(LZ.R[0]-LZ.B[0],LZ.R[1]-LZ.B[1]); // an iso cube: the vertical edge as long as a top edge 115const PARTS=[ 116 {id:'core',off:[0,-400],side:'r'}, 117 {id:'frame',off:[0,-100],side:'l'}, 118 {id:'wingL',off:[-178,98],side:'l'}, 119 {id:'wingR',off:[178,98],side:'r'}, 120 {id:'spine',off:[0,210],side:'s'} 121]; 122// a rounded polyline: each inner corner (or every corner, closed) becomes an arc of radius r[i] (capped by the room there). 123// Returns the dense points and, per input vertex, the index of its arc's middle point. 124function roundPts(pts,rad,closed){const n=pts.length,out=[],vi=[]; 125 for(let i=0;i<n;i++){const p=pts[i]; 126 if(!closed&&(i===0||i===n-1)){vi.push(out.length);out.push(p);continue} 127 const a=pts[(i-1+n)%n],b=pts[(i+1)%n],la=Math.hypot(a[0]-p[0],a[1]-p[1]),lb=Math.hypot(b[0]-p[0],b[1]-p[
1271]); 128 if(la<.5||lb<.5){vi.push(out.length);out.push(p);continue} 129 const ua=[(a[0]-p[0])/la,(a[1]-p[1])/la],ub=[(b[0]-p[0])/lb,(b[1]-p[1])/lb],cs=clamp(ua[0]*ub[0]+ua[1]*ub[1],-1,1),th=Math.acos(cs); 130 if(th>3.1){vi.push(out.length);out.push(p);continue} 131 const t=Math.min(rad[i]/Math.tan(th/2),la*.48,lb*.48),r=t*Math.tan(th/2); 132 const p0=[p[0]+ua[0]*t,p[1]+ua[1]*t],p1=[p[0]+ub[0]*t,p[1]+ub[1]*t]; 133 // the arc from p0 to p1 around the corner (a quadratic through p is close enough to a circle at these sizes) 134 const m=Math.max(2,Math.round(r/4));for(let j=0;j<=m;j++){const q=j/m,iq=1-q;if(j===Math.round(m/2))vi.push(out.length); 135 out.push([iq*iq*p0[0]+2*iq*q*p[0]+q*q*p1[0],iq*iq*p0[1]+2*iq*q*p[1]+q*q*p1[1]]);}} 136 return{pts:out,vi}} 137// cumulative lengths of a dense polyline, and a point + direction at fraction f of it 138function chain(pts){const c=[0];for(let i=1;i<pts.length;i++)c.push(c[i-1]+Math.hypot(pts[i][0]-pts[i-1][0],pts[i][1]-pts[i-1][1]));return{pts,c,L:c[c.length-1]}} 139function atChain(C,f){const d=clamp(f,0,1)*C.L,c=C.c;let lo=0,hi=c.length-1;while(hi-lo>1){const m=(lo+hi)>>1;if(c[m]<=d)lo=m;else hi=m} 140 const a=C.pts[lo],b=C.pts[hi],q=(d-c[lo])/Math.max(1e-6,c[hi]-c[lo]);return[a[0]+(b[0]-a[0])*q,a[1]+(b[1]-a[1])*q,Math.atan2(b[1]-a[1],b[0]-a[0])]} 141// the core at rise h (0: the flat lozenge, 1: a cube): its corners, and its outline chains, all in mark units 142// O: L -> T -> R (the top edges, always drawn) S: R -> R' -> B' -> L' -> L (the sides' outline) I: L -> B -> R and B -> B' (inner) 143const LZr={T:LZ.rT,R:16,B:LZ.rT,L:16,d:LZ.rT*.55}; 144function cubeGeo(rise){const h=CUBE_H*rise,T=LZ.T,R=LZ.R,B=LZ.B,L=LZ.L,R2=[R[0],R[1]+h],B2=[B[0],B[1]+h],L2=[L[0],L[1]+h]; 145 const hex=h<1?[T,R,B,L]:[T,R,R2,B2,L2,L],rr=h<1?[LZr.T,LZr.R,LZr.B,LZr.L]:[LZr.T,LZr.R,LZr.R,LZr.d,LZr.L,LZr.L]; 146 const H=roundPts(hex,rr,true),n=H.pts.length,vi=H.vi,iR=vi[1],iL=vi[hex.length-1]; 147 const O=[],Sd=[];for(let i=iL;i!==iR;i=(i+1)%n)O.push(H.pts[i]);O.push(H.pts[iR]);for(let i=iR;i!==iL;i=(i+1)%n)Sd.push(H.pts[i]);Sd.push(H.pts[iL]); 148 const Ir=roundPts([L,B,R],[0,LZr.B,0],false),I=Ir.pts,Bi=I[Ir.vi[1]],Bo=h<1?Bi:H.pts[vi[3]]; 149 // the inner vertical edge runs between the two rounded corners (not the sharp ones), so it never pokes out below the cube 150 return{rh:H.pts,T,R,B,L,R2,B2,L2,h,hex:h<1?[T,R,B,L]:[T,R,R2,B2,L2,L],O:chain(O),S:chain(Sd),I:chain(I),V:chain([Bi,[Bi[0],Math.max(Bi[1],Bo[1])]])}} 151const CUBE1=cubeGeo(1); 152PARTS.forEach(P=>{ 153 if(P.id==='core'){P.faces=[{p:CUBE1.hex}];} 154 else{const D=SW.pieces[P.id];P.edges=D.e.map(XY);P.rev=D.r.map(XY);P.fc=D.f.map(([c,ang,p])=>({c,ang,p:XY(p)}));P.faces=[{p:D.poly}];} 155 const xs=P.faces[0].p.map(p=>p[0]),ys=P.faces[0].p.map(p=>p[1]);P.cx=(Math.min(...xs)+Math.max(...xs))/2;P.cy=(Math.min(...ys)+Math.max(...ys))/2;}); 156const MARK_FULL=new Path2D(MARK_D.full); 157const PI={};PARTS.forEach((p,k)=>PI[p.id]=k); 158// which part each breakdown step lights up, in the order of the site's pages: About, Our rules, Agentic systems, AI products, 159// AI Audit. Step 6 puts the mark back together. 160const STEP_PART={1:'core',2:'frame',3:'wingL',4:'wingR',5:'spine'}; 161const NSTEP=5; 162// how much the active part grows (a small scale-up) 163const GROW=.07; 164// intro: each piece's agents stream in from one side, bottom-up (as J) 165const ENTRY={spine:{t0:.15,dir:[.1,1],bow:.12},wingL:{t0:.45,dir:[-.45,1],bow:-.16},wingR:{t0:.6,dir:[1,.5],bow:.16},frame:{t0:.9,dir:[.75,-1],bow:-.14},core:{t0:1.45,dir:[.05,-1],bow:.1}}; 166const INTRO_END=4.8; 167// agent tones, on cream: the outline dark green, the faces hatched in sage (light tops, mid left faces, dark right faces). 168// E edges, V strokes shown only while apart; T/U tops, L/l left-facing, R/r right-facing; CE/CS/CI the core's outline, 169// side outline and inner edges, CL/CR its side faces. _D: the same on a sage ground (cream agents). 170const DKG=[18,29,26],SGE=[50,78,72],CRM=[239,231,218]; 171const mix3=(a,b,k)=>[lerp(a[0],b[0],k),lerp(a[1],b[1],k),lerp(a[2],b[2],k)]; 172const TONE_BASE={E:DKG,V:DKG,T:mix3(SGE,CRM,.42),U:mix3(SGE,CRM,.3),L:mix3(SGE,CRM,.16),l:mix3(SGE,DKG,.15),R:SGE,r:mix3(SGE,DKG,.35)}; 173const TONE_ACTV={E:DKG,V:DKG,T:mix3(SGE,CRM,.12),U:SGE,L:SGE,l:mix3(SGE,DKG,.45),R:mix3(SGE,DKG,.5),r:mix3(SGE,DKG,.7)}; 174const TONE_DIM=[176,184,174]; 175// the chosen piece turns orange (1 Oct 2026): edges a deep orange, tops light, left faces full, right faces darker 176const TONE_OR={E:mix3(ORG,DKG,.22),V:mix3(ORG,DKG,.22),T:mix3(ORG,CRM,.38),U:mix3(ORG,CRM,.22),L:ORG,l:mix3(ORG,DKG,.14),R:mix3(ORG,DKG,.12),r:mix3(ORG,DKG,.3)}
176; 177const TONE_MAP={CE:'E',CS:'E',CI:'E',CL:'L',CR:'R'}; 178const TONE_A={E:1,V:.9,T:1,U:1,L:1,l:1,R:1,r:1}; 179const DENS={T:1.25,U:1.15,L:1,l:.95,R:.95,r:.9}; 180const polyLen=l=>{let s=0;for(let i=1;i<l.length;i++)s+=Math.hypot(l[i][0]-l[i-1][0],l[i][1]-l[i-1][1]);return s}; 181const polyArea=p=>{let s=0;for(let i=0;i<p.length;i++){const a=p[i],b=p[(i+1)%p.length];s+=a[0]*b[1]-b[0]*a[1]}return Math.abs(s)/2}; 182const inPoly=(x,y,p)=>{let c=false;for(let i=0,j=p.length-1;i<p.length;j=i++){if(((p[i][1]>y)!==(p[j][1]>y))&&(x<(p[j][0]-p[i][0])*(y-p[i][1])/(p[j][1]-p[i][1])+p[i][0]))c=!c}return c}; 183const segD=(x,y,a,b)=>{const dx=b[0]-a[0],dy=b[1]-a[1],q=clamp(((x-a[0])*dx+(y-a[1])*dy)/(dx*dx+dy*dy),0,1);return Math.hypot(x-a[0]-dx*q,y-a[1]-dy*q)}; 184const edgeD=(x,y,p)=>{let m=1e9;for(let i=0;i<p.length;i++)m=Math.min(m,segD(x,y,p[i],p[(i+1)%p.length]));return m}; 185// sample the mark into about N agent homes, in tidy patterns (as J): 186// - edges: the stroke centre lines, traced end to end at one even spacing, each stroke laid along its edge 187// - faces: a regular lattice fitted to the face, every stroke on a face the same length and direction (hatching, not noise) 188// - the core: parametric homes (a chain and a fraction, or a face and u, v) so they follow the lozenge as it becomes a cube 189function sampleMark(N,edgeFrac){ 190 SEED=11;const out=[];const CD=1.6; // the core is small: a denser weave, so it still reads 191 let TL=0;PARTS.forEach(P=>{if(P.id==='core')TL+=CUBE1.O.L+CUBE1.S.L;
191else P.edges.forEach(l=>TL+=polyLen(l))}); 192 const es=TL/(N*edgeFrac); 193 const cubeA=polyArea([LZ.T,LZ.R,LZ.B,LZ.L])*(1+1.7); 194 let WA=cubeA*CD;PARTS.forEach(P=>{if(P.fc)P.fc.forEach(F=>WA+=polyArea(F.p)*DENS[F.c])}); 195 const sp0=Math.sqrt(WA*.866/(N*(1-edgeFrac))); 196 const HW=14; // the logo's stroke half width (mark units): the hatching starts inside the strokes 197 PARTS.forEach((P,k)=>{ 198 if(P.id==='core'){const G0=cubeGeo(0),G1=CUBE1; 199 const ch=(c,tone,step)=>{const n=Math.max(2,Math.round(G1[c].L/step));for(let j=0;j<n;j++){const f=(j+.5)/n,[x,y,a]=atChain(G0[c],f);out.push({k,core:1,ch:c,f,hx:x,hy:y,a0:a,tone,edge:1})}}; 200 ch('O','CE',es*.8);ch('S','CS',es*.8);ch('I','CI',es*.9);ch('V','CI',es*.9); 201 const sp=sp0/Math.sqrt(CD*1.2),E=CUBE_H; 202 const quad=(fc,tone,ang,Lu,Lv)=>{const n=Math.max(1,Math.round(Lu/sp)),m=Math.max(1,Math.round(Lv/(sp*.866))),mu=Math.min(.3,(HW+sp*.45)/Lu),mv=Math.min(.3,(HW+sp*.45)/Lv); 203 for(let j=0;j<m;j++)for(let i=0;i<n;i++)out.push({k,core:1,fc,u:mu+(1-2*mu)*(i+.5)/n,v:mv+(1-2*mv)*(j+.5)/m,hx:0,hy:0,a0:ang,tone,edge:0});}; 204 quad('T','T',0,E,E);quad('CL','CL',Math.PI/6-Math.PI/2+.2,E,E);quad('CR','CR',-Math.PI/6-Math.PI/2-.2,E,E); 205 out.forEach(a=>{if(a.k===k&&a.fc)cubeHome(a,G0)}); 206 return;} 207 const edge=(l,step,tone)=>{const C=chain(l),n=Math.max(1,Math.round(C.L/step));for(let j=0;j<n;j++){const [x,y,a]=atChain(C,(j+.5)/n);out.push({k,hx:x,hy:y,a0:a,tone,edge:1})}};
208 P.edges.forEach(l=>edge(l,es,'E')); 209 P.rev.forEach(l=>edge(l,es*1.1,'V')); 210 P.fc.forEach(F=>{ 211 const sp=sp0/Math.sqrt(DENS[F.c]); 212 // lattice axes: tops run along the two iso top-face directions; side faces along the vertical and their slope 213 const side=F.ang!==0,sg=F.ang>-Math.PI/2?1:-1,ax=side?[0,1]:[C30,.5],bx=side?[C30,.5*sg]:[-C30,.5],o=F.p[0]; 214 let x0=1e9,x1=-1e9,y0=1e9,y1=-1e9;F.p.forEach(p=>{x0=Math.min(x0,p[0]);x1=Math.max(x1,p[0]);y0=Math.min(y0,p[1]);y1=Math.max(y1,p[1])}); 215 const R=Math.ceil(Math.hypot(x1-x0,y1-y0)/sp)+2; 216 for(let i=-R;i<R;i++)for(let j=-R;j<R;j++){const x=o[0]+(i+.5)*sp*ax[0]+(j+.5)*sp*bx[0],y=o[1]+(i+.5)*sp*ax[1]+(j+.5)*sp*bx[1]; 217 if(x<x0||x>x1||y<y0||y>y1)continue; 218 if(inPoly(x,y,F.p)&&edgeD(x,y,F.p)>=Math.max(sp*.42,3))out.push({k,hx:x,hy:y,a0:F.ang,tone:F.c,edge:0});} 219 }); 220 }); 221 // intro timing: each part assembles from the side it arrives on, in order (nearest first), with a little personality 222 PARTS.forEach((P,k)=>{const E=ENTRY[P.id],dl=Math.hypot(E.dir[0],E.dir[1]),dx=E.dir[0]/dl,dy=E.dir[1]/dl; 223 const Lk=out.filter(a=>a.k===k);let mn=1e9,mx=-1e9;Lk.forEach(a=>{a.pr=a.hx*dx+a.hy*dy;mn=Math.min(mn,a.pr);mx=Math.max(mx,a.pr)}); 224 Lk.forEach(a=>{a.del=E.t0+(1-(a.pr-mn)/Math.max(1,mx-mn))*.95+srnd()*.25;});}); 225 out.forEach((a,i)=>{ 226 a.dur=1.45+srnd()*.5;a.spr=(srnd()-.5)*2;a.bn=(srnd()-.5)*.04;a.ph=srnd()*TAU;a.back=srnd(); 227 a.acc=!a.edge&&!a.core&&(i%67===23); 228 a.x=0;a.y=0;a.a=a.a0;a.ox=0;a.oy=0;a.on=false;}); 229 return out; 230} 231// a core agent's home in the cube geometry G (mark units) 232function cubeHome(a,G){ 233 if(a.ch){const [x,y,an]=atChain(G[a.ch],a.f);a.hx=x;a.hy=y;a.a0=an;return} 234 const q=a.fc==='T'?[G.T,G.R,G.B,G.L]:a.fc==='CL'?[G.L,G.B,G.B2,G.L2]:[G.B,G.R,G.R2,G.B2],u=a.u,v=a.v; 235 // T: u along T->R, v along T->L; the sides: u along the top edge, v down 236 const [A,B,C,D]=q; 237 if(a.fc==='T'){a.hx=A[0]+(B[0]-A[0])*u+(D[0]-A[0])*v;a.hy=A[1]+(B[1]-A[1])*u+(D[1]-A[1])*v;return} 238 a.hx=A[0]*(1-u)*(1-v)+B[0]*u*(1-v)+C[0]*u*v+D[0]*(1-u)*v;a.hy=A[1]*(1-u)*(1-v)+B[1]*u*(1-v)+C[1]*u*v+D[1]*(1-u)*v;} 239 240/* ============================================================ 241 THE STAGE: one canvas: a calm isometric world + the mark, made of agents 242 ============================================================ */ 243 244/* ---- LOGO STYLE OPTIONS (round 5, 30 Sep 2026; ?mk=1|2|3, the default unchanged until the founder picks) ---- 245 Founder: the swarm logos looked like wobbly pencil drawings, the core's hatching was denser than the rest, the halo too 246 strong. All three options: the official stroke geometry (SW centre lines at the official weight), the core sampled and 247 toned exactly like every other piece, a gentle halo. 248 1 crisp official outline (a clean stroke) + faces in neat, evenly spaced parallel rows of agents that sway along their rows
249 2 no drawn stroke: the outline is formed by dense agents in tight lanes along the smooth paths; faces fine and lighter 250 3 the clean mark at rest (outline + a very light even tint); agents only while it assembles or breaks apart 251 Small marks (under ~230 px wide) fall back to the clean mark (3's rest state) in every option. */ 252const MK=Q.has('mk')&&[0,1,2,3,4,5,6].includes(+Q.get('mk'))?+Q.get('mk'):4; // round 7: the founder picked 4 (?mk=0 the old default) 253const MK_LW=24; // the official stroke width, in mark units (the traced logo's strokes are ~24-28) 254PARTS.forEach(P=>{if(P.id==='core')return;const pth=L=>{const p=new Path2D();L.forEach(l=>l.forEach((q,i)=>i?p.lineTo(q[0],q[1]):p.moveTo(q[0],q[1])));return p}; 255 P.ep=pth(P.edges);P.rp=pth(P.rev);}); 256// sp: face row spacing, es: edge spacing (mark units); lanes: agent lanes across the stroke (option 2) 257function sampleMarkMK(sp,es,lanes){ 258 SEED=11;const out=[];const hw=MK_LW/2; 259 const lo=li=>lanes>1?(-hw+hw*2*(li+.5)/lanes)*.86:0; 260 const edgeC=(C,k,tone,extra)=>{const n=Math.max(2,Math.round(C.L/es));for(let li=0;li<lanes;li++)for(let j=0;j<n;j++){const f=((j+.5+(li%2)*.5)/n)%1,[x,y,a]=atChain(C,f); 261 out.push(Object.assign({k,C,f,hx:x,hy:y,a0:a,tone,edge:1,lo:lo(li)},extra||{}))}}; 262 PARTS.forEach((P,k)=>{ 263 if(P.id==='core'){const G1=CUBE1,G0=cubeGeo(0); 264 [['O','CE'],['S','CS'],['I','CI'],['V','CI']].forEach(([c,tn])=>edgeC(G1[c],k,tn,{core:1,ch:c})); 265 // faces: the same rows and spacing as every other piece (top: rows along the iso direction; sides: vertical rows) 266 const quad=(fc,tone,Lu,Lv,ang)=>{const n=Math.max(1,Math.round(Lu/sp)),m=Math.max(1,Math.round(Lv/sp)),mu=Math.min(.35,(hw+sp*.35)/Lu),mv=Math.min(.35,(hw+sp*.35)/Lv); 267 for(let j=0;j<m;j++)for(let i=0;i<n;i++)out.push({k,core:1,fc,u:mu+(1-2*mu)*(i+.5)/n,v:mv+(1-2*mv)*(j+.5)/m,hx:0,hy:0,a0:ang,tone,edge:0,row:fc==='T'?j:i});}; 268 const E=CUBE_H,LT=Math.hypot(LZ.R[0]-LZ.T[0],LZ.R[1]-LZ.T[1]),LV=Math.hypot(LZ.L[0]-LZ.T[0],LZ.L[1]-LZ.T[1]); 269 quad('T','T',LT,LV,Math.PI/6);quad('CL','CL',E,E,Math.PI/2);quad('CR','CR',E,E,Math.PI/2); 270 out.forEach(a=>{if(a.k===k&&a.fc)cubeHome(a,G0)}); 271 out.forEach(a=>{if(a.k===k&&a.fc)a.a0=a.fc==='T'?Math.PI/6:Math.PI/2}); 272 return;}
273 P.edges.forEach(l=>edgeC(chain(l),k,'E')); 274 P.rev.forEach(l=>edgeC(chain(l),k,'V')); 275 P.fc.forEach(F=>{ 276 const side=F.ang!==0,sg=F.ang>-Math.PI/2?1:-1,A=side?[0,1]:[C30,.5],B=side?[C30,.5*sg]:[-C30,.5],o=F.p[0],ang=Math.atan2(A[1],A[0]); 277 let x0=1e9,x1=-1e9,y0=1e9,y1=-1e9;F.p.forEach(p=>{x0=Math.min(x0,p[0]);x1=Math.max(x1,p[0]);y0=Math.min(y0,p[1]);y1=Math.max(y1,p[1])}); 278 const R=Math.ceil(Math.hypot(x1-x0,y1-y0)/sp)+2; 279 for(let i=-R;i<R;i++)for(let j=-R;j<R;j++){const x=o[0]+(i+.5)*sp*A[0]+(j+.5)*sp*B[0],y=o[1]+(i+.5)*sp*A[1]+(j+.5)*sp*B[1]; 280 if(x<x0||x>x1||y<y0||y>y1)continue; 281 if(inPoly(x,y,F.p)&&edgeD(x,y,F.p)>=hw+sp*.35)out.push({k,hx:x,hy:y,a0:ang,tone:F.c,edge:0,row:j});} 282 }); 283 }); 284 PARTS.forEach((P,k)=>{const E=ENTRY[P.id],dl=Math.hypot(E.dir[0],E.dir[1]),dx=E.dir[0]/dl,dy=E.dir[1]/dl; 285 const Lk=out.filter(a=>a.k===k);let mn=1e9,mx=-1e9;Lk.forEach(a=>{a.pr=a.hx*dx+a.hy*dy;mn=Math.min(mn,a.pr);mx=Math.max(mx,a.pr)}); 286 Lk.forEach(a=>{a.del=E.t0+(1-(a.pr-mn)/Math.max(1,mx-mn))*.95+srnd()*.25;});}); 287 out.forEach(a=>{a.dur=1.45+srnd()*.5;a.spr=(srnd()-.5)*2;a.bn=(srnd()-.5)*.04;a.ph=srnd()*TAU;a.back=srnd();a.acc=false;a.p=0; 288 a.x=0;a.y=0;a.a=a.a0;a.ox=0;a.oy=0;a.on=false;}); 289 return out; 290} 291 292/* ---- THE SWARM OPTIONS (round 6, 30 Sep 2026; ?mk=4|5|6; round 7: the founder picked 4, now the default everywhere) ---- 293 Founder on 1-3: "it needs more swarm, this looks like our logo with just some lines inside it". So in 4-6 nothing is drawn: 294 every mark is an agent, thousands of them, and the mark's shape comes from where they crowd and how they flow. The 295 outline is the official 28-unit stroke band around SW's centre lines (rounded corners and all); the core is sampled and 296 toned exactly like the rest, flat in the notch and rising into a cube of agents in the breakdown. 297 4 dense particle swarm: short dashes fill the stroke bands, drift slowly along the path and now and then swap lanes; 298 the faces hold a sparser, lighter drifting population 299 5 flowing traffic: chevron-headed agents stream along the strokes in evenly spaced lanes, both directions, each lane at 300 its own speed; the faces are hatching agents sliding along their hatch 301 6 J's swarm on the official geometry: J's orderly face hatching, the edges traced by 3 lanes of dashes (2-3x J's edge 302 agents) that crawl along the path, with arrow heads among them; no jitter 303 Sizes are in px at the stage's largest fit (g scales them down for small marks: a denser, finer swarm, never a drawn 304 outline). Every agent is drawn batched: one path per piece, tone and shape. */ 305const SWM=MK>=4,SW_HW=14; // half the official stroke width (mark units) 306TONE_BASE.E2=mix3(DKG,SGE,.8);TONE_ACTV.E2=mix3(DKG,SGE,.55);TONE_A.E2=1;TONE_OR.E2=mix3(ORG,DKG,.1); 307const SWP={ 308 4:{eArea:12,rim:4.6,eL:3.3,elw:1.45,fSp:12.5,fL:3.2,flw:1.25,fa:.6,v:5,sage:.38}, 309 5:{lanePx:6.8,eSp:7.6,chr:3.2,fSp:7.4,fL:3,flw:1.2,fa:.85,v:15,vs:3.2,sage:.3}, 310 6:{lanes:4,eSp:7.2,eL:5.4,elw:3.2,chr:3.3,fSp:7.2,fL:4.2,flw:1.6,fa:1,v:4.5,sage:.3}}; 311/* ---- option 4's strokes, sampled from the official INK (round 7, 30 Sep 2026) ---- 312 Founder: the band had gaps where strokes meet (the stem stopped short of the lower V's point). Cause: each stroke's agents 313 were laid in a 28-unit band around its own centre line, so the band stopped with a square end wherever a centre line ends: 314 at every T and Y junction, the corners where a branch leaves a band, the loop's start/end, the panels' window corners. 315 Now the agents' homes come from the official ink itself (MARK_FULL rasterised, 2 px per unit): a stratified (jittered grid) 316 sample of the ink, so the band is continuous by construction, junctions and corners included. Each ink point belongs to 317 one piece: the piece whose centre line it lies across (a branch's last half-stroke, inside the band it runs into, goes to 318 that band; points no band claims, like junction wedges, go to the nearest centre line). Its flow direction is that 319 centre line's tangent. The strokes a piece shows only while apart (rev: a neighbour's shared stroke, the cuts through the 320 wing strips) are sampled the same way over their band, minus what the piece already owns, so a separated piece's cut 321 ends are closed by a full band. The rims: agents on the ink's real edge (found by marching out from the centre line), so 322 the silhouette stays the official one. Motion: each agent slides along its tangent inside the ink over a short run 323 (about a spacing each way), shrinking to a dot as it wraps; nothing ever leaves the ink, so the coverage holds while it flows. */ 324let SWI=null; 325function swInk(){if(SWI)return SWI;const R=2,W=950*R,H=1000*R,cv=document.createElement('canvas');cv.width=W;cv.height=H; 326 const c=cv.getContext('2d');c.setTransform(R,0,0,R,0,0);c.fillStyle='#000';c.fill(MARK_FULL,'evenodd'); 327 const d=c.getImageData(0,0,W,H).data,M=new Uint8Array(W*H);for(let i=0;i<W*H;i++)M[i]=d[i*4+3]>127?1:0; 328 const ink=(x,y)=>{const i=Math.round(x*R-.5),j=Math.round(y*R-.5);return i>=0&&j>=0&&i<W&&j<H&&M[j*W+i]===1}; 329 // the centre lines: non-core pieces' own strokes (e) and their apart-only strokes (r), plus the flat lozenge (core) 330 const mk=(pts,k,rev)=>{const C=chain(pts);let x0=1e9,x1=-1e9,y0=1e9,y1=-1e9;
330pts.forEach(p=>{x0=Math.min(x0,p[0]);x1=Math.max(x1,p[0]);y0=Math.min(y0,p[1]);y1=Math.max(y1,p[1])}); 331 return{k,rev,C,bb:[x0-40,x1+40,y0-40,y1+40],closed:Math.hypot(pts[0][0]-pts[pts.length-1][0],pts[0][1]-pts[pts.length-1][1])<1}}; 332 const E=[],RV=[];PARTS.forEach((P,k)=>{if(P.id==='core')return;P.edges.forEach(l=>E.push(mk(l,k,0)));P.rev.forEach(l=>RV.push(mk(l,k,1)))}); 333 const G0=cubeGeo(0),CO=[mk(G0.O.pts,PI.core,0)],FP=PARTS[PI.frame].faces[0].p; 334 // nearest point of a chain: distance, arc position, tangent angle 335 const near=(ch,x,y)=>{const P=ch.C.pts,c=ch.C.c;let bd=1e9,bs=0,ba=0;for(let i=1;i<P.length;i++){const a=P[i-1],b=P[i],dx=b[0]-a[0],dy=b[1]-a[1],l2=dx*dx+dy*dy||1e-9, 336 q=clamp(((x-a[0])*dx+(y-a[1])*dy)/l2,0,1),ex=x-a[0]-dx*q,ey=y-a[1]-dy*q,dd=ex*ex+ey*ey;if(dd<bd){bd=dd;bs=c[i-1]+(c[i]-c[i-1])*q;ba=Math.atan2(dy,dx)}} 337 return{d:Math.sqrt(bd),s:bs,a:ba}}; 338 const inBB=(ch,x,y)=>x>=ch.bb[0]&&x<=ch.bb[1]&&y>=ch.bb[2]&&y<=ch.bb[3]; 339 // who owns an ink point: -1 not ink, PI.core the lozenge's band (the core's own agents cover it), else the piece + tangent 340 const own=(x,y)=>{if(!ink(x,y))return null; 341 let bi=null,bo=null;for(const ch of E){if(!inBB(ch,x,y))continue;const r=near(ch,x,y);r.ch=ch; 342 if(!bo||r.d<bo.d)bo=r;const Z=ch.closed?0:SW_HW;if(r.d<SW_HW+2&&r.s>=Z&&r.s<=ch.C.L-Z&&(!bi||r.d<bi.d))bi=r;} 343 // a point across a piece's own stroke band is that piece's. Otherwise, the lozenge's arch (the flat core shows only its 344 // top edges O, clipped to outside the frame's silhouette) is the core's where O is the nearest centre line 345 if(!(bi&&bi.d<SW_HW)&&!inPoly(x,y,FP))for(const ch of CO){if(inBB(ch,x,y)&&near(ch,x,y).d<(bo?bo.d:1e9))return{k:PI.core}} 346 const b=bi||bo;return b?{k:b.ch.k,a:b.a,d:b.d}:null}; 347 const revD=(k,x,y)=>{let b=null;for(const ch of RV){if(ch.k!==k||!inBB(ch,x,y))continue;const r=near(ch,x,y);if(!b||r.d<b.d)b=r}return b}; 348 return SWI={ink,own,revD,E,RV};} 349function swInkAgents(out,P0,u,g,sR,mul,G0){ 350 const I=swInk(),hw=SW_HW,m=u(P0.elw)*.6,sp=Math.sqrt(P0.eArea*g*g/(sR*sR)*mul),vel0=u(P0.v)/g,Rm=sp*1.1;out.esp=sp; 351 const add=(k,x,y,an,rev,rim,runOK,endOK)=>{ 352 // the run along the tangent: as far as the ink (or the apart-only band) reaches, up to Rm each way 353 const cx=Math.cos(an),sn=Math.sin(an),lim=rim?Math.min(Rm,u(P0.rim)*mul):Rm;let ra=0,rb=0; 354 for(const sd of[-1,1]){let r=0;while(r<lim&&runOK(x+cx*(r+1)*sd,y+sn*(r+1)*sd))r+=1; 355 // the run's far end must still be this piece's (checked once, not per step) 356 for(let i=0;i<3&&r>0&&endOK&&!endOK(x+cx*r*sd,y+sn*r*sd);i++)r=Math.floor(r*.5);if(sd<0)ra=r;else rb=r;} 357 out.push({k,hx:x,hy:y,bx:x,by:y,a0:an,b0:an,ra,rb,tone:rev?'V':'E',edge:1,mk:1,sh:0,dir:1,lo:0,v:srnd()<(rim?.15:P0.sage)?1:0, 358 vel:vel0*(rim?.8:(.7+srnd()*.6)),rim:rim?1:0});}; 359 // 1) the fill: one jittered point per sp x sp cell of the whole mark; ink points go to their owner (not the core's band) 360 for(let y=22;y<980;y+=sp)for(let x=8;x<942;x+=sp){const px=x+(.12+.76*srnd())*sp,py=y+(.12+.76*srnd())*sp,o=I.own(px,py); 361 if(!o||o.k===PI.core)continue;const k=o.k;add(k,px,py,o.a,0,0,I.ink,(qx,qy)=>{const q=I.own(qx,qy);return q&&q.k===k});} 362 // 2) the rims: from points along each centre line, march out both ways to the ink's edge; an agent just inside it 363 const rs=u(P0.rim)*mul; 364 for(const ch of I.E){const n=Math.max(2,Math.round(ch.C.L/rs));for(let j=0;j<n;j++){const [x,y,an]=atChain(ch.C,(j+.5)/n),nx=-Math.sin(an),ny=Math.cos(an); 365 for(const sd of[-1,1]){let r=0;while(r<hw+6&&I.ink(x+nx*sd*(r+.5),y+ny*sd*(r+.5)))r+=.5;if(r>=hw+6||r<m+1)continue; 366 const px=x+nx*sd*(r-m),py=y+ny*sd*(r-m),o=I.own(px,py);if(!o||o.k!==ch.k)continue; 367 add(ch.k,px,py,an,0,1,(qx,qy)=>I.ink(qx,qy)&&!I.ink(qx+nx*sd*(m+1.5),qy+ny*sd*(m+1.5)),(qx,qy)=>{const q=I.own(qx,qy);return q&&q.k===ch.k});}}} 368 // 3) the apart-only strokes: the band around each r line (on the ink: the ink it covers, a little wider to meet the piece's 369 // own ink; off the ink, the cuts: 28 units), minus what the piece owns; rims along both sides 370 const inRev=(k,x,y)=>{const r=I.revD(k,x,y);if(!r)return null;const ik=I.ink(x,y);if(r.d>=(ik?hw+2.5:hw))return null;if(ik){const o=I.own(x,y);if(o&&o.k===k)return null}return r};
371 PARTS.forEach((P,k)=>{if(P.id==='core')return;const R=I.RV.filter(c=>c.k===k);if(!R.length)return; 372 let x0=1e9,x1=-1e9,y0=1e9,y1=-1e9;R.forEach(c=>{x0=Math.min(x0,c.bb[0]+24);x1=Math.max(x1,c.bb[1]-24);y0=Math.min(y0,c.bb[2]+24);y1=Math.max(y1,c.bb[3]-24)}); 373 for(let y=y0;y<y1;y+=sp)for(let x=x0;x<x1;x+=sp){const px=x+(.12+.76*srnd())*sp,py=y+(.12+.76*srnd())*sp,r=inRev(k,px,py);if(!r)continue;add(k,px,py,r.a,1,0,(qx,qy)=>!!inRev(k,qx,qy));} 374 for(const ch of R){const n=Math.max(2,Math.round(ch.C.L/rs));for(let j=0;j<n;j++){const [x,y,an]=atChain(ch.C,(j+.5)/n),nx=-Math.sin(an),ny=Math.cos(an); 375 for(const sd of[-1,1]){const px=x+nx*sd*(hw-m),py=y+ny*sd*(hw-m);if(!inRev(k,px,py))continue;add(k,px,py,an,1,1,(qx,qy)=>!!inRev(k,qx,qy));}}} 376 }); 377} 378function sampleSwarm(opt,g,sR,cap,mul){ 379 SEED=13;const out=[],P0=SWP[opt],u=v=>v*g/sR,hw=SW_HW;mul=mul||1; 380 const G0=cubeGeo(0),chs=[]; 381 PARTS.forEach((P,k)=>{if(P.id==='core'){[['O','CE'],['S','CS'],['I','CI'],['V','CI']].forEach(([c,tn])=>chs.push({k,core:1,ch:c,L:CUBE1[c].L,tone:tn}));return}
382 P.edges.forEach(l=>{const C=chain(l);chs.push({k,C,L:C.L,tone:'E'})});P.rev.forEach(l=>{const C=chain(l);chs.push({k,C,L:C.L,tone:'V'})});}); 383 const home0=(c,f)=>atChain(c.core?G0[c.ch]:c.C,f); 384 const push=(c,f0,lo0,extra)=>{const [x,y,an]=home0(c,f0);out.push(Object.assign({k:c.k,C:c.C,core:c.core,ch:c.ch,Lc:c.L,f0,lo0,hx:x-Math.sin(an)*lo0,hy:y+Math.cos(an)*lo0,a0:an,tone:c.tone,edge:1,sh:0,v:0,dir:1,vel:0,lo:0},extra))}; 385 chs.forEach((c,ci)=>{ 386 if(opt===4){if(!c.core)return; // option 4's strokes (all but the core's): sampled from the official ink itself, below 387 const m=u(P0.elw)*.6,wB=2*(hw-m),n=Math.max(2,Math.round(c.L*wB*sR*sR/(P0.eArea*g*g)/mul)); 388 for(let j=0;j<n;j++)push(c,(j+srnd())/n,(srnd()*2-1)*(hw-m),{v:srnd()<P0.sage?1:0,vel:u(P0.v)/g*(.7+srnd()*.6),tsw:5+srnd()*9,m}); 389 // the rims: two files of agents walk the band's edges in order, so the silhouette stays the official one, crisp 390 const nr=Math.max(2,Math.round(c.L/(u(P0.rim)*mul)));for(const sd of[-1,1])for(let j=0;j<nr;j++)push(c,((j+(sd>0?.5:0)+srnd()*.3)/nr)%1,sd*(hw-m),{v:srnd()<.15?1:0,vel:u(P0.v)/g*.8,tsw:0,m,rim:1});} 391 else if(opt===5){const nl=clamp(Math.round(2*hw*sR/(P0.lanePx*g)),2,4),sp=u(P0.eSp)*mul; 392 for(let li=0;li<nl;li++){const lo=(-hw+2*hw*(li+.5)/nl)*.9,dir=li%2?-1:1,vel=u(P0.v)/g*(1+(hash(li+ci*.37,3.1)-.5)*.3),n=Math.max(2,Math.round(c.L/sp)),outer=li===0||li===nl-1; 393 for(let j=0;j<n;j++)push(c,((j+.5+(li%2)*.5)/n)%1,lo,{sh:1,dir,vel,v:srnd()<(outer?.12:.5)?1:0});}} 394 else{const nl=clamp(Math.round(2*hw*.88*sR/(P0.elw*g*1.4)),2,P0.lanes),sp=u(P0.eSp)*mul; 395 for(let li=0;li<nl;li++){const lo=(-hw+2*hw*(li+.5)/nl)*.88,n=Math.max(2,Math.round(c.L/sp)); 396 for(let j=0;j<n;j++)push(c,((j+.5+li/nl)/n)%1,lo,{sh:(j+li*3)%8===5?1:0,vel:u(P0.v)/g,v:srnd()<(li>0&&li<nl-1?.5:.1)?1:0});}} 397 }); 398 if(opt===4)swInkAgents(out,P0,u,g,sR,mul,G0); 399 // faces 400 const fsp=u(P0.fSp)*Math.sqrt(mul); 401 PARTS.forEach((P,k)=>{ 402 if(P.id==='core'){ 403 const quad=(fc,tone,Lu,Lv,ang)=>{const n=Math.max(1,Math.round(Lu/fsp)),m=Math.max(1,Math.round(Lv/fsp)),mu=Math.min(.35,(hw+fsp*.35)/Lu),mv=Math.min(.35,(hw+fsp*.35)/Lv); 404 for(let j=0;j<m;j++)for(let i=0;i<n;i++){const ji=opt===4?(srnd()-.5)*.8:0,jj=opt===4?(srnd()-.5)*.8:0; 405 out.push({k,core:1,fc,u:mu+(1-2*mu)*clamp((i+.5+ji)/n,0,1),v:0,vv:mv+(1-2*mv)*clamp((j+.5+jj)/m,0,1),hx:0,hy:0,bx:0,by:0,a0:ang,b0:ang,tone,edge:0,sh:0,row:fc==='T'?j:i,lo:0});}}; 406 const E=CUBE_H,LT=Math.hypot(LZ.R[0]-LZ.T[0],LZ.R[1]-LZ.T[1]),LV=Math.hypot(LZ.L[0]-LZ.T[0],LZ.L[1]-LZ.T[1]); 407 const tA=opt===6?0:Math.PI/6;quad('T','T',LT,LV,tA);quad('CL','CL',E,E,Math.PI/2);quad('CR','CR',E,E,Math.PI/2); 408 out.forEach(a=>{if(a.k===k&&a.fc){a.v=a.vv;const uu=a.u;cubeHome(a,G0);a.bx=a.hx;a.by=a.hy;a.v=0;a.u=uu}}); 409 return;} 410 P.fc.forEach(F=>{ 411 const side=F.ang!==0,sg=F.ang>-Math.PI/2?1:-1,A=side?[0,1]:[C30,.5],B=side?[C30,.5*sg]:[-C30,.5],o=F.p[0]; 412 const sp=opt===6?fsp/Math.sqrt(DENS[F.c]):fsp,ang=opt===6?F.ang:Math.atan2(A[1],A[0]); 413 let x0=1e9,x1=-1e9,y0=1e9,y1=-1e9;F.p.forEach(p=>{x0=Math.min(x0,p[0]);x1=Math.max(x1,p[0]);y0=Math.min(y0,p[1]);y1=Math.max(y1,p[1])}); 414 const R=Math.ceil(Math.hypot(x1-x0,y1-y0)/sp)+2;let n=0; 415 for(let i=-R;i<R;i++)for(let j=-R;j<R;j++){let x=o[0]+(i+.5)*sp*A[0]+(j+.5)*sp*B[0],y=o[1]+(i+.5)*sp*A[1]+(j+.5)*sp*B[1]; 416 if(opt===4){x+=(srnd()-.5)*sp*.7;y+=(srnd()-.5)*sp*.7;} 417 if(x<x0||x>x1||y<y0||y>y1)continue; 418 if(inPoly(x,y,F.p)&&edgeD(x,y,F.p)>=hw+sp*.4){n++;out.push({k,hx:x,hy:y,bx:x,by:y,a0:ang,b0:ang,tone:F.c,edge:0,sh:opt===6&&n%67===23?1:0,row:j,lo:0});}} 419 }); 420 }); 421 if(out.length>cap*1.05&&mul<8)return sampleSwarm(opt,g,sR,cap,mul*out.length/cap); 422 PARTS.forEach((P,k)=>{const E=ENTRY[P.id],dl=Math.hypot(E.dir[0],E.dir[1]),dx=E.dir[0]/dl,dy=E.dir[1]/dl; 423 const Lk=out.filter(a=>a.k===k);let mn=1e9,mx=-1e9;Lk.forEach(a=>{a.pr=a.hx*dx+a.hy*dy;mn=Math.min(mn,a.pr);mx=Math.max(mx,a.pr)}); 424 Lk.forEach(a=>{a.del=E.t0+(1-(a.pr-mn)/Math.max(1,mx-mn))*.95+srnd()*.25;});}); 425 out.forEach((a,i)=>{a.or=hash(i*.0173+.31,9.13)<OR_FRAC?1:0;a.id=i;a.dur=1.45+srnd()*.5;a.spr=(srnd()-.5)*2;a.bn=(srnd()-.5)*.04;a.ph=srnd()*TAU;a.back=srnd();a.acc=false;a.p=0;a.rp=srnd(); 426 a.x=0;a.y=0;a.a=a.a0;a.ox=0;a.oy=0;a.on=false;}); 427 out.fsp=fsp;return out; 428} 429// an agent's home right now (mark units): edges flow along their chain (the core's from the cube as it is now), faces drift 430function swHome(a,t,G,opt,fsp){ 431 if(a.mk){const D=a.ra+a.rb;let d=0,ls=1;if(D>
431.5&&!RM){const q=((a.ph/TAU+t*a.vel/D)%1+1)%1;d=-a.ra+q*D;ls=Math.min(1,Math.sin(Math.PI*q)*2.4);} 432 a.hx=a.bx+Math.cos(a.b0)*d;a.hy=a.by+Math.sin(a.b0)*d;a.a0=a.b0;a.ls=ls;return} 433 if(a.edge){const C=a.core?G[a.ch]:a.C;let f=a.f0+a.dir*a.vel*t/a.Lc;f-=Math.floor(f);let lo=a.lo0; 434 if(opt===4&&!a.rim){const T=a.tsw,q=t/T+a.ph,i0=Math.floor(q),fr=q-i0,lim=SW_HW-a.m,r=i=>(hash(a.id*.0131+i*1.71,a.id*.0077+.3)*2-1)*lim; 435 lo=clamp(lerp(r(i0),r(i0+1),ss(.72,1,fr))+Math.sin(t*.9+a.ph*5)*.8,-lim,lim);} 436 const [x,y,an]=atChain(C,f);a.hx=x-Math.sin(an)*lo;a.hy=y+Math.cos(an)*lo; 437 a.a0=an+(a.dir<0?Math.PI:0)+(opt===4&&!a.rim?Math.sin(t*.7+a.ph)*.16:0);return} 438 if(a.core){const u=a.u,v=a.vv;a.v=v;cubeHome(a,G);a.v=0;a.bx=a.hx;a.by=a.hy;} 439 const b=a.b0;let d=0,px=0,py=0; 440 if(opt===4){px=Math.sin(t*.31+a.ph)*fsp*.3;py=Math.cos(t*.27+a.ph*1.3)*fsp*.3;a.a0=b+Math.sin(t*.4+a.ph)*.35;} 441 else if(opt===5){d=((t*.35+hash(a.row*.73+a.k,a.k*1.9+(a.fc?5:0)))%1-.5)*fsp;a.a0=b;} // a row slides one spacing and repeats: the lattice looks like it flows 442 else{d=Math.sin(t*.8+a.row*.45+a.k*1.7)*fsp*.14;a.a0=b;} 443 a.hx=a.bx+Math.cos(b)*d+px;a.hy=a.by+Math.sin(b)*d+py; 444} 445 446 447/* ============================================================ 448 THE STAGE: one canvas, a calm isometric world on cream + the official mark (or one piece of it), made of agents. 449 Website L, round 3: J's swarm (agents trace the outlines, hatch the faces, stream in) on the traced OFFICIAL geometry 450 (SW in p1-helpers.js), so the silhouette and its rounded corners are the official mark's. The core is the lozenge in the 451 notch of the top V band: flat and in place when the mark is whole; while the pieces are apart it rises from behind the 452 band and extrudes into a small cube (this.rise), and sinks back and flattens when they come together. 453 Options: 454 fits(W,H,mob,S) named placements of the mark: {A, E?, B?, C?} (from FB(box, region)) 455 phase(f) scroll phase -> {e: how far apart, w: weight per step, fit: [from, to, k]} 456 only a part id: draw just that piece (the core: as its cube) 457 cards the breakdown labels (HTML links), placed beside their pieces 458 ============================================================ */ 459const FB=(b,r)=>{const s=Math.min((r[2]-r[0])/(b[1]-b[0]),(r[3]-r[1])/(b[3]-b[2]));return{s,ox:(r[0]+r[2])/2-(b[0]+b[1])/2*s,oy:(r[1]+r[3])/2-(b[2]+b[3])/2*s,b}}; 460// the same placement at another scale, around the same centre 461const rescale=(F,s)=>{const b=F.b,cx=F.ox+(b[0]+b[1])/2*F.s,cy=F.oy+(b[2]+b[3])/2*F.s;return{s,ox:cx-(b[0]+b[1])/2*s,oy:cy-(b[2]+b[3])/2*s,b}}; 462const partBox=(id,m)=>{const P=PARTS[PI[id]],xs=P.faces.flatMap(F=>F.p.map(p=>p[0])),ys=P.faces.flatMap(F=>F.p.map(p=>p[1])); 463 return[Math.min(...xs)-m,Math.max(...xs)+m,Math.min(...ys)-m,Math.max(...ys)+m]}; 464const PHASE0={e:0,w:[0,0,0,0,0,0],fit:['A','A',0]}; 465// stack labels on one side without overlaps, keeping each as close to where it wants to be as the room allows 466function stack(L,top,bot,gap){let c=top;for(const i of L){i.y=Math.max(i.y,c);c=i.y+i.h+gap;} 467 let lim=bot;for(let j=L.length-1;j>=0;j--){const i=L[j];i.y=Math.min(i.y,lim-i.h);lim=i.y-gap;} 468 c=top;for(const i of L){i.y=Math.max(i.y,c);c=i.y+i.h+gap;}} 469// the x-extent of a polygon inside a horizontal band [y0,y1] 470function bandX(q,y0,y1,cb){for(let i=0;i<q.length;i++){const a=q[i],b=q[(i+1)%q.length]; 471 const lo=Math.min(a[1],b[1]),hi=Math.max(a[1],b[1]);if(hi<y0||lo>y1)continue; 472 if(Math.abs(b[1]-a[1])<1e-6){cb(a[0]);cb(b[0]);continue} 473 const ya=clamp(y0,lo,hi),yb=clamp(y1,lo,hi);[ya,yb].forEach(y=>cb(a[0]+(b[0]-a[0])*(y-a[1])/(b[1]-a[1])));}} 474 475class Stage{ 476 constructor(cv,o){this.cv=cv;this.o=o;this.t=0;this.f=0;this.fT=0;this.e=0;this.mouse=null;this.mt=-1e9;this.cur=0;this.shiftY=0;this.hot=null; 477 this.started=!o.lazy;this.st=PARTS.map(()=>({act:0,sc:1,hv:0,tk:0,or:0}));this.lift=0;this.rise=o.only==='core'?1:0;this.G=cubeGeo(this.rise);this.ph=PHASE0;this.resize();} 478 resize(){ 479 const s=setupCanvas(this.cv);Object.assign(this,s);this.mob=this.W<760;this.below=this.W<1024;this.ok=this.mob?.7:1; 480 const o=this.o,W=this.W,H=this.H,mob=this.mob; 481 if(o.cards){if(this.below)o.cards.forEach(el=>{el.style.width='';el.style.transform='';el._w=el._tr=null});this.measureCards();} 482 this.FT=o.fits(W,H,mob,this); 483 this.applyFit(); 484 // the drawing's centre and size in mark units (the halo and the calm patch of world sit under it) 485 if(o.only){const b=partBox(o.only,0);this.mcU=[(b[0]+b[1])/2,(b[2]+b[3])/2,Math.max(b[1]-b[0],b[3]-b[2])*1.1];}else this.mcU=[473,520,946]; 486 // the world: fixed blocks, drawn once into a cache 487 const u=o.unit(W,H,mob);this.u=u;this.P={u,ox:W*.5,oy:H*.5-5*u+(o.oyAdj?o.oyAdj(this,u):0)}; 488 const FA=this.FT.A,plinthScr=o.plinth?o.plinth.map(p=>[FA.ox+p[0]*FA.s,FA.oy+p[1]*FA.s]):null; 489 const calm=Object.values(this.FT).map(F=>[F.ox+this.mcU[0]*F.s,F.oy+this.mcU[1]*F.s,this.mcU[2]*F.s*.62]); 490 this.cells=[];this.grid=new Map(); 491 for(let i=-80;i<90;i++)for(let j=-80;j<90;j++){ 492 const sx=this.P.ox+(i-j)*u*C30,sy=this.P.oy+(i+j+1)*u*.5; 493 if(sx<-u*2||sx>W+u*2||sy<-u*1.5||sy>H+u*4)continue; 494 const r=hash(i,j);let base=r<.76?0:r<.9?.25:r<.97?.5:r<.993?1:1.5; 495 let f=o.flat?o.flat(sx,sy,W,H,mob):1,q=1; 496 // near the mark the world goes flat and its grid lines fade right down, so the swarm is read against a quiet plate 497 for(const [x,y,R] of calm){const d=Math.hypot((sx-x)*.8,sy-y);f*=ss(R*.8,R*1.35,d);q=Math.min(q,ss(R*.55,R*1.25,d));} 498 base*=f; 499 const plinth=!!plinthScr&&inPoly(sx,sy,plinthScr);if(plinth)base=0; 500 const c={i,j,base,h:base,lift:0,plinth,gs:plinth?`rgba(18,29,26,${(.03).toFixed(3)})`:`rgba(18,29,26,${(.085*(.2+.8*q)).toFixed(3)})`};this.cells.push(c);this.grid.set(i*1000+j,c); 501 } 502 this.cells.sort((a,b)=>(a.i+a.j)-(b.i+b.j)); 503 if(!this.wc)this.wc=document.createElement('canvas'); 504 this.wc.width=this.cv.width;this.wc.height=this.cv.height; 505 const wx=this.wc.getContext('2d');wx.setTransform(this.dpr,0,0,this.dpr,0,0);this.drawWorld(wx,true); 506 // agents: as many as it takes to read the mark at this size (the lattice rounding adds ~29%, so the real count lands on n) 507 const n=o.count?o.count(this):mob?o.nMob:Math.round(clamp(946*FA.s*3.4,o.nMin,o.nMax)); 508 let mkKey=null;if(SWM){const sR=Math.max(...Object.values(this.FT).map(F=>F.s));this.swsR=sR;
508this.swg=clamp(946*sR/760,this.mob?.55:.42,1); 509 mkKey='sw'+MK+':'+Math.round(sR*1000)+':'+(this.mob?1:0);} 510 else if(MK){const sR=Math.max(...Object.values(this.FT).map(F=>F.s)),mob=this.mob; 511 this.spU=(MK===2?(mob?6:7.2):(mob?6.4:8))/sR;this.esU=(MK===2?(mob?2.6:3.2):9)/sR;this.lanes=MK===2?clamp(Math.round(MK_LW*sR/6),1,3):1;mkKey='mk'+Math.round(this.spU*10)+':'+Math.round(this.esU*10)+':'+this.lanes;} 512 if(o.noMark){this.agents=[];this.bk=[];} 513 else if(REFM)this.refInit(); 514 else if(!this.agents||this.nWant!==(mkKey||n)){this.nWant=mkKey||n;let A=SWM?sampleSwarm(MK,this.swg,this.swsR,this.mob?4200:9800):MK?sampleMarkMK(this.spU,this.esU,this.lanes):sampleMark(Math.round(n/1.29),o.edgeFrac||.42); 515 if(o.only){const k=PI[o.only],t0=ENTRY[o.only].t0-.1;const esp=A.esp,fsp=A.fsp;A=A.filter(a=>a.k===k);A.esp=esp;A.fsp=fsp;A.forEach(a=>a.del-=t0);} 516 this.agents=A;if(SWM)this.swFsp=A.fsp||8;this.bucket();} 517 if(o.cards)this.cardW(); 518 if(o.onResize)o.onResize(this); 519 } 520 bucket(){const m=new Map();this.acc=[]; 521 if(SWM){for(const a of this.agents){const key=a.k+':'+a.tone+':'+(a.v|0)+':'+a.sh+':'+a.edge+':'+(a.or|0);if(!m.has(key))m.set(key,{k:a.k,tone:a.tone,v:a.v|0,sh:a.sh,edge:a.edge,or:a.or|0,list:[]});m.get(key).list.push(a);} 522 this.bk=[...m.values()].sort((a,b)=>(a.edge-b.edge)||(a.sh-b.sh));return} 523 for(const a of this.agents){if(a.acc){this.acc.push(a);continue}const key=a.k+':'+a.tone;if(!m.has(key))m.set(key,{k:a.k,tone:a.tone,list:[]});m.get(key).list.push(a);} 524 const ord={r:0,l:0,R:1,L:1,CL:1,CR:1,U:2,T:2,V:3,CI:3,E:4,CS:4,CE:4}; 525 this.bk=[...m.values()].sort((a,b)=>ord[a.tone]-ord[b.tone]);} 526 // sy: on the landing hero the mark rides up with the first bit of scroll (as G), only while it is in its hero place A 527 applyFit(){const [a,b,k]=this.ph.fit,A=this.FT[a]||this.FT.A,B=this.FT[b]||A,sy=this.sy||0; 528 this.s=lerp(A.s,B.s,k);this.ox=lerp(A.ox,B.ox,k);this.oy=lerp(A.oy-(a==='A'?sy:0),B.oy-(b==='A'?sy:0),k)+this.shiftY;} 529 PX(p,P,e,sc){return this.ox+(P.cx+(p[0]-P.cx)*sc+P.off[0]*e*this.ok)*this.s} 530 PY(p,P,e,sc){return this.oy+(P.cy+(p[1]-P.cy)*sc+P.off[1]*e*this.ok)*this.s} 531 update(dt){ 532 if(!this.started)return; 533 const o=this.o; 534 if(!RM)this.t+=dt;const t=RM?INTRO_END+5:this.t;const K=(typeof o.introK==='function'?o.introK(this):o.introK)||1;const eD=o.entryD?o.entryD(this):0; 535 this.f+=(this.fT-this.f)*(RM||dt===0?1:Math.min(1,dt*10));if(Math.abs(this.fT-this.f)<.0005)this.f=this.fT; 536 this.ph=o.phase?o.phase(this.f):PHASE0;this.e=this.ph.e;this.applyFit(); 537 // the active part grows a little and its agents turn to the strong sage/dark green; a part whose label is hovered does so a little 538 const kh=RM||dt===0?1:Math.min(1,dt*8); 539 PARTS.forEach((P,k)=>{const Z=this.st[k]; 540 if(o.only){Z.act=P.id===o.only?1:0;Z.tk=P.id===o.only?.35:0;Z.sc=1;Z.or=0;return} // a page's own piece: a touch stronger than at rest, not the full highlight 541 let act=0;for(let q=1;q<=NSTEP;q++){if(STEP_PART[q]===P.id)act=Math.max(act,this.ph.w[q]);} 542 Z.act=act*ss(.35,.8,this.e);Z.sc=1+GROW*easeIO(Z.act); 543 Z.hv+=((this.hot===P.id&&this.e>.5?1:0)-Z.hv)*kh;Z.tk=Math.max(Z.act,Z.hv*.5);Z.or=Math.max(Z.act,Z.hv*.75);}); 544 // the core: flat lozenge while whole, a cube while apart (scroll-driven, with the pieces' separation) 545 this.rise=o.only?(o.only==='core'?1:0):easeIO(ss(.04,.62,this.e));this.G=cubeGeo(this.rise); 546 // cursor 547 const now=performance.now(),mOn=!RM&&this.mouse&&now-this.mt<2500; 548 this.cur=mOn?1:Math.max(0,this.cur-dt*.8); 549 if(!RM){const u=this.u;let mi=0,mj=0;if(mOn){const a=(this.mouse[0]-this.P.ox)/(u*C30),b=(this.mouse[1]-this.P.oy)/(u*.5);mi=(a+b)/2;mj=(b-a)/2;} 550 let any=0;const kk=Math.min(1,dt*5); 551 if(mOn||this.lift>0)for(const c of this.cells){let tg=0;if(mOn&&!c.plinth){const d=Math.hypot(c.i+.5-mi,c.j+.5-mj);if(d<3)tg=Math.pow(1-d/3,1.6)*1.1;} 552 c.lift+=(tg-c.lift)*kk;if(c.lift<.002)c.lift=0;c.h=c.base+c.lift;any=Math.max(any,c.lift);} 553 this.lift=any;} 554 if(o.noMark)return; 555 if(REFM){this.refUpdate(dt);return} 556 // agents 557 const s=this.s,e=this.e,W=this.W,H=this.H,ox=this.ox,oy=this.oy,G=this.G; 558 if(MK){const de=Math.abs(e-(this._pe==null?e:this._pe));this._pe=e;const tg=RM||dt<=0?0:clamp(de/dt*2.5,0,1);this.mv=(this.mv||0);this.mv+=(tg-this.mv)*Math.min(1,dt*(tg>this.mv?8:2.2));if(this.mv<.005)this.mv=0; 559 this.arrN=PARTS.map(()=>0);this.arrC=PARTS.map(()=>0);} 560 const cx=ox+this.mcU[0]*s,cy=oy+(o.only?this.mcU[1]:500)*s;const jit=RM?0:.6;const R=this.mob?70:110;const kc=Math.min(1,dt*6); 561 for(const a of this.agents){const P=PARTS[a.k],sc=this.st[a.k].sc; 562 if(SWM)swHome(a,t,G,MK,this.swFsp);else if(a.core)cubeHome(a,G); 563 if(MK===3&&a.C&&!a.core&&this.mv>.01&&!RM){const r=atChain(a.C,(a.f+t*.07)%1);a.hx=r[0];a.hy=r[1];a.a0=r[2];} 564 const lo=a.lo||0,hx=a.hx-Math.sin(a.a0)*lo,hy=a.hy+Math.cos(a.a0)*lo; 565 const tx=ox+(P.cx+(hx-P.cx)*sc+P.off[0]*e*this.ok)*s,ty=oy+(P.cy+(hy-P.cy)*sc+P.off[1]*e*this.ok)*s; 566 const p=(RM||o.noIntro)?1:clamp((t-a.del*K)/(a.dur*K),0,1); 567 a.p=p;if(MK){this.arrN[a.k]++;if(p>=1)this.arrC[a.k]++;} 568 if(p<=0){a.on=false;continue} 569 a.on=true;let x,y,ang; 570 if(p>=1&&SWM){x=tx;y=ty;ang=a.a0;} 571 else if(p>=1&&MK){let wv=0;if(!a.edge&&!RM)wv=Math.sin(t*.8+a.row*.45+a.k*1.7)*this.spU*s*.16;x=tx+Math.cos(a.a0)*wv;y=ty+Math.sin(a.a0)*wv;ang=a.a0;} 572 else if(p>=1){x=tx+Math.sin(t*1.3+a.ph)*jit;y=ty+Math.cos(t*1.1+a.ph*1.3)*jit;ang=a.a0+(RM?0:Math.sin(t*.9+a.ph)*.05);} 573 else{ 574 const E=ENTRY[P.id],dl=Math.hypot(E.dir[0],E.dir[1]),dx0=E.dir[0]/dl,dy0=E.dir[1]/dl; 575 const D=eD?eD*(1+a.back*.35):Math.max(W,H)*.75+a.back*Math.max(W,H)*.25; 576 const sx=cx+dx0*D-dy0*a.spr*26,sy=cy+dy0*D+dx0*a.spr*26; 577 const vx=tx-sx,vy=ty-sy,len=Math.hypot(vx,vy)+1,nx=-vy/len,ny=vx/len,bow=len*(E.bow+a.bn); 578 const mx=(sx+tx)/2+nx*bow,my=(sy+ty)/2+ny*bow; 579 const q=1-Math.pow(1-p,2.6),iq=1-q; 580 const wig=Math.sin(p*7+a.ph)*6*iq; 581 x=iq*iq*sx+2*iq*q*mx+q*q*tx+nx*wig;y=iq*iq*sy+2*iq*q*my+q*q*ty+ny*wig; 582 const ddx=2*iq*(mx-sx)+2*q*(tx-mx),ddy=2*iq*(my-sy)+2*q*(ty-my),am=Math.atan2(ddy,ddx); 583 const w=ss(.62,1,p);ang=am+((a.acc||a.sh)?angWrap(a.a0-am):lineWrap(a.a0-am))*w; 584 } 585 if(this.cur>0){let gx=0,gy=0;if(mOn){const dx=x-this.mouse[0],dy=y-this.mouse[1],d=Math.hypot(dx,dy); 586 if(d<R&&d>.5){const k=Math.pow(1-d/R,2);gx=(dx/d*13-dy/d*4)*k;gy=(dy/d*13+dx/d*4)*k;}} 587 a.ox+=(gx-a.ox)*kc;a.oy+=(gy-a.oy)*kc;} 588 else{a.ox=0;a.oy=0;} 589 a.x=x+a.ox;a.y=y+a.oy;a.a=ang; 590 } 591 } 592 drawWorld(ctx,base){ 593 ctx.fillStyle=WORLD_BG;ctx.fillRect(0,0,this.W,this.H); 594 ctx.lineWidth=1;const gs=this.o.gridStroke,P0=this.P; 595 for(const c of this.cells){const h=base?c.base:c.h; 596 const nl=this.grid.get(c.i*1000+c.j+1),nr=this.grid.get((c.i+1)*1000+c.j); 597 const hl=nl?(base?nl.base:nl.h):-1,hr=nr?(base?nr.base:nr.h):-1; 598 box(ctx,P0,c.i,c.j,-3,1,1,3+h,c.plinth?P_PLINTH:(!base&&c.lift>.15?P_LIFT:P_GROUND),c.gs||gs,hl>=h,hr>=h); 599 } 600 } 601 // a soft cream halo under the drawing, wherever it is right now: lowers the contrast of the world where the swarm is read 602 drawHalo(ctx){if(this.o.halo===false)return;const hk=this.o.haloK?this.o.haloK(this):1;if(hk<.01)return;const s=this.s,x=this.ox+this.mcU[0]*s,y=this.oy+this.mcU[1]*s,r=this.mcU[2]*s*.62; 603 if(!(r>0)||!isFinite(x+y))return;
603 // a zero-size or collapsed stage (tiny window, mid-resize): nothing to draw 604 const g=ctx.createRadialGradient(x,y,r*.1,x,y,r*1.05); 605 g.addColorStop(0,'rgba(239,231,218,.9)');g.addColorStop(.55,'rgba(239,231,218,.7)');g.addColorStop(1,'rgba(239,231,218,0)'); 606 ctx.save();const hk2=MK?hk*.38:hk;if(hk2<1)ctx.globalAlpha=hk2;ctx.translate(x,y);ctx.scale(1.25,1);ctx.translate(-x,-y);ctx.fillStyle=g;ctx.fillRect(x-r*1.2,y-r*1.1,r*2.4,r*2.2);ctx.restore();} 607 // a piece's silhouette on screen now (the core: its lozenge or cube as it is right now) 608 partPolys(k){const P=PARTS[k],e=this.e,sc=this.st[k].sc,F=P.id==='core'?[{p:this.G.hex}]:P.faces;return F.map(F=>F.p.map(p=>[this.PX(p,P,e,sc),this.PY(p,P,e,sc)]));} 609 hitPart(x,y){for(let k=0;k<PARTS.length;k++){if(this.partPolys(k).some(q=>inPoly(x,y,q)))return PARTS[k].id}return null} 610 draw(){ 611 const ctx=this.ctx,e=this.e,mob=this.mob,s=this.s,o=this.o,W=this.W,H=this.H; 612 ctx.setTransform(1,0,0,1,0,0); 613 if(this.lift>0){ctx.setTransform(this.dpr,0,0,this.dpr,0,0);this.drawWorld(ctx,false);} 614 else ctx.drawImage(this.wc,0,0); 615 ctx.setTransform(this.dpr,0,0,this.dpr,0,0); 616 this.drawHalo(ctx); 617 // markA (concept I's chat screen): the swarm mark fades out once the conversation starts, and the crisp logo takes over 618 const MA=o.markA?o.markA():1; 619 if(REFM&&!o.noMark){this.refDraw(MA);ctx.globalAlpha=1;
619if(o.cards){this.placeCards();this.drawLead(ctx);}return} 620 if(MA<.01){ctx.globalAlpha=1;if(o.cards)this.placeCards();return} 621 ctx.globalAlpha=MA; 622 if(MK){if(SWM)this.drawSW(ctx);else this.drawMK(ctx);ctx.globalAlpha=1;if(o.cards)this.placeCards();return} 623 // ---- the mark: agents only. Stroke length and weight follow the mark's size, so it reads the same big or small ---- 624 const Ls=clamp((o.lsK||7.4)*s,mob?2.6:3.1,o.lsMax||5.2),rv=o.only?1:ss(.3,.8,e),rc=ss(.02,.3,this.rise),apart=o.only?0:ss(.02,.3,e);ctx.lineCap='round'; 625 const LW=mob?(o.lwMob||1.7):clamp(1.3+s*1.1,o.lwMin||1.9,o.lwMax||2.5); 626 const segs=b=>{const tn=TONE_MAP[b.tone]||b.tone,Lb=(tn==='E'||tn==='V'?Ls*1.25:Ls)*this.st[b.k].sc; 627 ctx.beginPath();for(const a of b.list){if(!a.on)continue;const c=Math.cos(a.a)*Lb,sn=Math.sin(a.a)*Lb;ctx.moveTo(a.x-c,a.y-sn);ctx.lineTo(a.x+c,a.y+sn);}}; 628 const alphaOf=b=>{const tn=TONE_MAP[b.tone]||b.tone;let al=TONE_A[tn];if(b.tone==='V')al*=rv;if(b.tone==='CS'||b.tone==='CI'||b.tone==='CL'||b.tone==='CR')al*=rc;return al}; 629 const colOf=b=>{const tn=TONE_MAP[b.tone]||b.tone,Z=this.st[b.k],dim=apart*(1-Z.tk)*(tn==='E'||tn==='V'?.55:.7); 630 const c=mix3(mix3(TONE_BASE[tn],TONE_DIM,dim),TONE_ACTV[tn],Z.tk);return`rgba(${c[0]|0},${c[1]|0},${c[2]|0},`}; 631 const core=PI.core,pass=list=>{ 632 // a cream under-stroke first, so the agents read crisply over the world's grid lines 633 ctx.lineWidth=LW+1.6;for(const b of list){const al=alphaOf(b);if(al<.02||b.tone==='V')continue;ctx.strokeStyle=`rgba(239,231,218,${(.45*al).toFixed(3)})`;segs(b);ctx.stroke();} 634 ctx.lineWidth=LW;for(const b of list){const al=alphaOf(b);if(al<.02)continue;ctx.strokeStyle=colOf(b)+al.toFixed(3)+')';segs(b);ctx.stroke();}}; 635 pass(this.bk.filter(b=>b.k!==core)); 636 const cb=this.bk.filter(b=>b.k===core); 637 if(cb.length){ctx.save(); 638 // the core sits BEHIND the frame: whatever of it overlaps the frame's silhouette is hidden, so as it rises its sides 639 // come out from behind the top V band 640 if(!o.only){const fp=this.partPolys(PI.frame)[0];ctx.beginPath();ctx.rect(-10,-10,W+20,H+20);fp.forEach((p,i)=>i?ctx.lineTo(p[0],p[1]):ctx.moveTo(p[0],p[1]));ctx.closePath();ctx.clip('evenodd');} 641 pass(cb);ctx.restore();} 642 const cs=o.chevS?clamp(s/.5,.9,1.6):1; 643 for(const a of this.acc){if(!a.on)continue;const Z=this.st[a.k];ctx.fillStyle=Z.act>.5?'#324E48':(apart>.5&&Z.tk<.2?'rgb(120,138,130)':'#121D1A');ctx.beginPath();chev(ctx,a.x,a.y,a.a,(mob?4.2:5.2)*Z.sc*cs);ctx.fill();} 644 ctx.globalAlpha=1; 645 if(o.cards){this.placeCards();this.drawLead(ctx);} 646 } 647 drawLead(ctx){const Ld=this._lead;{ 648 if(Ld&&Ld[3]>.02){ctx.globalAlpha=Ld[3];ctx.strokeStyle=ORANGE;ctx.lineWidth=1.5;ctx.beginPath();ctx.moveTo(Ld[0],Ld[1]);ctx.lineTo(Ld[2],Ld[1]);ctx.stroke(); 649 ctx.fillStyle=ORANGE;ctx.beginPath();ctx.arc(Ld[0],Ld[1],3,0,TAU);ctx.fill();ctx.globalAlpha=1;}} 650 } 651 // ---- the swarm options (?mk=4|5|6; see p1-helpers.js): agents only, batched per piece, tone, shade and shape ---- 652 drawSW(ctx){ 653 const o=this.o,W=this.W,H=this.H,e=this.e,MA=ctx.globalAlpha,P0=SWP[MK],g=this.swg,z=this.s/this.swsR; 654 const rv=o.only?1:ss(.3,.8,e),rc=ss(.02,.3,this.rise),apart=o.only?0:ss(.02,.3,e); 655 const col=b=>{const tn=TONE_MAP[b.tone]||b.tone,Z=this.st[b.k],dim=apart*(1-Z.tk)*(b.edge?.55:.7); 656 const B0=b.edge&&b.v?TONE_BASE.E2:TONE_BASE[tn],A0=b.edge&&b.v?TONE_ACTV.E2:TONE_ACTV[tn],O0=b.edge&&b.v?TONE_OR.E2:TONE_OR[tn]; 657 // the sprinkled orange agents stay orange (a touch lighter while their piece is dimmed); the chosen piece turns orange 658 const c=b.or?mix3(b.edge?TONE_OR.E2:ORG,CRM,dim*.45):mix3(mix3(mix3(B0,TONE_DIM,dim),A0,Z.tk),O0,easeIO(Z.or));return`rgba(${c[0]|0},${c[1]|0},${c[2]|0},`}; 659 const alphaOf=b=>{let al=b.edge?1:P0.fa*(1+.35*this.st[b.k].tk);if(b.tone==='V')al*=rv;if(b.tone==='CS'||b.tone==='CI'||b.tone==='CL'||b.tone==='CR')al*=rc;return Math.min(1,al)*MA}; 660 const draw=b=>{const al=alphaOf(b);if(al<.02)return;const sc=this.st[b.k].sc; 661 if(b.sh){const r=Math.max(1.45,(P0.chr||3)*g*z)*sc;ctx.fillStyle=col(b)+al.toFixed(3)+')';ctx.beginPath(); 662 for(const a of b.list){if(a.on)chev(ctx,a.x,a.y,a.a,r)}ctx.fill();return} 663 const Lh=Math.max(.8,(b.edge?P0.eL:P0.fL)*g*z*.5)*sc;ctx.lineWidth=Math.max(.9,(b.edge?P0.elw:P0.flw)*g*z);ctx.lineCap='round'; 664 ctx.strokeStyle=col(b)+al.toFixed(3)+')';ctx.beginPath(); 665 for(const a of b.list){if(!a.on)continue;const L2=a.ls===undefined?Lh:Lh*a.ls,c=Math.cos(a.a)*L2,sn=Math.sin(a.a)*L2;ctx.moveTo(a.x-c,a.y-sn);ctx.lineTo(a.x+c,a.y+sn);}ctx.stroke();}; 666 const core=PI.core; 667 for(const b of this.bk)if(b.k!==core)draw(b); 668 if(!o.only||o.only==='core'){ctx.save(); 669 if(!o.only){const fp=this.partPolys(PI.frame)[0];ctx.beginPath();ctx.rect(-10,-10,W+20,H+20);fp.forEach((p,i)=>i?ctx.lineTo(p[0],p[1]):ctx.moveTo(p[0],p[1]));ctx.closePath();ctx.clip('evenodd');} 670 for(const b of this.bk)if(b.k===core)draw(b);
670ctx.restore();} 671 } 672 // ---- the logo style options (?mk=1|2|3; see p1-helpers.js) ---- 673 drawMK(ctx){ 674 const o=this.o,s=this.s,e=this.e,dpr=this.dpr,W=this.W,H=this.H,G=this.G,MA=ctx.globalAlpha; 675 const small=!o.only&&946*s<230,rest=MK===3||small,mv=rest&&!small?(this.mv||0):0; 676 const rv=o.only?1:ss(.3,.8,e),rc=ss(.02,.3,this.rise),apart=o.only?0:ss(.02,.3,e); 677 const col=(tone,k)=>{const tn=TONE_MAP[tone]||tone,Z=this.st[k],dim=apart*(1-Z.tk)*(tn==='E'||tn==='V'?.55:.7); 678 const c=mix3(mix3(TONE_BASE[tn],TONE_DIM,dim),TONE_ACTV[tn],Z.tk);return`rgba(${c[0]|0},${c[1]|0},${c[2]|0},`}; 679 const arr=k=>this.arrN&&this.arrN[k]?this.arrC[k]/this.arrN[k]:1; 680 const setT=k=>{const P=PARTS[k],sc=this.st[k].sc,a=s*sc;ctx.setTransform(dpr*a,0,0,dpr*a,dpr*(this.ox+(P.cx*(1-sc)+P.off[0]*e*this.ok)*s),dpr*(this.oy+(P.cy*(1-sc)+P.off[1]*e*this.ok)*s));}; 681 const scr=()=>ctx.setTransform(dpr,0,0,dpr,0,0); 682 const spPx=this.spU*s,esPx=this.esU*s; 683 const plate=k=>{const P=PARTS[k],Z=this.st[k],A=arr(k),core=P.id==='core';setT(k); 684 const polys=core?[G.rh]:P.fc.map(F=>F.p);ctx.beginPath();polys.forEach(q=>{q.forEach((p,i)=>i?ctx.lineTo(p[0],p[1]):ctx.moveTo(p[0],p[1]));ctx.closePath()}); 685 ctx.globalAlpha=MA*A*(core?Math.max(rc,1-rc):1);ctx.fillStyle='rgba(239,231,218,.9)';ctx.fill(); 686 if(rest){ctx.fillStyle=`rgba(50,78,72,${(.075+.09*Z.tk).toFixed(3)})`;ctx.fill();} 687 ctx.globalAlpha=MA;scr();}; 688 const agentsOf=(k,edge)=>{ 689 for(const b of this.bk){if(b.k!==k)continue;const isE=b.tone==='E'||b.tone==='V'||b.tone==='CE'||b.tone==='CS'||b.tone==='CI';if(isE!==edge)continue; 690 let al=1;
690if(b.tone==='V')al*=rv;if(b.tone==='CS'||b.tone==='CI'||b.tone==='CL'||b.tone==='CR')al*=rc; 691 const settledOK=edge?(MK===2&&!small):!rest,sc=this.st[k].sc; 692 let Lh,lw;if(edge){Lh=(MK===2&&!small?esPx*.4:Math.max(2.2,spPx*.35))*sc;lw=MK===2&&!small?Math.max(1.1,MK_LW*s/this.lanes*.62):1.6;} 693 else{Lh=spPx*(MK===2?.24:.3)*sc;lw=MK===2?1.15:clamp(spPx*.19,1,1.6);if(MK===2)al*=.8;} 694 ctx.lineWidth=lw;ctx.lineCap=edge&&MK===2?'butt':'round'; 695 // settled agents 696 if(settledOK&&al>.02){ctx.strokeStyle=col(b.tone,k)+al.toFixed(3)+')';ctx.beginPath();let any=0; 697 for(const a of b.list){if(!a.on||a.p<1)continue;any=1;const c=Math.cos(a.a)*Lh,sn=Math.sin(a.a)*Lh;ctx.moveTo(a.x-c,a.y-sn);ctx.lineTo(a.x+c,a.y+sn);}if(any)ctx.stroke();} 698 // agents still flying in (all options), and in option 3 the edge agents streaming while the mark moves 699 const fl=edge&&mv>.01&&!settledOK;const fa=al*(fl?mv:1);if(fa<.02)continue; 700 ctx.strokeStyle=col(b.tone,k)+fa.toFixed(3)+')';ctx.lineCap='round';if(edge&&!settledOK)ctx.lineWidth=Math.max(1.6,MK_LW*s*.3);ctx.beginPath();let any=0; 701 for(const a of b.list){if(!a.on||(a.p>=1&&!fl))continue;any=1;const c=Math.cos(a.a)*Lh,sn=Math.sin(a.a)*Lh;ctx.moveTo(a.x-c,a.y-sn);ctx.lineTo(a.x+c,a.y+sn);}if(any)ctx.stroke();}}; 702 const outline=k=>{if(MK===2&&!small)return;const P=PARTS[k],A=arr(k)*(1-.8*mv);if(A<.01)return;setT(k); 703 ctx.lineWidth=MK_LW;ctx.lineJoin='round';ctx.lineCap='round';const c=col('E',k); 704 if(P.id==='core'){const pl=(C,al)=>{if(al<.01)return;ctx.strokeStyle=c+(al*A).toFixed(3)+')';ctx.beginPath();C.pts.forEach((p,i)=>i?ctx.lineTo(p[0],p[1]):ctx.moveTo(p[0],p[1]));ctx.stroke()}; 705 pl(G.O,1);pl(G.S,1);pl(G.I,rc);if(G.h>1)pl(G.V,rc);} 706 else{ctx.strokeStyle=c+A.toFixed(3)+')';ctx.stroke(P.ep);if(rv>.01){ctx.strokeStyle=c+(A*rv).toFixed(3)+')';ctx.stroke(P.rp);}} 707 scr();}; 708 const core=PI.core,others=PARTS.map((P,k)=>k).filter(k=>k!==core&&(!o.only||PARTS[k].id===o.only)); 709 others.forEach(plate);others.forEach(k=>agentsOf(k,false));others.forEach(outline);others.forEach(k=>agentsOf(k,true)); 710 if(!o.only||o.only==='core'){ctx.save(); 711 if(!o.only){const fp=this.partPolys(PI.frame)[0];ctx.beginPath();ctx.rect(-10,-10,W+20,H+20);fp.forEach((p,i)=>i?ctx.lineTo(p[0],p[1]):ctx.moveTo(p[0],p[1]));ctx.closePath();ctx.clip('evenodd');} 712 plate(core);agentsOf(core,false);outline(core);agentsOf(core,true);ctx.restore();} 713 } 714 /* ---- the breakdown labels (HTML links). Each has two measured states: the label and the full card. ---- */ 715 measureCards(){const C=this.o.cards,below=this.W<1024,cw=this._cw||340; 716 for(const el of C){const body=el._b||(el._b=el.querySelector('.pc-body'));const s0=el.style.cssText,b0=body.style.cssText; 717 el.style.setProperty('--a','0');el.style.width=below?'':'max-content';body.style.height='0px';body.style.display='none'; 718 const lw=el.offsetWidth+4,lh=el.offsetHeight;body.style.display=''; 719 el.style.setProperty('--a','1');el.style.width=below?'':cw+'px';body.style.height='auto'; 720 const ch=el.offsetHeight,bh=body.offsetHeight; 721 el.style.cssText=s0;body.style.cssText=b0;el._a=el._bh=el._w=null;el._m={lw,lh,ch,bh};} 722 if(below){let sum=0,mx=0;C.forEach(el=>{sum+=el._m.lh;mx=Math.max(mx,el._m.ch-el._m.lh)});this.cardH=sum+6*(C.length-1)+mx;} 723 else this.cardH=Math.max(...C.map(el=>el._m.ch)); 724 document.documentElement.style.setProperty('--pch',this.cardH+'px');} 725 // card width on wide screens: whatever room the broken-apart mark leaves at the side 726 cardW(){if(this.below)return;const F=this.FT.E;let mn=1e9,mx=-1e9; 727 PARTS.forEach(P=>P.faces.forEach(Fc=>Fc.p.forEach(p=>{const x=F.ox+(P.cx+(p[0]-P.cx)*(1+GROW)+P.off[0])*F.s;mn=Math.min(mn,x);mx=Math.max(mx,x);}))); 728 const room=Math.min(mn,this.W-mx)-30-22;const w=Math.round(clamp(room,250,400)); 729 if(this._cw!==w){this._cw=w;this.measureCards();} 730 // where each label sits across: just outside the mark, within the rows its full card will cover (mark units, apart and grown) 731 const s=F.s,polys=PARTS.map(P=>P.faces.map(Fc=>Fc.p.map(p=>[P.cx+(p[0]-P.cx)*(1+GROW)+P.off[0],P.cy+(p[1]-P.cy)*(1+GROW)+P.off[1]]))); 732 for(const el of this.o.cards){const k=PI[el.dataset.part],P=PARTS[k],m=el._m,l=P.side==='l';let y0=1e9,y1=-1e9,own=l?1e9:-1e9;
733 polys[k].forEach(q=>q.forEach(p=>{y0=Math.min(y0,p[1]);y1=Math.max(y1,p[1]);own=l?Math.min(own,p[0]):Math.max(own,p[0]);})); 734 let best=null; 735 for(const fy of [.5,.4,.6,.3,.7,.2,.8]){const top=y0+(y1-y0)*fy-m.lh/2/s,b0=top-26/s,b1=top+(m.ch+26)/s;let ext=own; 736 const take=x=>{ext=l?Math.min(ext,x):Math.max(ext,x)};polys.forEach(pp=>pp.forEach(q=>bandX(q,b0,b1,take))); 737 const cost=Math.abs(ext-own)+Math.abs(fy-.5)*(y1-y0)*.35;if(!best||cost<best.cost)best={cost,ext,fy};} 738 m.xm=best.ext;m.fy=best.fy;}} 739 placeCards(){ 740 const C=this.o.cards,vis=RM?(this.e>.5?1:0):ss(.45,.9,this.e),items=[];this._lead=null; 741 for(const el of C){const k=PI[el.dataset.part],P=PARTS[k],Z=this.st[k],m=el._m;if(!m)continue; 742 const a=RM?(Z.act>.5?1:0):ss(.25,.9,Z.act); 743 const av=a.toFixed(3);if(el._a!==av){el.style.setProperty('--a',av);el._a=av;} 744 const bh=(m.bh*a).toFixed(1);if(el._bh!==bh){el._b.style.height=bh+'px';el._bh=bh;} 745 const op=vis.toFixed(3);if(el._op!==op){el.style.opacity=op;el._op=op;} 746 el.classList.toggle('live',vis>.5);el.classList.toggle('on',a>.5&&vis>.5);el.classList.toggle('hot',this.hot===P.id); 747 if(!this.below){let y0=1e9,y1=-1e9;this.partPolys(k).forEach(q=>q.forEach(p=>{y0=Math.min(y0,p[1]);y1=Math.max(y1,p[1]);})); 748 items.push({el,a,side:P.side,w:lerp(m.lw,this._cw,a),h:lerp(m.lh,m.ch,a),y:y0+(y1-y0)*(m.fy==null?.5:m.fy)-m.lh/2,xm:m.xm});} 749 } 750 if(this.below)return; 751 // the spine (AI Audit): its label sits right beside the spine's lower part and its card opens from there, below the 752 // right wing and clear of every other piece (as G). The labels on the right then stack above it, never over it. 753 const S=items.find(i=>i.side==='s');let rBot=this.H-16; 754 if(S){const k=PI.spine;let x1=-1e9,y0=1e9,y1=-1e9;this.partPolys(k).forEach(q=>q.forEach(p=>{x1=Math.max(x1,p[0]);y0=Math.min(y0,p[1]);y1=Math.max(y1,p[1]);})); 755 S.x=Math.min(x1+22,this.W-S.w-16); 756 let yb=-1e9;for(let j=0;j<PARTS.length;j++){if(j===k)continue;this.partPolys(j).forEach(q=>{let a0=1e9,a1=-1e9,b1=-1e9;q.forEach(p=>{a0=Math.min(a0,p[0]);a1=Math.max(a1,p[0]);b1=Math.max(b1,p[1]);}); 757 if(a1>S.x-6&&a0<S.x+S.w+6)yb=Math.max(yb,b1);});} 758 const bot=this.H-16,ty=y1-(y1-y0)*.3-S.el._m.lh/2; 759 if(yb+16+S.h<=bot)S.y=Math.max(Math.min(ty,bot-S.h),yb+16); 760 else{// short screen (as G): no room under the wing, so the card rises and steps right just past the wing, 761 // and a short ink leader ties it back to the spine, under the wing 762 S.y=Math.min(ty,bot-S.h);let x=x1+22; 763 for(let j=0;j<PARTS.length;j++){if(j===k)continue;this.partPolys(j).forEach(q=>{let a0=1e9,a1=-1e9,b0=1e9,b1=-1e9;q.forEach(p=>{a0=Math.min(a0,p[0]);a1=Math.max(a1,p[0]);b0=Math.min(b0,p[1]);b1=Math.max(b1,p[1]);}); 764 if(a1>x1&&b1>S.y-12&&b0<S.y+S.h+12)x=Math.max(x,a1+22);});} 765 S.x=Math.min(x,this.W-S.w-16); 766 const ly=clamp(Math.max(ty+S.el._m.lh/2,yb+14),S.y+18,S.y+S.h-18);if(S.x>x1+30)this._lead=[x1+6,ly,S.x,S.a*vis];} 767 rBot=S.y-12;} 768 for(const side of['l','r'])stack(items.filter(i=>i.side===side).sort((a,b)=>a.y-b.y),16,side==='r'?rBot:this.H-16,12); 769 for(const it of items){const X=this.ox+it.xm*this.s;let lx=it.side==='s'?it.x:it.side==='l'?X-30-it.w:X+30;lx=clamp(lx,16,this.W-it.w-16); 770 const w=it.w.toFixed(1);if(it.el._w!==w){it.el.style.width=w+'px';it.el._w=w;} 771 const tr=`translate(${lx.toFixed(1)}px,${it.y.toFixed(1)}px)`;if(it.el._tr!==tr){it.el.style.transform=tr;it.el._tr=tr;}} 772 } 773} 774 775/* ============================================================ 776 THE REFERENCE SWARM (founder, 1 Oct 2026): "use the same swarms and effect, exactly the same" as the concept page 777 share3/swarm.html, in the site's green. Short dashes ride a slow curl-noise flow, gather into a shape, hold it with a 778 faint tremor and leave trails (each frame only fades the last one, as the reference paints its paper at 34%); about 779 one agent in 97 is an orange chevron. The physics, timings, sampling and sizes below are the reference's own. 780 It drives (1) the landing: the icon, "Where intelligence begins." and the ADROLAB wordmark in turn, in the band 781 between the nav and "Ask the lab anything" (RefHero), and (2) every Stage, the breakdown and the pages' pieces: the official 782 mark's pieces, each piece's ink sampled the way the reference samples its shapes (Stage.ref*). ?mk=<n> shows the 783 earlier swarms instead. 784 ============================================================ */ 785const REFM=!Q.has('mk'); 786const RS_INK=[50,78,72]; 787const rrnd=(a,b)=>a+Math.random()*(b-a); 788const rsEase=t=>t<.5?4*t*t*t:1-Math.pow(-2*t+2,3)/2; 789function rsPsi(x,y,t){ 790 return Math.sin(x*.0031+t*.13)*Math.cos(y*.0043-t*.09)*1.0
791 + Math.sin((x+y)*.0017-t*.07+1.3)*0.8 792 + Math.cos(x*.0071-y*.0023+t*.19)*0.35; 793} 794function rsField(x,y,t){ // angle of a divergence-free (curl) flow: agents stream without collapsing 795 const e=2,dx=rsPsi(x+e,y,t)-rsPsi(x-e,y,t),dy=rsPsi(x,y+e,t)-rsPsi(x,y-e,t);return Math.atan2(-dx,dy); 796} 797// the reference's sampler: n points (x, y, angle) of a shape drawn in alpha on cv, mapped into box 798function rsSample(cv,n,box,hatch){ 799 const w=cv.width,h=cv.height,d=cv.getContext('2d').getImageData(0,0,w,h).data; 800 const bc=document.createElement('canvas');bc.width=w;bc.height=h; 801 const bx=bc.getContext('2d');bx.filter='blur(4px)';bx.drawImage(cv,0,0); 802 const bd=bx.getImageData(0,0,w,h).data; 803 let area=0;for(let i=3;i<d.length;i+=4)if(d[i]>110)area++; 804 const step=Math.max(1,Math.sqrt(area/n)*0.94); 805 let pts=[]; 806 for(let y=step/2;y<h;y+=step)for(let x=step/2;x<w;x+=step){ 807 const jx=clamp(Math.round(x+rrnd(-.3,.3)*step),0,w-1),jy=clamp(Math.round(y+rrnd(-.3,.3)*step),0,h-1); 808 if(d[(jy*w+jx)*4+3]>110){ 809 let a; 810 if(hatch!=null)a=hatch; 811 else{const g=(xx,yy)=>bd[(clamp(yy,0,h-1)*w+clamp(xx,0,w-1))*4+3]; 812 const gx=g(jx+2,jy)-g(jx-2,jy),gy=g(jx,jy+2)-g(jx,jy-2);a=Math.atan2(gy,gx)+Math.PI/2;} 813 pts.push([jx,jy,a]); 814 } 815 } 816 for(let i=pts.length-1;i>0;i--){const j=(Math.random()*(i+1))|0;[pts[i],pts[j]]=[pts[j],pts[i]]} 817 if(pts.length>n)pts.length=n; 818 const base=pts.length;while(pts.length<n&&base)pts.push(pts[(Math.random()*base)|0]); 819 const sx=box.w/w,sy=box.h/h; 820 return pts.map(p=>({x:box.x+p[0]*sx,y:box.y+p[1]*sy,a:p[2]})); 821} 822// the accent agent: a small chevron 823function rsChev(c,x,y,a,r){const co=Math.cos(a),s=Math.sin(a);c.moveTo(x+co*r,y+s*r);c.lineTo(x-co*r*.8-s*r*.75,y-s*r*.8+co*r*.75); 824 c.lineTo(x-co*r*.3,y-s*r*.3);c.lineTo(x-co*r*.8+s*r*.75,y-s*r*.8-co*r*.75);c.closePath();} 825// trails on a see-through layer: fade what is there by k (the reference paints its paper at 34% each frame) 826function rsFade(c,k){c.save();c.setTransform(1,0,0,1,0,0);if(k>=1)c.clearRect(0,0,c.canvas.width,c.canvas.height); 827 else{c.globalCompositeOperation='destination-out';c.globalAlpha=k;c.fillStyle='#000';c.fillRect(0,0,c.canvas.width,c.canvas.height);}c.restore();} 828const rsCol=c=>`rgb(${c[0]|0},${c[1]|0},${c[2]|0})`; 829 830/* ---- (1) THE LANDING: the reference's hero, in the band between the nav and the headline ---- */ 831function RefHero(land,head){ 832 const cv=document.createElement('canvas');cv.className='land-swarm';cv.setAttribute('aria-hidden','true'); 833 land.insertBefore(cv,land.querySelector('.land-in'));const ctx=cv.getContext('2d'); 834 let W=0,H=0,DPR=1,agents=[],targets={},wordImg=null;const mouse={x:-1e4,y:-1e4,t:-1e4}; 835 // the band: from just under the nav to just above the headline, the full width 836 const place=()=>{const L=land.getBoundingClientRect(),nav=land.querySelector('.nav'),nb=nav?nav.getBoundingClientRect().bottom-L.top:60; 837 const top=Math.round(nb+4),bot=Math.round(head.getBoundingClientRect().top-L.top-6);cv.style.top=top+'px';cv.style.height=Math.max(60,bot-top)+'px';}; 838 function size(){place();DPR=Math.min(2,devicePixelRatio||1);W=cv.clientWidth;H=cv.clientHeight;cv.width=W*DPR;cv.height=H*DPR;ctx.setTransform(DPR,0,0,DPR,0,0);} 839 const nWant=()=>W<700?Math.round(clamp(W*H/80,700,1200)):Math.round(clamp(W*H/380,520,1700)); 840 function makeAgents(){const n=nWant();agents=[]; 841 for(let i=0;i<n;i++)agents.push({x:rrnd(0,W),y:rrnd(0,H),vx:rrnd(-1,1),vy:rrnd(-1,1),a:rrnd(0,TAU),acc:i%97===0,d:Math.random(),s:rrnd(.7,1.3),ph:rrnd(0,TAU),tx:0,ty:0,ta:0});} 842 function buildTargets(){ 843 const n=agents.length,mob=W<700,cy=H/2; 844 // the icon: the official mark, as tall as the band allows 845 {const R=.8,oc=document.createElement('canvas');oc.width=Math.round(930*R);oc.height=Math.round(956*R);const o=oc.getContext('2d'); 846 o.setTransform(R,0,0,R,-8*R,-22*R);o.fillStyle='#000';o.fill(MARK_FULL,'evenodd'); 847 const mh=Math.min(H*.7,W*(mob?.62:.5)),mw=mh*930/956;targets.mark=rsSample(oc,n,{x:W/2-mw/2,y:cy-mh/2,w:mw,h:mh});} 848 // the line: in the site's type, on one, two or three lines, whichever sets it biggest in the band 849 {const F=fs=>`500 ${fs}px "Switzer", system-ui, sans-serif`,o0=document.createElement('canvas').getContext('2d');o0.font=F(100); 850 const bw=Math.min(W*.92,1240),bh=H*.66;let best=null; 851 for(const lines of [['Where intelligence begins.'],['Where intelligence','begins.'],['Where','intelligence','begins.']]){ 852 const w100=Math.max(...lines.map(l=>o0.measureText(l).width)),fs=Math.min(bw/w100*100,bh/(lines.length*1.02+.12));if(!best||fs>best.fs)best={lines,fs,w100};} 853 const R=2,fs=best.fs,lh=fs*1.02,tw=best.w100*fs/100,th=lh*best.lines.length+fs*.12,oc=document.createElement('canvas'); 854 oc.width=Math.ceil(tw*R+12);oc.height=Math.ceil(th*R+12);const o=oc.getContext('2d');o.font=F(fs*R);o.textAlign='center';o.fillStyle='#000';
855 best.lines.forEach((l,i)=>o.fillText(l,oc.width/2,6+R*(lh*i+fs*.86))); 856 const w=oc.width/R,h=oc.height/R;targets.words=rsSample(oc,n,{x:W/2-w/2,y:cy-h/2,w,h},-1.05);} 857 // the wordmark 858 if(wordImg){const oc=document.createElement('canvas');oc.width=wordImg.width*2;oc.height=wordImg.height*2;oc.getContext('2d').drawImage(wordImg,0,0,oc.width,oc.height); 859 let bw=Math.min(W*(mob?.86:.62),980),bh=bw*wordImg.height/wordImg.width;if(bh>H*.5){bh=H*.5;bw=bh*wordImg.width/wordImg.height;} 860 targets.word=rsSample(oc,n,{x:W/2-bw/2,y:cy-bh/2,w:bw,h:bh},Math.PI/2);} 861 for(const k in targets){const T=targets[k],cx=W/2;T.sort((p,q)=>Math.atan2(p.y-cy,p.x-cx)-Math.atan2(q.y-cy,q.x-cx));} 862 } 863 const SEQ=[ 864 ['working',.1],['gathering',1.7,'mark'],['formed',2.6,'mark'],['dispersing',.8], 865 ['working',1.8],['gathering',1.7,'words'],['formed',2.8,'words'],['dispersing',.8], 866 ['working',1.8],['gathering',1.6,'word'],['formed',2.2,'word'],['dispersing',.8],['working',1.4]]; 867 const SEQT=SEQ.reduce((s,p)=>s+p[1],0); 868 function phaseAt(t){t%=SEQT;for(const p of SEQ){if(t<p[1])return{name:p[0],shape:p[2],t,dur:p[1]};t-=p[1];}return{name:'working',t:0,dur:1};} 869 let lastPhase='',lastShape='',T0=performance.now(); 870 function assign(shape){const T=targets[shape];if(!T)return;const cx=W/2,cy=H/2; 871 const idx=agents.map((a,i)=>[Math.atan2(a.y-cy,a.x-cx),i]).sort((p,q)=>p[0]-q[0]); 872 idx.forEach(([_,i],k)=>{const p=T[k%T.length],a=agents[i];a.tx=p.x;a.ty=p.y;a.ta=p.a;});} 873 function step(now){ 874 const t=(now-T0)/1000,ph=phaseAt(t); 875 if(ph.name!==lastPhase||ph.shape!==lastShape){ 876 if(ph.name==='gathering')assign(ph.shape); 877 if(ph.name==='dispersing')for(const a of agents){const dx=a.x-W/2,dy=a.y-H/2,dd=Math.hypot(dx,dy)+1,f=rrnd(3,8);a.vx+=dx/dd*f+rrnd(-1.5,1.5);a.vy+=dy/dd*f+rrnd(-1.5,1.5);} 878 lastPhase=ph.name;lastShape=ph.shape;} 879 const mouseOn=(now-mouse.t)<2500,R=Math.min(260,W*.3); 880 for(const a of agents){ 881 let ax=0,ay=0; 882 if(ph.name==='gathering'||ph.name==='formed'){ 883 const p=ph.name==='formed'?1:clamp((ph.t/ph.dur-a.d*.3)/.5,0,1); 884 const k=.006+rsEase(p)*.07,damp=.84-p*.05; 885 const bx=a.tx+Math.sin(t*1.3+a.ph)*.5,by=a.ty+Math.cos(t*1.1+a.ph)*.5; 886 if(p<=0){const f=rsField(a.x,a.y,t);ax+=(Math.cos(f)*1.1*a.s-a.vx)*.04;ay+=(Math.sin(f)*1.1*a.s-a.vy)*.04;} 887 else{ax+=(bx-a.x)*k;ay+=(by-a.y)*k;a.vx*=damp;a.vy*=damp;} 888 }else{ 889 const f=rsField(a.x,a.y,t)+Math.sin(t*.7+a.ph)*.35,sp=(ph.name==='dispersing'?.8:1.15)*a.s; 890 ax+=(Math.cos(f)*sp-a.vx)*.035;ay+=(Math.sin(f)*sp-a.vy)*.035; 891 } 892 if(mouseOn){const dx=mouse.x-a.x,dy=mouse.y-a.y,d=Math.hypot(dx,dy); 893 if(d<R&&d>1){const f=(1-d/R),g=ph.name==='formed'?.35:1;ax+=(dx/d*.22*f-dy/d*.18*f)*g;ay+=(dy/d*.22*f+dx/d*.18*f)*g;if(d<26){ax-=dx/d*.3;ay-=dy/d*.3}}} 894 a.vx+=ax;a.vy+=ay; 895 const vmax=ph.name==='gathering'?16:7,v=Math.hypot(a.vx,a.vy);if(v>vmax){a.vx*=vmax/v;a.vy*=vmax/v} 896 a.x+=a.vx;a.y+=a.vy; 897 if(ph.name==='working'||ph.name==='dispersing'){if(a.x<-20)a.x+=W+40;if(a.x>W+20)a.x-=W+40;if(a.y<-20)a.y+=H+40;if(a.y>H+20)a.y-=H+40;} 898 const settle=(ph.name==='formed'||ph.name==='gathering')?clamp(1-v/1.6,0,1):0,va=Math.atan2(a.vy,a.vx); 899 let target=v>.05?va:a.a;if(settle>0)target=a.ta; 900 let da=((target-a.a)%TAU+TAU*1.5)%TAU-Math.PI; 901 if(settle>0&&Math.abs(da)>Math.PI/2)da=((da+Math.PI)%TAU+TAU*1.5)%TAU-Math.PI; // lines are symmetric 902 a.a+=da*(settle>0?.12:.2); 903 } 904 } 905 function draw(){ 906 rsFade(ctx,RM?1:.34);ctx.setTransform(DPR,0,0,DPR,0,0);ctx.lineCap='round'; 907 const mob=W<700;ctx.strokeStyle=rsCol(RS_INK);ctx.lineWidth=mob?1.1:1.35;ctx.beginPath();const L=mob?2.4:3.6; 908 for(const a of agents){if(a.acc)continue;const c=Math.cos(a.a)*L,s=Math.sin(a.a)*L;ctx.moveTo(a.x-c,a.y-s);ctx.lineTo(a.x+c,a.y+s);} 909 ctx.stroke();ctx.fillStyle=ORANGE;ctx.beginPath();for(const a of agents){if(a.acc)rsChev(ctx,a.x,a.y,a.a,mob?4.2:5.5);}ctx.fill(); 910 } 911 const settleAll=()=>{assign('mark');for(const a of agents){a.x=a.tx;a.y=a.ty;a.a=a.ta;}draw();}; 912 let vis=true;new IntersectionObserver(es=>{vis=es[0].isIntersecting}).observe(cv); 913 addEventListener('pointermove',e=>{const r=cv.getBoundingClientRect();mouse.x=e.clientX-r.left;mouse.y=e.clientY-r.top;mouse.t=performance.now();},{passive:true}); 914 const talking=()=>land.classList.contains('talking'); 915 const rebuild=()=>
915{if(talking())return;const n0=agents.length;size();if(Math.abs(nWant()-n0)>n0*.25)makeAgents();buildTargets(); 916 if(RM)settleAll();else if(lastPhase==='gathering'||lastPhase==='formed')assign(lastShape);}; 917 size();makeAgents();buildTargets(); 918 const wsrc=(document.querySelector('.brand img.w')||{}).src; 919 if(wsrc)loadImg(wsrc).then(i=>{wordImg=i;buildTargets();if(lastShape==='word'&&(lastPhase==='gathering'||lastPhase==='formed'))assign('word');}); 920 if(document.fonts)document.fonts.load('500 100px "Switzer"').then(()=>{if(!(lastShape==='words'&&(lastPhase==='gathering'||lastPhase==='formed')))buildTargets();if(RM)settleAll();}); 921 let rt;addEventListener('resize',()=>{clearTimeout(rt);rt=setTimeout(rebuild,160)}); 922 if(RM||FREEZE){settleAll();return} 923 T0=performance.now(); 924 const loop=now=>{if(vis&&!talking()){if(Math.abs(cv.clientWidth-W)>2)rebuild();step(now);draw();}requestAnimationFrame(loop)};requestAnimationFrame(loop); 925} 926function loadImg(src){return new Promise(r=>{const i=new Image();i.onload=()=>r(i);i.src=src})} 927 928/* ---- (2) THE STAGES: the official mark's pieces, sampled and moved as the reference does ---- */ 929let RS_RAS=null; 930// the official ink at 1 px per mark unit, and a blurred copy for the dashes' angles (the reference's tangent trick) 931function rsRaster(){if(RS_RAS)return RS_RAS;const W=950,H=1000,c=document.createElement('canvas');c.width=W;c.height=H;const x=c.getContext('2d'); 932 x.fillStyle='#000';x.fill(MARK_FULL,'evenodd');const d=x.getImageData(0,0,W,H).data; 933 const b=document.createElement('canvas');b.width=W;b.height=H;const bx=b.getContext('2d');bx.filter='blur(5px)';bx.drawImage(c,0,0); 934 return RS_RAS={W,H,d,bd:bx.getImageData(0,0,W,H).data};} 935// agents at spacing sp (mark units): the ink, each point to the piece that owns it; the strokes a piece shows only while 936// apart (V); and the core, laid along its cube's outline so it can rise and flatten 937function rsMarkAgents(sp){ 938 const I=swInk(),K=rsRaster(),out=[],W=K.W,H=K.H,hw=SW_HW; 939 const g=(x,y)=>K.bd[(clamp(Math.round(y),0,H-1)*W+clamp(Math.round(x),0,W-1))*4+3]; 940 const ang=(x,y)=>{const gx=g(x+2,y)-g(x-2,y),gy=g(x,y+2)-g(x,y-2);return Math.atan2(gy,gx)+Math.PI/2}; 941 for(let y=22+sp/2;y<980;y+=sp)for(let x=8+sp/2;x<940;x+=sp){ 942 const px=clamp(x+rrnd(-.3,.3)*sp,0,W-1),py=clamp(y+rrnd(-.3,.3)*sp,0,H-1); 943 if(K.d[(Math.round(py)*W+Math.round(px))*4+3]<=110)continue; 944 const o=I.own(px,py);if(!o||o.k===PI.core)continue;out.push({k:o.k,hx:px,hy:py,a0:ang(px,py),cat:'E'});} 945 // apart-only strokes: the band round each such line, minus the ink the piece already has 946 PARTS.forEach((P,k)=>{if(P.id==='core')return;const RV=I.RV.filter(c=>c.k===k);if(!RV.length)return; 947 let x0=1e9,x1=-1e9,y0=1e9,y1=-1e9;RV.forEach(c=>{x0=Math.min(x0,c.bb[0]+24);x1=Math.max(x1,c.bb[1]-24);y0=Math.min(y0,c.bb[2]+24);y1=Math.max(y1,c.bb[3]-24)}); 948 for(let y=y0+sp/2;y<y1;y+=sp)for(let x=x0+sp/2;x<x1;x+=sp){const px=x+rrnd(-.3,.3)*sp,py=y+rrnd(-.3,.3)*sp,r=I.revD(k,px,py);if(!r)continue; 949 const ik=I.ink(px,py);if(r.d>=(ik?hw+2.5:hw))continue;if(ik){const o=I.own(px,py);if(o&&o.k===k)continue} 950 out.push({k,hx:px,hy:py,a0:hw-r.d<6?r.a:Math.PI/2,cat:'V'});}}); 951 // the core: its outline chains as 28-unit bands; dashes near a band's edge follow it, inside they stand upright (as the 952 // reference's blurred-tangent angles do in a thick stroke) 953 const k=PI.core;[['O','C'],['S','X'],['I','X'],['V','X']].forEach(([ch,cat])=>{const L=CUBE1[ch].L,n=Math.max(2,Math.round(L*2*hw/(sp*sp))); 954 for(let j=0;j<n;j++){const lo=rrnd(-hw+1,hw-1);out.push({k,core:1,ch,f0:Math.random(),lo,vert:Math.abs(lo)<hw-6,hx:0,hy:0,a0:0,cat});}}); 955 out.forEach((a,i)=>{a.acc=i%97===0;a.d=Math.random();a.s=rrnd(.7,1.3);a.ph=rrnd(0,TAU);a.on=true;a.edge=1;a.tone=a.cat==='V'?'V':'E';}); 956 return out; 957} 958Object.assign(Stage.prototype,{ 959 refInit(){ 960 const o=this.o,cv=this.cv; 961 if(!this.ov){const v=document.createElement('canvas');v.setAttribute('aria-hidden','true');v.style.cssText='position:absolute;pointer-events:none;margin:0';cv.after(v);this.ov=v;this.oc=v.getContext('2d');} 962 const v=this.ov,zi=getComputedStyle(cv).zIndex;v.style.left=cv.offsetLeft+'px';v.style.top=cv.offsetTop+'px';v.style.width=cv.clientWidth+'px';v.style.height=cv.clientHeight+'px'; 963 v.style.zIndex=zi==='auto'?'':zi;v.width=cv.width;v.height=cv.height; 964 // spacing: ~5 px between agents at the largest size this stage shows the mark (the reference's letters are this dense;
965 // the mark's strokes are thinner than its letters, so at its 7.6 px they read as dashed outlines) 966 const sR=Math.max(...Object.values(this.FT).map(F=>F.s));let sp=(this.mob?4.4:5)/sR; 967 const key='ref:'+Math.round(sp*10)+':'+(o.only||''); 968 if(this.refKey!==key){this.refKey=key;let A=rsMarkAgents(sp); 969 const cap=o.only?2800:4200,nk=o.only?A.filter(a=>a.k===PI[o.only]).length:A.length;if(nk>cap){sp*=Math.sqrt(nk/cap);A=rsMarkAgents(sp);} 970 if(o.only)A=A.filter(a=>a.k===PI[o.only]);this.agents=A;this.bk=[]; 971 const m=new Map();for(const a of A){if(a.acc)continue;const kk=a.k+':'+a.cat;if(!m.has(kk))m.set(kk,{k:a.k,cat:a.cat,list:[]});m.get(kk).list.push(a);} 972 this.rg=[...m.values()];} 973 }, 974 refUpdate(dt){ 975 const o=this.o,s=this.s,e=this.e,ox=this.ox,oy=this.oy,G=this.G,W=this.W,H=this.H; 976 const now=performance.now(),mOn=!RM&&this.mouse&&now-this.mt<2500,R=Math.min(260,W*.3),tt=RM?99:this.t; 977 // the pages: the agents flow for a moment, then gather (the reference's "working" then "gathering"); the breakdown 978 // starts formed (the landing above has had the intro) 979 const T1=o.refFormed?-99:.4,TG=1.7,nsub=RM?1:clamp(Math.round(dt*60),1,3); 980 for(const a of this.agents){const P=PARTS[a.k],sc=this.st[a.k].sc; 981 if(a.core){const r=atChain(G[a.ch],a.f0);a.hx=r[0]-Math.sin(r[2])*a.lo;a.hy=r[1]+Math.cos(r[2])*a.lo;a.a0=a.vert?Math.PI/2:r[2];} 982 const tx=ox+(P.cx+(a.hx-P.cx)*sc+P.off[0]*e*this.ok)*s,ty=oy+(P.cy+(a.hy-P.cy)*sc+P.off[1]*e*this.ok)*s; 983 if(!a.init){a.init=1;if(o.refFormed||RM){a.x=tx;a.y=ty;a.a=a.a0;}else{a.x=rrnd(0,W);a.y=rrnd(0,H);a.a=rrnd(0,TAU);}a.vx=rrnd(-1,1);a.vy=rrnd(-1,1);} 984 if(RM){a.x=tx;a.y=ty;a.a=a.a0;continue} 985 const p=clamp(((tt-T1)/TG-a.d*.3)/.5,0,1); 986 for(let i=0;i<nsub;i++){ 987 let ax=0,ay=0; 988 if(p<=0){const f=rsField(a.x,a.y,tt)+Math.sin(tt*.7+a.ph)*.35;ax+=(Math.cos(f)*1.15*a.s-a.vx)*.035;ay+=(Math.sin(f)*1.15*a.s-a.vy)*.035;} 989 else{const k=.006+rsEase(p)*.07,damp=.84-p*.05,bx=tx+Math.sin(tt*1.3+a.ph)*.5,by=ty+Math.cos(tt*1.1+a.ph)*.5;ax+=(bx-a.x)*k;ay+=(by-a.y)*k;a.vx*=damp;a.vy*=damp;} 990 if(mOn){const dx=this.mouse[0]-a.x,dy=this.mouse[1]-a.y,d=Math.hypot(dx,dy); 991 if(d<R&&d>1){const f=(1-d/R),gg=p>=1?.35:1;ax+=(dx/d*.22*f-dy/d*.18*f)*gg;ay+=(dy/d*.22*f+dx/d*.18*f)*gg;if(d<26){ax-=dx/d*.3;ay-=dy/d*.3}}} 992 a.vx+=ax;a.vy+=ay;const vmax=p>0&&p<1?16:7,v=Math.hypot(a.vx,a.vy);if(v>vmax){a.vx*=vmax/v;a.vy*=vmax/v} 993 a.x+=a.vx;a.y+=a.vy; 994 if(p<=0){if(a.x<-20)a.x+=W+40;if(a.x>W+20)a.x-=W+40;if(a.y<-20)a.y+=H+40;if(a.y>H+20)a.y-=H+40;} 995 const settle=p>0?clamp(1-v/1.6,0,1):0,va=Math.atan2(a.vy,a.vx);let target=v>.05?va:a.a;if(settle>0)target=a.a0; 996 let da=((target-a.a)%TAU+TAU*1.5)%TAU-Math.PI;if(settle>0&&Math.abs(da)>Math.PI/2)da=((da+Math.PI)%TAU+TAU*1.5)%TAU-Math.PI; 997 a.a+=da*(settle>0?.12:.2);} 998 } 999 }, 1000 refDraw(MA){ 1001 const c=this.oc,o=this.o,e=this.e;if(!c)return;rsFade(c,RM?1:.34);c.setTransform(this.dpr,0,0,this.dpr,0,0);if(MA<.01)return; 1002 const rv=o.only?1:ss(.3,.8,e),rc=ss(.02,.3,this.rise),apart=o.only?0:ss(.02,.3,e),mob=this.mob,L=mob?3.2:3.6; 1003 c.lineCap='round';c.lineWidth=mob?1.3:1.35; 1004 const alphaOf=g=>(g.cat==='V'?rv:g.cat==='X'?rc:1)*MA; 1005 // a piece: green; a little paler while another piece is chosen; orange when it is the chosen one 1006 const colOf=k=>{const Z=this.st[k];return rsCol(mix3(mix3(RS_INK,TONE_DIM,apart*(1-Z.tk)*.45),ORG,easeIO(Z.or||0)))}; 1007 const pass=g=>{const al=alphaOf(g);if(al<.02)return;c.globalAlpha=al;c.strokeStyle=colOf(g.k);c.beginPath(); 1008 for(const a of g.list){const x=Math.cos(a.a)*L,y=Math.sin(a.a)*L;c.moveTo(a.x-x,a.y-y);c.lineTo(a.x+x,a.y+y);}c.stroke();}; 1009 const core=PI.core;this.rg.forEach(g=>{if(g.k!==core)pass(g)}); 1010 const cg=this.rg.filter(g=>g.k===core); 1011 if(cg.length){c.save(); 1012 // the core sits BEHIND the frame: as it rises its sides come out from behind the top V band 1013 if(!o.only){const fp=this.partPolys(PI.frame)[0];c.beginPath();c.rect(-10,-10,this.W+20,this.H+20);fp.forEach((p,i)=>i?c.lineTo(p[0],p[1]):c.moveTo(p[0],p[1]));c.closePath();c.clip('evenodd');} 1014 cg.forEach(pass);
1014c.restore();} 1015 c.fillStyle=ORANGE;c.beginPath();let any=0; 1016 for(const a of this.agents){if(!a.acc)continue;const al=a.cat==='V'?rv:a.cat==='X'?rc:1;if(al<.5)continue;any=1;rsChev(c,a.x,a.y,a.a,5.5);} 1017 c.globalAlpha=MA;if(any)c.fill();c.globalAlpha=1; 1018 } 1019}); 1020/* ============================================================ 1021 SMALL SCENES: dividers (slow), belief glyphs (move on hover), the lab floor (slow) 1022 ============================================================ */ 1023// Dividers. L, round 3 (30 Sep 2026): the colour fades from one section into the next through a skyline of plain isometric 1024// cubes (ported from "Isometric World": light top, mid left, dark right, thin darker edges, bobbing gently). 1025// Founder's round-3 notes: "back to cubes, but less" and "they look like they are suddenly cutting off". So the default 1026// ('cubes') is calmer than round 2 ('old'): smaller cubes, a lower maximum height, and a sparse skyline with gaps (the back 1027// rows are thinned out in clusters, so about half the blocks of round 2 show), and long eased fades at both edges: the cubes 1028// rise out of the colour above over the top ~75% and sink into the colour below over the bottom ~55% (smoothstep, not linear), 1029// so neither edge has a line. 1030// ?div=cubes (the default) | old (round 2's dense skyline and short fade, for comparison) | mix | logos (the logo blocks of 1031// round 2's second pass; ?divr=<0..1> sets the mix share). 1032// Every block is a sprite pre-rendered per size, palette and DPR, then blitted: no path is traced per frame. Static under 1033// reduced motion (t = 0); the loop only draws a divider while it is on screen. 1034const DIV_MODE=({cubes:1,old:1,mix:1,logos:1})[Q.get('div')]?Q.get('div'):'cubes'; // founder's decision, 30 Sep 2026 (round 3): plain cubes 1035const DIV_RATIO=DIV_MODE==='cubes'||DIV_MODE==='old'?0:DIV_MODE==='logos'?1:Q.has('divr')?clamp(parseFloat(Q.get('divr'))||0,0,1):.2; 1036// the mark's faces, in mark units: T top, U the lower top step, L / R left- / right-facing, l / r the recessed wing panels 1037const DIV_FACES=[["T",[31,102,33,116,44,126,434,365,450,373,464,376,484,376,496,374,883,138,906,123,916,110,915,99,905,88,851,53,817,37,792,37,754,57,508,210,484,220,465,220,438,209,185,51,165,40,154,37,129,37,106,47,43,87]],["T",[374,162,375,175,385,186,430,214,462,227,493,225,513,216,562,185,573,171,572,160,562,149,514,118,495,109,477,105,459,107,432,119,383,150]],["U",[23,134,23,233,29,244,39,254,90,289,244,379,457,513,471,518,487,514,871,279,907,254,918,243,923,232,923,134,915,123,905,120,899,121,505,361,486,368,467,369,442,361,48,121,35,121,25,129]],["L",[136,314,41,367,33,378,34,390,53,407,333,579,340,587,348,605,349,942,353,951,369,964,379,966,395,964,464,924,475,913,477,908,476,518,468,509,450,501,385,462,281,393,155,317,142,313]],["L",[42,395,32,398,26,404,23,416,23,733,25,745,32,753,41,758,52,759,67,754,118,726,163,698,171,689,172,478,160,463,54,399]],["l",[173,484,165,495,164,668,167,692,174,699,331,784,339,785,349,782,357,771,357,602,351,588,328,565,202,488,183,481]],["R",[819,316,804,313,786,320,531,480,478,510,471,518,469,906,473,916,480,923,549,963,570,965,583,961,592,952,597,938,599,915,599,789,596,632,597,611,606,587,629,569,904,400,913,390,913,376,904,366]],["R",[905,395,899,396,872,411,833,437,784,466,775,478,774,675,776,689,783,697,854,739,890,758,896,759,906,758,913,754,924,740,923,409,915,398]],["r",[774,484,764,481,757,482,718,504,613,569,593,592,589,606,589,768,592,777,598,783,608,786,614,785,768,702,780,692,782,687,782,495]]].map(([c,a])=>{const p=new Path2D();for(let i=0;i<a.length;i+=2)p[i?'lineTo':'moveTo'](a[i],a[i+1]);p.closePath();return[c,p]}); 1038let DIV_PAL={bone:{top:'#E4DCCA',left:'#D2C9B5',right:'#BCB19B',edge:'rgba(18,29,26,.12)',ink:'#A99E86'}, 1039 cobalt:{top:'#324E48',left:'#2E4A44',right:'#253E39',edge:'rgba(18,32,29,.45)',ink:'#213833'}, 1040 // 1 Oct 2026: a few cubes in every divider are orange 1041 orange:{top:'#EE6A33',left:'#D65A27',right:'#B24A1E',edge:'rgba(18,29,26,.22)',ink:'#B24A1E'}}; 1042const DIV_HL=.78; // how far the mark stands above its column's top face (units of u): the mark is 1.78 u tall, a cube top 1 u 1043const DIV_SPR=new Map(); 1044// round 5 (30 Sep 2026): divider options for the founder to pick from (?dv=1|2|3; the default unchanged until then). 1045// 1 an even low skyline: a continuous band of small cubes, no holes, gentle height variation (1 to 3 cubes) 1046// 2 a single soft row of small cubes along the boundary, slight height jitter 1047// 3 J's original density and look, lighter (lower contrast) and lower 1048// founder's pick (30 Sep 2026, round 6): 1, the even low skyline, is the default;
1048 ?dv=0 shows the old default, ?dv=2|3 the others 1049const DV=Q.has('dv')&&[0,1,2,3].includes(+Q.get('dv'))?+Q.get('dv'):1; 1050if(DV===3){const mh=(a,b,k)=>{const A=hex(a),B=hex(b);return'#'+A.map((v,i)=>Math.round(lerp(v,B[i],k)).toString(16).padStart(2,'0')).join('')}; 1051 const L=(q,k)=>Object.assign({},q,{left:mh(q.top,q.left,k),right:mh(q.top,q.right,k)}); 1052 DIV_PAL.bone=Object.assign(L(DIV_PAL.bone,.55),{edge:'rgba(18,29,26,.06)'});DIV_PAL.cobalt=Object.assign(L(DIV_PAL.cobalt,.5),{edge:'rgba(18,32,29,.22)'});} 1053// a column: a 1 x 1 block whose sides run far enough down to leave the canvas (it is always drawn behind the next row) 1054function divCol(u,dpr,pk,H,one){const k='c|'+u+'|'+dpr+'|'+pk+'|'+H+'|'+(one?1:0);let s=DIV_SPR.get(k);if(s)return s; 1055 const q=DIV_PAL[pk],m=2,hz=one?1:Math.ceil(H/u)+3,w=Math.ceil(2*u*C30)+m*2,h=Math.ceil(u+hz*u)+m*2,c=document.createElement('canvas'); 1056 c.width=Math.ceil(w*dpr);c.height=Math.ceil(h*dpr);const x=c.getContext('2d');x.setTransform(dpr,0,0,dpr,0,0); 1057 box(x,{u,ox:m+u*C30,oy:m},0,0,-hz,1,1,hz,pal(q.top,q.left,q.right),q.edge); 1058 s={c,w:c.width/dpr,h:c.height/dpr,ax:m+u*C30,ay:m};DIV_SPR.set(k,s);return s;} 1059// a logo block: the mark as wide as a cube's top face, its faces in the cube tones, its outline as the edges 1060function divLogo(u,dpr,pk){const k='m|'+u+'|'+dpr+'|'+pk;let s=DIV_SPR.get(k);if(s)return s; 1061 const q=DIV_PAL[pk],m=2,sc=2*u*C30/930,w=930*sc+m*2,h=956*sc+m*2,c=document.createElement('canvas'); 1062 c.width=Math.ceil(w*dpr);c.height=Math.ceil(h*dpr);const x=c.getContext('2d');x.setTransform(dpr*sc,0,0,dpr*sc,dpr*(m-8*sc),dpr*(m-22*sc)); 1063 const T={T:q.top,U:mixC(q.top,q.left,.45),L:q.left,l:mixC(q.left,q.right,.5),R:q.right,r:mixC(q.right,'#121D1A',.18)}; 1064 for(const[cl,p]of DIV_FACES){x.fillStyle=T[cl];x.fill(p);} 1065 x.fillStyle=q.ink;x.fill(MARK_FULL,'evenodd'); 1066 s={c,w:c.width/dpr,h:c.height/dpr,ax:m+465*sc,ay:m};DIV_SPR.set(k,s);return s;} 1067// a smooth value noise along one axis (clusters for the skyline's gaps) 1068const vnoise=(x,sd)=>{const i=Math.floor(x),f=x-i,a=hash(i,sd),b=hash(i+1,sd),q=f*f*(3-2*f);return a+(b-a)*q}; 1069// an eased vertical fade: colour c from alpha a0 at y0 to a1 at y1, smoothstep through many stops (no visible band edges) 1070function divFade(ctx,W,y0,y1,c,a0,a1,soft){const g=ctx.createLinearGradient(0,y0,0,y1); 1071 // soft (1 Oct 2026): smootherstep, flat at both ends, so there is no visible start or end to the fade 1072 for(let k=0,N=soft?24:12;k<=N;k++){const q=k/N,e=soft?q*q*q*(q*(6*q-15)+10):q*q*(3-2*q),a=a0+(a1-a0)*e;g.addColorStop(q,c+Math.round(clamp(a,0,1)*255).toString(16).padStart(2,'0'));} 1073 ctx.fillStyle=g;ctx.fillRect(0,Math.min(y0,y1),W,Math.abs(y1-y0));} 1074function drawDivider(D,t){ 1075 const {ctx,W,H,dpr}=D; const from=D.from, to=D.to, pk=D.pal==='bone'?'bone':'cobalt', OLD=DV===3||(!DV&&DIV_MODE==='old'); 1076 if(!D.P){ const u=DV===1?worldU(innerWidth<760):DV===2?(W<700?11:15):DV===3?(W<700?13:19):OLD?(W<700?16:24):(W<700?12:17); D.P={u,ox:W/2,oy:30}; D.cols=[]; /* oy 30 (B: 18) so the tallest bobbing block never clips on the canvas top edge */ 1077 const n=Math.ceil(W/(u*C30))+8;let lift=0,raised=0; 1078 // the calm skyline: its back rows start lower in the band (the top fade has room), and are thinned out in clusters 1079 if(!OLD||DV)D.P.oy=Math.round(H*(DV===1?.36:DV===2?.4:DV===3?.3:(W<700?.36:.33))); 1080 for(let i=-n;i<n;i++)for(let r=0;r<Math.ceil(H/(u*.5))+8;r+=1){ const j=r-i; const sx=D.P.ox+(i-j)*u*C30; if(sx<-u*2||sx>W+u*2) continue; 1081 const hh=hash(i,j*1.7); 1082 let base; 1083 if(DV===1){ if(r<3)base=-2; else{const k=r-3;if(k>7)continue;const q=vnoise(sx/(u*4.5),3.1+(D.ix||0)*1.3)*.55+hh*.45; 1084 base=(k<2?(q<.5?0:q<.82?1:2):k<4?(q<.6?0:.5):0)+k*-.05; 1085 if(D.P.oy+u*(1.05*r-2.2-base)>H*.95)continue;} } 1086 else if(DV===2){ if(r<3)base=-2; else if(r===3)base=hh*.35; else continue; } 1087 else if(OLD) base = r<3?-2: ((hh<.4?0:hh<.75?.5:hh<.93?1:1.5)*(DV===3?.6:1)) + (r-3)*-.05; 1088 else{ if(r<3){base=-2;} 1089 else{ // presence: clusters of gaps (a slow noise across the band) plus a light random thinning, strongest in the back rows
1090 const k=r-3,keep=vnoise(sx/(u*5.5),7.3+(D.ix||0)*1.9)*.8+hash(i*1.3,j*.7+4.1)*.45-.4+k*.1; 1091 if(keep<.5)continue; 1092 base=(hh<.6?0:hh<.87?.28:hh<.97?.5:.72)+k*-.06; } } 1093 const logo=r>=3&&hash(i*.37+(D.ix||0)*5.1,j*1.13+2.7)<DIV_RATIO; 1094 if(logo){const y=D.P.oy+r*u*.5-(2.2-r*.55+base+.45+DIV_HL)*u; // the mark's top at the highest bob 1095 if(y<2){if(DIV_RATIO<1){D.cols.push({i,j,r,base,logo:false});continue;} lift=Math.max(lift,2-y);}} 1096 if(r>=3&&r<9)raised++; 1097 D.cols.push({i,j,r,base,logo,or:false} /* founder 1 Oct 2026: no orange cubes in the dividers */); } 1098 D.P.oy+=Math.ceil(lift); // only with ?div=logos: the whole band moves down so the tallest mark never clips 1099 D.cols.sort((a,b)=>(a.i+a.j)-(b.i+b.j)); D.nBlocks=raised; } 1100 const P=D.P,u=P.u; ctx.setTransform(D.dpr,0,0,D.dpr,0,0); ctx.fillStyle=from; ctx.fillRect(0,0,W,H); 1101 // round 3: the ground behind the cubes already turns from the colour above to the colour below (eased), so the gaps in the 1102 // skyline never show a line where the solid band starts 1103 if(!OLD||DV)divFade(ctx,W,DV===1?H*.42:H*.18,H*.9,to,0,1,DV===1); 1104 if(DV){ctx.fillStyle=to;ctx.fillRect(0,H*.9-.5,W,H*.1+1);} 1105 const col=divCol(u,dpr,pk,H,DV===2),colO=divCol(u,dpr,'orange',H,DV===2),lg=DIV_RATIO>0?divLogo(u,dpr,pk):null; 1106 for(const c of D.cols){ const wob = RM?0:Math.max(0,Math.sin(t*.7+c.i*.9+c.j*.4)-.85)*(DV?3:OLD?6:4); 1107 const top=(c.r<3?-40:(2.2 - c.r*.55 + c.base + wob*.5)); 1108 if(top<-30) continue; 1109 const X=P.ox+(c.i-c.j)*u*C30,Y=P.oy+(c.i+c.j)*u*.5-top*u; 1110 const cc=c.or?colO:col;ctx.drawImage(cc.c,X-cc.ax,Y-cc.ay,cc.w,cc.h); 1111 if(c.logo)ctx.drawImage(lg.c,X-lg.ax,Y-DIV_HL*u-lg.ay,lg.w,lg.h); } 1112 if(OLD&&!DV){const g=ctx.createLinearGradient(0,H*.55,0,H); g.addColorStop(0,to+'00'); g.addColorStop(1,to+'ff'); 1113 ctx.fillStyle=g; ctx.fillRect(0,H*.55,W,H*.45);} 1114 else{ 1115 // the long, eased fades: the colour above lies over the skyline's top and thins out slowly (the cubes rise out of it); 1116 // the colour below rises from the middle and covers everything by the bottom edge 1117 if(DV===2){divFade(ctx,W,0,H*.4,from,1,0);divFade(ctx,W,H*.5,H,to,0,1);} 1118 // DV 1 (1 Oct 2026, founder: "the fade is like a cut"): longer, smootherstep fades; the cubes come out of the colour above 1119 // gradually and sink into the colour below just as gradually 1120 // founder, 1 Oct 2026 (late): no wash of the colour above over the cubes: the skyline stands solid on it; below, the cubes 1121 // show for longer and only then sink smoothly into the colour below 1122 else if(DV===1){divFade(ctx,W,H*.6,H*.97,to,0,1,1);ctx.fillStyle=to;ctx.fillRect(0,H*.97-.5,W,H*.03+1);} 1123 else{divFade(ctx,W,0,H*.72,from,1,0);divFade(ctx,W,H*.46,H,to,0,1);}} 1124 // the last rows are solid "to", so the canvas edge never shows a seam 1125 ctx.fillStyle=to; ctx.fillRect(0,H-3,W,3); 1126} 1127function drawGlyph(G,t,still){ 1128 const {ctx,W,H,kind}=G;ctx.setTransform(G.dpr,0,0,G.dpr,0,0);ctx.clearRect(0,0,W,H); 1129 const S=RM||still; 1130 const P=fit(W,H,[0,3,0,3,0,2.8],8,0);const st='rgba(18,29,26,.6)';ctx.lineWidth=1; 1131 const it=[];const b=(x,y,z,w,d,h,p)=>it.push([x+w/2+y+d/2,z,()=>box(ctx,P,x,y,z,w,d,h,p,st)]); 1132 const k=S?.5:(Math.sin(t*1.4)*.5+.5); 1133 if(kind==='lab'){b(0,0,0,3,3,.3,P_FLOOR);b(1,1,.3+k*.5,1,1,1,P_OR); 1134 [[0,0],[2.7,0],[0,2.7],[2.7,2.7]].forEach(([x,y])=>b(x,y,.3,.3,.3,1.9,P_COB)); 1135 b(0,0,2.2,3,.3,.3,P_COB);b(0,2.7,2.2,3,.3,.3,P_COB);b(0,.3,2.2,.3,2.4,.3,P_COB);b(2.7,.3,2.2,.3,2.4,.3,P_COB);} 1136 else if(kind==='train'){b(0,2,0,3,1,.7,P_COB);b(0,1,0,3,1,1.4,P_COB);b(0,0,0,3,1,2.1,P_COB); 1137 const q=S?1:((t*.45)%1),st2=Math.min(2,Math.floor(q*3.2)),z=[.7,1.4,2.1][st2];b(1,2.1-st2+.1,z,.8,.8,.8,P_OR);} 1138 else if(kind==='evidence'){b(0,0,0,3,3,.3,P_FLOOR);const g1=S?1:ss(0,.5,(t*.3)%1.2);b(.3,2,.3,.8,.8,.2+.4*g1,P_FOG);b(1.1,1.1,.3,.8,.8,.3+.9*g1,P_FOG);b(2,.3,.3,.8,.8,.4+1.7*g1,P_OR);} 1139 else{b(0,0,0,3,3,.3,P_FLOOR);b(.2,.2,.3,1.2,1.2,1.4,P_COB);b(1.6,1.6,.3,1.2,1.2,1.4,P_INK); 1140 const q=S?.5:(Math.sin(t*.9)*.5+.5);b(.4+q*1.4,.4+q*1.4,1.7+Math.sin(q*Math.PI)*.8,.8,.8,.4,P_OR);} 1141 it.sort((a,b)=>a[0]-b[0]||a[1]-b[1]);it.forEach(o=>o[2]()); 1142 ctx.strokeStyle=INK;ctx.lineWidth=1.3;ctx.lineCap='round';ctx.beginPath(); 1143 for(let n=0;n<5;n++){const q=((S?n*.2:t*.12+n*.2)%1)*12;const side=Math.floor(q/3),f=q%3; 1144 const pts=[[f,-.35],[3.35,f],[3-f,3.35],[-.35,3-f]][side];const d=[[1,0],[0,1],[-1,0],[0,-1]][side]; 1145 const a=projPt(P,pts[0]-d[0]*.13,pts[1]-d[1]*.13,0),c=projPt(P,pts[0]+d[0]
1145*.13,pts[1]+d[1]*.13,0);ctx.moveTo(a[0],a[1]);ctx.lineTo(c[0],c[1]);} 1146 ctx.stroke(); 1147} 1148// Fig. 2 (ported from "Isometric World", same scene, same speed): work moves through the rooms and passes a human check. 1149// One change from B: every item passes. The gate lights as each item goes under it, the item turns sage there, 1150// then leaves along the dark lane (it sinks away at the end instead of hanging off the edge and vanishing). 1151const P_BONE=pal('#E9E2D2','#CCC3B0','#9FB0A8'); 1152function FloorScene(cv){ 1153 const S={cv,t:0}; 1154 S.resize=()=>{Object.assign(S,setupCanvas(cv));S.mob=S.W<700; 1155 if(S.mob)S.P=fit(S.W,S.H,[0,11,0,7,-.4,2.2],14,0); 1156 else{const P=fit(S.W*.62,S.H*.9,[0,11,0,7,-.4,2.2],24,0);P.ox+=S.W*.36;P.oy+=S.H*.06;S.P=P;}}; 1157 S.resize(); 1158 S.nAg=4; 1159 S.step=dt=>{S.t+=dt;}; 1160 const path=[[.2,1],[2,2.5],[3.5,2.5],[5,4.5],[7,4.5],[8.3,3.1],[9.6,3.1],[9.6,3.1],[11.4,3.1]]; 1161 const seglen=[];let total=0;for(let k=0;k<path.length-1;k++){const l=Math.hypot(path[k+1][0]-path[k][0],path[k+1][1]-path[k][1])||1.1;seglen.push(l);total+=l;} 1162 S.draw=()=>{const {ctx,W,H,P}=S,t=RM?4:S.t;ctx.setTransform(S.dpr,0,0,S.dpr,0,0);ctx.clearRect(0,0,W,H); 1163 const it=[]; 1164 for(let i=0;i<11;i++)for(let j=0;j<7;j++)it.push([i+j+1,-1,()=>box(ctx,P,i,j,-.4,1,1,.4,i===10?P_INK:P_FLOOR,'rgba(18,29,26,.14)')]); 1165 const wall=(x,y)=>it.push([x+.12+y+.5+.05,0,()=>box(ctx,P,x,y,0,.24,1,.95,P_COB,'rgba(18,29,26,.25)')]); 1166 for(let j=0;j<7;j++){if(j!==2)wall(3.4,j);if(j!==4)wall(6.8,j);} 1167 let gateGlow=0;const N=4,period=7.2; 1168 for(let n=0;n<N;n++){ 1169 const ph=((t/period)+n/N)%1;let s=ph*total,k=0;while(k<seglen.length-1&&s>seglen[k]){s-=seglen[k];k++;} 1170 const f=Math.min(1,s/seglen[k]);const A=path[k],B=path[k+1];const x=A[0]+(B[0]-A[0])*f,y=A[1]+(B[1]-A[1])*f; 1171 // every item passes: it lights the gate while it stands under it, and turns sage as it is approved 1172 if(k===6)gateGlow=Math.max(gateGlow,Math.sin(Math.PI*f)); 1173 const ok=k>6||(k===6&&f>=.5); 1174 // grow in at the start, sink away at the end of the out lane 1175 const sc=clamp(ph*total/.6,0,1)*clamp((11.3-x)/.9,0,1);if(sc<=.01)continue;const sz=.6*sc; 1176 it.push([x+y+.7,1,()=>box(ctx,P,x-sz/2,y-sz/2,0,sz,sz,sz,ok?P_OR:P_COB,'rgba(18,29,26,.35)')]); 1177 } 1178 const gp=gateGlow>.01?{top:mixC('#3A5650','#F47F4A',gateGlow),left:mixC('#121D1A','#E0602B',gateGlow),right:mixC('#121D1A','#B24A1E',gateGlow)}:P_INK; 1179 it.push([9.4+2.3+.2,0,()=>box(ctx,P,9.3,2.2,0,.26,.26,1.6,P_INK,null)]); 1180 it.push([9.4+4.0+.2,0,()=>box(ctx,P,9.3,3.8,0,.26,.26,1.6,P_INK,null)]); 1181 it.push([9.4+4.1+.3,2,()=>box(ctx,P,9.3,2.2,1.6,.26,1.86,.3,gp,'rgba(18,29,26,.3)')]); 1182 it.sort((a,b)=>a[0]-b[0]||a[1]-b[1]);it.forEach(o=>o[2]()); 1183 // labels: B's leaders, set on small solid tags so they read on this section's dark ground 1184 const fs=S.mob?10:12;ctx.strokeStyle=BONE;ctx.lineWidth=1; 1185 const g=projPt(P,9.43,2.2,2.3);ctx.beginPath();ctx.moveTo(g[0],g[1]);ctx.lineTo(g[0],g[1]-30);ctx.stroke(); 1186 chip(ctx,'Human check',g[0],g[1]-40,'center',fs,INK,BONE); 1187 const e=projPt(P,0,1.5,.1);ctx.beginPath();ctx.moveTo(e[0],e[1]);ctx.lineTo(e[0]-12,e[1]-30);ctx.lineTo(e[0]-26,e[1]-30);ctx.stroke(); 1188 chip(ctx,'Work in',e[0]-26,e[1]-30,'right',fs,INK,BONE); 1189 const o=projPt(P,10.5,3.1,.1);ctx.beginPath();ctx.moveTo(o[0],o[1]);ctx.lineTo(o[0]+16,o[1]+26);ctx.lineTo(o[0]+30,o[1]+26);ctx.stroke(); 1190 chip(ctx,'Out',o[0]+30,o[1]+26,'left',fs,CREAM,INK); 1191 }; 1192 return S; 1193} 1194 1195/* ============================================================ 1196 OUT OF THE LAB: three small isometric scenes, each drawn by an organized swarm. 1197 Every block is traced by agents at one even spacing along its visible edges and hatched on a lattice across its 1198 visible faces (same stroke length and direction per face). Moving work runs on lanes at even spacing. 1199 ============================================================ */ 1200const isoAng=(dx,dy,dz)=>Math.atan2((dx+dy)*.5-dz,(dx-dy)*C30); 1201function boxSwarm(w,d,h,es,sp,faces){ 1202 const out=[],E=(p,q)=>{const L=Math.hypot(q[0]-p[0],q[1]-p[1],q[2]-p[2]),n=Math.max(1,Math.round(L/es)),a=isoAng(q[0]-p[0],q[1]-p[1],q[2]-p[2]); 1203 for(let j=0;j<n;j++){const f=(j+.5)/n;
1203out.push({l:[p[0]+(q[0]-p[0])*f,p[1]+(q[1]-p[1])*f,p[2]+(q[2]-p[2])*f],a0:a,tone:'E'})}}; 1204 E([0,0,h],[w,0,h]);E([w,0,h],[w,d,h]);E([w,d,h],[0,d,h]);E([0,d,h],[0,0,h]); 1205 if(h>.12){E([w,0,0],[w,0,h]);E([w,d,0],[w,d,h]);E([0,d,0],[0,d,h]);E([w,0,0],[w,d,0]);E([w,d,0],[0,d,0]);} 1206 const F=(tone,A,B,pt,a0)=>{const n1=Math.round(A/sp),n2=Math.round(B/sp);if(n1<1||n2<1)return;const m1=Math.min(.2,es*.45/A),m2=Math.min(.2,es*.45/B); 1207 for(let j=0;j<n2;j++)for(let i=0;i<n1;i++)out.push({l:pt(m1+(1-2*m1)*(i+.5)/n1,m2+(1-2*m2)*(j+.5)/n2),a0,tone});}; 1208 if(faces.includes('t'))F('T',w,d,(u,v)=>[u*w,v*d,h],0); 1209 if(faces.includes('r'))F('R',d,h,(u,v)=>[w,u*d,v*h],.846); 1210 if(faces.includes('l'))F('L',w,h,(u,v)=>[u*w,d,v*h],-.846); 1211 return out; 1212} 1213const SC_TONES={ 1214 light:{E:[18,29,26,1],T:[18,29,26,.72],L:[18,29,26,.6],R:[18,29,26,.5],OE:[18,29,26,1],OT:[50,78,72,.95],OL:[50,78,72,.85],OR:[43,70,65,.85],FE:[18,29,26,.6],FT:[18,29,26,.38],FL:[18,29,26,.32],FR:[18,29,26,.28]}, 1215 dark:{E:[246,240,228,1],T:[240,234,220,.95],L:[218,210,193,.85],R:[178,200,190,.9],OE:[252,249,242,1],OT:[250,246,238,1],OL:[240,234,222,1],OR:[226,219,204,1],FE:[228,220,202,.45],FT:[228,220,202,.3],FL:[228,220,202,.25],FR:[228,220,202,.2]} 1216}; 1217const SC_FILL={light:pal('#E9E2D2','#DCD4C2','#CFC5B0'),dark:pal('#3A5953','#33504A','#2B4641'),lightO:pal('#C9D6CF','#B3C3BA','#9FB2A8'),darkO:pal('#3A5953','#33504A','#2B4641')}; 1218const SCENES={ 1219 products:{mode:'light',plate:false,bounds:[.4,9.1,.5,6.8,0,4.3],sbox:[-5.7,7.6,-1.6,7.1],es:.24,sp:.32, 1220 build(){return[{id:'p1',x:.5,y:3.7,z:0,w:3,d:3,h:.35,faces:'tlr'},{id:'tower',x:1.3,y:4.5,z:.35,w:1.4,d:1.4,h:3.8,faces:'tlr',sp:.9,order:1}, 1221 {id:'p2',x:5.4,y:.6,z:0,w:3.6,d:3,h:.35,faces:'tlr',order:2},{id:'screen',x:5.8,y:1.0,z:.35,w:2.8,d:.2,h:2.1,faces:'t',sp:.8,order:3}];}, 1222 flows(){return[{ring:[1.12,4.32,1.76],n:24,v:.55,tone:'O',zf:t=>.6+(RM?.55:(Math.sin(t*1.25)*.5+.5))*3.3}];}, 1223 bars:{x:6.02,y:1.0,n:8,dx:.31,w:.2,d:.2,z:.5,max:1.55}, 1224 labels:[{at:[3.5,6.7,0],dx:12,dy:22,t:'RealSight',al:'left'},{at:[9,3.6,0],dx:12,dy:22,t:'ADRODUB',al:'left'}]}, 1225 audit:{mode:'light',bounds:[0,10,0,6.5,-.2,3.8],es:.27,sp:.4, 1226 stacks:[{x:5.0,y:4.4,tone:'O',t:'AI fully'},{x:6.9,y:2.5,tone:'',t:'With approval'},{x:8.8,y:.6,tone:'F',t:'Not at all'}], 1227 build(){const B=[],order=[0,1,0,2,0,1,0,1,2,0,1,2],cnt=[0,0,0]; 1228 order.forEach((g,k)=>B.push({id:'c'+k,cube:1,g,w:.8,d:.8,h:.8,faces:'tlr',nx:.3+hash(k,1)*3.4,ny:.3+hash(k,2)*5,nz:.5+hash(k,3)*2.2,sz:cnt[g]++*.8,ph:hash(k,4)*6,order:k*.5}));return B;}, 1229 flows(){return[];}, 1230 labels:[{at:[1.9,5.9,0],dx:0,dy:26,t:'Every recurring task',al:'center'}]} 1231}; 1232function SwarmScene(cv){ 1233 const kind=cv.dataset.scene,def=SCENES[kind],S={cv,kind,def,t:0,started:RM,dark:def.mode==='dark'}; 1234 S.resize=()=>{Object.assign(S,setupCanvas(cv));S.mob=S.W<560;const pd=S.mob?8:14; 1235 // sbox: the drawing's real extent on screen (iso units), so it fills the canvas instead of its much taller bounding box 1236 if(def.sbox){const b=def.sbox,u=Math.min((S.W-2*pd)/(b[1]-b[0]),(S.H-2*pd)/(b[3]-b[2]));S.P={u,ox:S.W/2-(b[0]+b[1])/2*u,oy:S.H/2-(b[2]+b[3])/2*u};} 1237 else S.P=fit(S.W,S.H,def.bounds,pd,0);}; 1238 S.resize(); 1239 SEED=41+kind.length*7; 1240 S.boxes=def.build().map((b,i)=>Object.assign(b,{ag:boxSwarm(b.w,b.d,b.h,def.es,def.sp*(b.sp||1),b.faces)})); 1241 S.boxes.forEach((b,i)=>{const o=b.order!=null?b.order:i;b.ag.forEach(a=>{a.del=.1+o*.16+srnd()*.55;a.dur=.8+srnd()*.4;a.sx=-.35-srnd()*.5;a.sy=(srnd()-.5)*.5;})}); 1242 S.flows=def.flows();S.n=S.boxes.reduce((q,b)=>q+b.ag.length,0)+S.flows.reduce((q,f)=>q+f.n,0)+(def.bars?def.bars.n*8:0); 1243 const TN=SC_TONES[def.mode]; 1244 const col=(tone,al)=>{const c=TN[tone]||TN.E;return`rgba(${c[0]},${c[1]},${c[2]},${(c[3]*(al==null?1:al)).toFixed(3)})`}; 1245 S.step=dt=>{if(S.started&&!RM)S.t+=dt;}; 1246 // where a box is right now (the audit cubes travel between the pile and their stack) 1247 const place=(b,t)=>{if(!b.cube)return;const st=def.stacks[b.g],cyc=RM?7:(t+3)%10,k=S.boxes.indexOf(b); 1248 let f=clamp((cyc-(.6+k*.38))/.9,0,1);if(cyc>8.6)f=1-clamp((cyc-8.6)/.9,0,1);const e=easeIO(f); 1249 const j=RM?0:1-e;b.x=b.nx+(st.x-b.nx)*e+Math.sin(t*2.3+b.ph)*.12*j;b.y=b.ny+(st.y-b.ny)*e+Math.cos(t*1.9+b.ph)*.12*j;b.z=b.nz+(b.sz-b.nz)*e+Math.sin(Math.PI*e)*1.1; 1250 b.tone=e>.98?st.tone:'F';}; 1251 const lw=()=>S.mob?1.25:(S.dark?1.7:1.5); 1252 S.draw=()=>{const {ctx,W,H,P}=S,t=S.t;ctx.setTransform(S.dpr,0,0,S.dpr,0,0);ctx.clearRect(0,0,W,H);ctx.lineCap='round'; 1253 const u=P.u,Lf=def.sp*u*.3,Le=def.es*u*.36; 1254 // the plate + a faint floor grid 1255 const bd=def.bounds;if(def.plate!==false){box(ctx,P,bd[0],bd[2],-.2,bd[1]-bd[0],bd[3]-bd[2],.2,S.dark?pal('#38564F','#314E48','#2A4540'):pal('#DED6C5','#D0C7B3','#BEB39E'),S.dark?'rgba(20,36,32,.5)':'rgba(18,29,26,.14)'); 1256 ctx.strokeStyle=S.dark?'rgba(20,36,32,.35)':'rgba(18,29,26,.07)';ctx.lineWidth=1;ctx.beginPath(); 1257 for(let i=bd[0]+1;i<bd[1];i++){const a=projPt(P,i,bd[2],0),c=projPt(P,i,bd[3],0);ctx.moveTo(a[0],a[1]);ctx.lineTo(c[0],c[1]);} 1258 for(let j=bd[2]+1;j<bd[3];j++){const a=projPt(P,bd[0],j,0),c=projPt(P,bd[1],j,0);ctx.moveTo(a[0],a[1]);ctx.lineTo(c[0],c[1]);}ctx.stroke();} 1259 if(def.stacks){ctx.save();ctx.setLineDash([4,4]);ctx.strokeStyle='rgba(18,29,26,.45)';def.stacks.forEach(st=>{ctx.beginPath();
1259[[0,0],[.8,0],[.8,.8],[0,.8],[0,0]].forEach(([dx,dy],i)=>{const p=projPt(P,st.x+dx,st.y+dy,0);i?ctx.lineTo(p[0],p[1]):ctx.moveTo(p[0],p[1])});ctx.stroke();});ctx.restore();} 1260 const items=[]; 1261 // blocks: a quiet fill (so nearer blocks hide farther agents), then their agents 1262 for(const b of S.boxes){place(b,t); 1263 items.push([b.x+b.w/2+b.y+b.d/2,b.z,()=>{ 1264 const tn=b.tone||'',fp=S.dark?SC_FILL.dark:(tn==='O'?SC_FILL.lightO:SC_FILL.light); 1265 box(ctx,P,b.x,b.y,b.z,b.w,b.d,b.h,fp,null); 1266 const groups={};for(const a of b.ag){const p=RM?1:clamp((t-a.del)/a.dur,0,1);if(p<=0)continue; 1267 const h=projPt(P,b.x+a.l[0],b.y+a.l[1],b.z+a.l[2]);let x=h[0],y=h[1],ang=a.a0; 1268 if(p<1){const q=1-Math.pow(1-p,3),sx=h[0]+a.sx*W,sy=h[1]+a.sy*H;x=sx+(h[0]-sx)*q;y=sy+(h[1]-sy)*q+Math.sin(p*Math.PI)*-18;ang=a.a0+lineWrap(Math.atan2(h[1]-sy,h[0]-sx)-a.a0)*(1-ss(.55,1,p));} 1269 const g=tn+a.tone;(groups[g]||(groups[g]=[])).push([x,y,ang,a.tone==='E'?Le:Lf]);} 1270 for(const g in groups){const L=groups[g],path=()=>{ctx.beginPath();for(const [x,y,a,l] of L){const c=Math.cos(a)*l,s=Math.sin(a)*l;ctx.moveTo(x-c,y-s);ctx.lineTo(x+c,y+s)}}; 1271 if(S.dark){ctx.lineWidth=lw()+1.6;ctx.strokeStyle='rgba(16,30,27,.45)';path();ctx.stroke();} 1272 ctx.lineWidth=lw();ctx.strokeStyle=col(g in TN?g:g.slice(-1));path();ctx.stroke();} 1273 }]);} 1274 // lanes: work moving at even spacing; past a gate it turns to the highlight (approved) 1275 for(const f of S.flows){let pts=f.pts;const z=f.zf?f.zf(t):null; 1276 if(f.ring){const [x0,y0,s]=f.ring;pts=[[x0,y0,z],[x0+s,y0,z],[x0+s,y0+s,z],[x0,y0+s,z],[x0,y0,z]];} 1277 let L=0;const segs=[];for(let i=1;i<pts.length;i++){const a=pts[i-1],b=pts[i],l=Math.hypot(b[0]-a[0],b[1]-a[1],b[2]-a[2]);segs.push([a,b,l]);L+=l;} 1278 for(let i=0;i<f.n;i++){let d=(((RM?0:t)*f.v+i*L/f.n)%L+L)%L;let k=0;while(k<segs.length-1&&d>segs[k][2]){d-=segs[k][2];k++;} 1279 const [a,b,l]=segs[k],q=d/l,x=a[0]+(b[0]-a[0])*q,y=a[1]+(b[1]-a[1])*q,zz=a[2]+(b[2]-a[2])*q; 1280 const ok=f.tone==='O'||(f.gate!=null&&y>f.gate);const ang=isoAng(b[0]-a[0],b[1]-a[1],b[2]-a[2]); 1281 const vis=RM?1:clamp((t-.2-i*.03)/.5,0,1);if(vis<=0)continue; 1282 items.push([x+y+(f.ring?.001:.3),zz+.01,()=>{const p=projPt(P,x,y,zz),l2=Le*.95;const c=Math.cos(ang)*l2,s=Math.sin(ang)*l2; 1283 ctx.beginPath();ctx.moveTo(p[0]-c,p[1]-s);ctx.lineTo(p[0]+c,p[1]+s); 1284 if(S.dark){ctx.lineWidth=lw()+2.4;ctx.strokeStyle='rgba(16,30,27,.45)';ctx.stroke();} 1285 ctx.lineWidth=lw()+1;ctx.strokeStyle=ok?ORANGE:col('E');ctx.globalAlpha=vis;ctx.stroke();ctx.globalAlpha=1;}]);}} 1286 // audio bars: stacks of level dashes that rise and fall 1287 if(def.bars){const B=def.bars;for(let k=0;k<B.n;k++){const x=B.x+k*B.dx,h=.2+(RM?.5:Math.abs(Math.sin(t*3.1+k*1.1)*Math.cos(t*1.7+k*.6)))*B.max,n=Math.max(1,Math.round(h/.15)); 1288 items.push([1e3,1,()=>{ctx.beginPath();for(let j=0;j<n;j++){const a=projPt(P,x,B.y+B.d,B.z+j*.15),c=projPt(P,x+B.w,B.y+B.d,B.z+j*.15);ctx.moveTo(a[0],a[1]);ctx.lineTo(c[0],c[1]);} 1289 ctx.lineWidth=lw()+2.4;ctx.strokeStyle='rgba(16,30,27,.45)';ctx.stroke();ctx.lineWidth=lw()+.8;ctx.strokeStyle=k===3?ORANGE:col('E');ctx.stroke();}]);}} 1290 items.sort((a,b)=>a[0]-b[0]||a[1]-b[1]);items.forEach(o=>o[2]()); 1291 // labels with short leaders 1292 const lc=S.dark?BONE:INK,fs=S.mob?10:12;ctx.lineWidth=1;ctx.strokeStyle=lc; 1293 const lab=(at,dx,dy,txt,al)=>{const p=projPt(P,at[0],at[1],at[2]);ctx.beginPath();ctx.moveTo(p[0],p[1]);ctx.lineTo(p[0]+dx,p[1]+dy);ctx.stroke(); 1294 const tx=p[0]+dx+(al==='left'?4:al==='center'?0:-4),ls=txt.split('\n'),y0=p[1]+dy+(dy>0?8:-6-(ls.length-1)*(fs+5)); 1295 ls.forEach((t2,i)=>haloLabel(ctx,t2,tx,y0+i*(fs+5),lc,S.dark?COBALT:BONE,al==='left'?'left':al==='center'?'center':'right',fs));ctx.lineWidth=1;}; 1296 const la=RM?1:ss(1.2,2,t);ctx.globalAlpha=la; 1297 (def.labels||[]).forEach(L=>{if(L.desk&&S.mob)return;lab(L.at,L.dx,L.dy,L.t,L.al)}); 1298 if(def.stacks)def.stacks.forEach(st=>{const p=projPt(P,st.x+.8,st.y+.8,0);haloLabel(ctx,st.t,p[0],p[1]+20,INK,BONE,'center',fs)}); 1299 ctx.globalAlpha=1; 1300 }; 1301 return S; 1302} 1303 1304/* ============================================================ 1305 OUTPUT A: two sibling drawings, one per product, in the site's swarm style. 1306 Laid out on screen axes: a = to the right, b = down, z = up (a box at (a,b) is still a true isometric box). 1307 ADROSUITE (ready-made): one compact unit. Messages from WhatsApp, Instagram, web chat and phone come in; its modules 1308 (chat replies, calls, bookings, follow-up, marketing, reviews) light up as they work, and everything lands on one 1309 dashboard on top, where a member of staff approves. 1310 ADROTEAM (built for you): a four-floor building, one department per floor, agents working on each floor; 1311 finished work drops out of its floor, stops at one person who checks it, and only then goes on to Done. 1312 ============================================================ */ 1313function OfficeScene(cv){ 1314 const kind=cv.dataset.office||'suite',S={cv,kind,t:0,started:RM}; 1315 const K=.5/C30,es=.2,sp=.3; 1316 const TN=SC_TONES.light,col=(tone,al)=>{const c=TN[tone]||TN.E;return`rgba(${c[0]},${c[1]},${c[2]},${(c[3]*(al==null?1:al)).toFixed(3)})`}; 1317 // a box centred at (a,b) on screen axes, w along world x, d along world y 1318 const wbox=(a,b,z,w,d,h,faces,tone,order)=>{const o={x:a*K+b-w/2,y:-a*K+b-d/2,z,w,d,h,faces,tone:tone||'',order};o.ag=boxSwarm(w,d,h,es,sp,faces);return o}; 1319 const blk=(a,b,z,s,h,faces,tone,order)=>wbox(a,b,z,s,s,h,faces,tone,order); 1320 const toAB=(x,y)=>[(x-y)*C30,(x+y)*.5]; 1321 const pt=(P,a,b,z)=>[P.ox+a*P.u,P.oy+b*P.u-z*P.u]; 1322 // --- ADROSUITE geometry: one compact, ready-made unit --- 1323 // channel tiles on the left; the unit itself is one cabinet with a module per row on its right face, 1324 // and the dashboard (where staff approve) stands on its top 1325 const CH=['WhatsApp','Instagram','Web chat','Phone'],chB=[-1.35,-.45,.45,1.35]; 1326 const MODS=['Chat replies','Calls','Bookings','Follow-up','Marketing','Reviews']; 1327 const UH=2.7,UA=3.0,UB=.6; 1328 const A=kind!=='suite'?null:(()=>{ 1329 const unit=blk(UA,UB,0,1.3,UH,'tlr','',1.4); 1330 const scr={x:unit.x+.12,y:unit.y+.05,z:unit.h,w:unit.w-.24,d:.1,h:1.0,order:2.2,tone:''};scr.ag=boxSwarm(scr.w,scr.d,scr.h,es,sp,'tlr'); 1331 const tiles=chB.map((b,k)=>blk(0,b,0,.52,.18,'tlr','',k*.4)); 1332 return{unit,scr,tiles,all:[...tiles,unit,scr]};})(); 1333 // --- ADROTEAM geometry (unchanged) --- 1334 const FH=1.0,BS=1.25,BA=0; 1335 const Bt=kind!=='team'?null:{slabs:[0,1,2,3,4].map(k=>blk(BA,0,k*FH,BS,.1,k===4?'t':'','',k*.5)),check:blk(BA+4.3,.35,0,.8,.1,'t','',3),done:blk(BA+6.5,.35,0,.95,.22,'tlr','',3.5)}; 1336 const DEP=['Sales','Marketing','Customer service','Operations'];// top floor first 1337 const all=A?A.all:[...Bt.slabs,Bt.check,Bt.done]; 1338 SEED=kind==='suite'?97:131;all.forEach(b=>b.ag.forEach(g=>{g.del=.1+(b.order||0)*.16+srnd()*.5;g.dur=.8+srnd()*.4;g.sx=-.35-srnd()*.5;g.sy=(srnd()-.5)*.5;})); 1339 // layout: fit the drawing and its measured labels into the canvas (measured on every draw, so it follows the web font) 1340 let fsL=0;const layout=()=>{const {W,H}=S;S.mob=W<560;S.fs=S.mob?10:12;const ctx=S.ctx;ctx.font=`500 ${S.fs}px ${FONT}`; 1341 const pad=S.mob?6:14; 1342 if(kind==='suite'){ 1343 const lw=Math.max(...CH.map(s=>ctx.measureText(s.toUpperCase()).width))+12,rw=Math.max(...MODS.map(s=>ctx.measureText(s.toUpperCase()).width))+14; 1344 // drawing spans a in [-.3, 4.15], screen-b in [-3.85, 2.2]; plus the channel labels at the left, the module labels at the right, 1345 // and the dashboard label above (in px) 1346 fsL=S.fs+14;
1346const u=Math.max(8,Math.min((W-2*pad-lw-rw)/4.45,(H-2*pad-fsL-S.fs-12)/6.05,S.mob?44:62)); 1347 const w=lw+4.45*u+rw,left=(W-w)/2;S.P={u,ox:left+lw+.3*u,oy:(H-6.05*u-fsL)/2+fsL+3.85*u}; 1348 }else{ 1349 S.dep=S.mob?DEP.map(d=>d.replace(' ','\n')):DEP; 1350 const lw=Math.max(...S.dep.flatMap(s=>s.split('\n')).map(s=>ctx.measureText(s.toUpperCase()).width))+14; 1351 // drawing spans a in [-1.2, 7.35], screen-b in [-4.85, 1.25] 1352 const u=Math.max(8,Math.min((W-2*pad-lw)/8.55,(H-2*pad)/6.35,S.mob?46:74)); 1353 const w=lw+8.55*u,left=(W-w)/2;S.P={u,ox:left+lw+1.2*u,oy:(H-6.35*u)/2+5.0*u}; 1354 }}; 1355 S.resize=()=>{Object.assign(S,setupCanvas(cv));layout();}; 1356 S.resize(); 1357 S.step=dt=>{if(S.started&&!RM)S.t+=dt;}; 1358 // one block in the site's swarm style: a quiet fill, then its agents (dashes on the edges, hatching on the faces) 1359 const drawBlk=(P,b,t,tint)=>{const {ctx,W,H}=S,u=P.u,Lf=sp*u*.3,Le=es*u*.36; 1360 box(ctx,P,b.x,b.y,b.z,b.w,b.d,b.h,b.tone==='O'||tint?SC_FILL.lightO:SC_FILL.light,null); 1361 const groups={};for(const a of b.ag){const p=RM?1:clamp((t-a.del)/a.dur,0,1);if(p<=0)continue; 1362 const h=projPt(P,b.x+a.l[0],b.y+a.l[1],b.z+a.l[2]);let x=h[0],y=h[1],ang=a.a0; 1363 if(p<1){const q=1-Math.pow(1-p,3),sx=h[0]+a.sx*W,sy=h[1]+a.sy*H;x=sx+(h[0]-sx)*q;y=sy+(h[1]-sy)*q+Math.sin(p*Math.PI)*-18;ang=a.a0+lineWrap(Math.atan2(h[1]-sy,h[0]-sx)-a.a0)*(1-ss(.55,1,p));} 1364 const g=(b.tone||'')+a.tone;(groups[g]||(groups[g]=[])).push([x,y,ang,a.tone==='E'?Le:Lf]);} 1365 ctx.lineCap='round'; 1366 for(const g in groups){const L=groups[g];ctx.beginPath();for(const [x,y,a,l] of L){const c=Math.cos(a)*l,s=Math.sin(a)*l;ctx.moveTo(x-c,y-s);ctx.lineTo(x+c,y+s)} 1367 ctx.lineWidth=S.mob?1.2:1.5;ctx.strokeStyle=col(g in TN?g:g.slice(-1));ctx.stroke();}}; 1368 // a message (envelope) and a piece of finished work (a sheet with lines), drawn flat on screen 1369 const env=(x,y,e,ok,al)=>{const {ctx}=S,w=e*1.45,h=e;ctx.globalAlpha=al;ctx.lineWidth=1.3;ctx.lineJoin='round'; 1370 ctx.fillStyle=ok?'#9DB3AA':BONE;ctx.strokeStyle=INK;ctx.beginPath();ctx.rect(x-w/2,y-h/2,w,h);ctx.fill();ctx.stroke(); 1371 ctx.beginPath();ctx.moveTo(x-w/2,y-h/2);ctx.lineTo(x,y+h*.08);ctx.lineTo(x+w/2,y-h/2);ctx.stroke();ctx.globalAlpha=1;}; 1372 const sheet=(x,y,e,ok,al)=>{const {ctx}=S,w=e*.95,h=e*1.25;ctx.globalAlpha=al;ctx.lineWidth=1.3; 1373 ctx.fillStyle=ok===true||ok>=1?'#9DB3AA':ok>0?mixC('#E4DCCA','#9DB3AA',ok):BONE;ctx.strokeStyle=INK;ctx.beginPath();ctx.rect(x-w/2,y-h/2,w,h);ctx.fill();ctx.stroke(); 1374 ctx.beginPath();for(let i=0;i<3;i++){const yy=y-h/2+h*(.3+i*.2);ctx.moveTo(x-w*.28,yy);ctx.lineTo(x+w*(i===2?.05:.28),yy);}ctx.stroke();ctx.globalAlpha=1;}; 1375 // the person who approves: a small ink figure; a check mark appears beside them as each item passes 1376 const person=(P,a,b,glow)=>{const {ctx}=S,u=P.u,[x,y]=pt(P,a,b,0),s=u*.95; 1377 ctx.fillStyle=INK;ctx.beginPath();ctx.moveTo(x-s*.2,y);ctx.lineTo(x-s*.2,y-s*.42);ctx.quadraticCurveTo(x-s*.2,y-s*.62,x,y-s*.62);ctx.quadraticCurveTo(x+s*.2,y-s*.62,x+s*.2,y-s*.42);ctx.lineTo(x+s*.2,y);ctx.closePath();ctx.fill(); 1378 ctx.beginPath();ctx.arc(x,y-s*.8,s*.13,0,TAU);ctx.fill(); 1379 if(glow>.02){const cx=x+s*.42,cy=y-s*.78,r=s*.17;ctx.globalAlpha*=glow;ctx.fillStyle=ORANGE;ctx.beginPath();ctx.arc(cx,cy,r,0,TAU);ctx.fill(); 1380 ctx.strokeStyle=INK;ctx.lineWidth=1.6;ctx.lineCap='round';ctx.lineJoin='round';ctx.beginPath();ctx.moveTo(cx-r*.45,cy+r*.02);ctx.lineTo(cx-r*.1,cy+r*.38);ctx.lineTo(cx+r*.5,cy-r*.35);ctx.stroke();ctx.globalAlpha=1;}}; 1381 // a faint dashed track with small arrowheads, so direction reads even in the still frame 1382 const track=(P,pts,arrows)=>{const {ctx}=S;ctx.save();ctx.setLineDash([3,5]);ctx.strokeStyle='rgba(18,29,26,.32)';ctx.lineWidth=1.2;ctx.beginPath(); 1383 pts.forEach((p,i)=>{const q=pt(P,p[0],p[1],p[2]||0);i?ctx.lineTo(q[0],q[1]):ctx.moveTo(q[0],q[1])});ctx.stroke();ctx.restore(); 1384 (arrows||[]).forEach(([i,f])=>{const p0=pts[i],p1=pts[i+1],a=pt(P,p0[0],p0[1],p0[2]||0),c=pt(P,p1[0],p1[1],p1[2]||0),x=a[0]+(c[0]-a[0])*f,y=a[1]+(c[1]-a[1])*f,an=Math.atan2(c[1]-a[1],c[0]-a[0]),r=S.mob?4:5.5; 1385 ctx.strokeStyle='rgba(18,29,26,.6)';ctx.lineWidth=1.5;ctx.lineCap='round';ctx.beginPath();ctx.moveTo(x-Math.cos(an-.6)*r,y-Math.sin(an-.6)*r);ctx.lineTo(x,y);ctx.lineTo(x-Math.cos(an+.6)*r,y-Math.sin(an+.6)*r);ctx.stroke();});}; 1386 // position along a polyline, by distance 1387 const along=(pts,d)=>{for(let i=1;i<pts.length;i++){const a=pts[i-1],b=pts[i],l=Math.hypot(b[0]-a[0],b[1]-a[1],(b[2]||0)-(a[2]||0));
1387if(d<=l||i===pts.length-1){const q=clamp(d/l,0,1);return[a[0]+(b[0]-a[0])*q,a[1]+(b[1]-a[1])*q,(a[2]||0)+((b[2]||0)-(a[2]||0))*q]}d-=l;}}; 1388 const plen=pts=>{let L=0;for(let i=1;i<pts.length;i++)L+=Math.hypot(pts[i][0]-pts[i-1][0],pts[i][1]-pts[i-1][1],(pts[i][2]||0)-(pts[i-1][2]||0));return L}; 1389 const lab=(P,a,b,z,txt,al,dy)=>{const p=pt(P,a,b,z);haloLabel(S.ctx,txt,p[0],p[1]+(dy||0),INK,BONE,al||'center',S.fs);}; 1390 // a quad on a box's front (+y) face, from face coordinates (fx along x, fz up), in world units 1391 const faceQuad=(P,b,x0,x1,z0,z1)=>{const y=b.y+b.d;return[[b.x+x0,y,b.z+z0],[b.x+x1,y,b.z+z0],[b.x+x1,y,b.z+z1],[b.x+x0,y,b.z+z1]].map(p=>projPt(P,p[0],p[1],p[2]));}; 1392 const fillQ=(q,c)=>{const {ctx}=S;ctx.fillStyle=c;ctx.beginPath();q.forEach((p,n)=>n?ctx.lineTo(p[0],p[1]):ctx.moveTo(p[0],p[1]));ctx.closePath();ctx.fill();}; 1393 1394 /* ---------- ADROSUITE ---------- */ 1395 const DT=1.45,ORD=[0,3,1,2,0,1,3,2],T1=1.7; 1396 // label above an object: centred on its top face, clear of its highest corner 1397 const topOf=(P,b)=>{const c=[[0,0],[b.w,0],[b.w,b.d],[0,b.d]].map(([dx,dy])=>projPt(P,b.x+dx,b.y+dy,b.z+b.h));return[(Math.min(...c.map(p=>p[0]))+Math.max(...c.map(p=>p[0])))/2,Math.min(...c.map(p=>p[1]))];}; 1398 const quadX=(P,b,y0,y1,z0,z1)=>{const x=b.x+b.w;return[[x,b.y+y0,b.z+z0],[x,b.y+y1,b.z+z0],[x,b.y+y1,b.z+z1],[x,b.y+y0,b.z+z1]].map(p=>projPt(P,p[0],p[1],p[2]));}; 1399 // the modules: one row each on the unit's right face, top to bottom 1400 const rowZ=k=>{const m=.16,g=.07,rh=(UH-2*m-g*5)/6,z1=UH-m-k*(rh+g);return[z1-rh,z1]}; 1401 const drawSuite=(t,la)=>{const {ctx}=S,P=S.P,u=P.u,e=clamp(u*.26,7,14),{unit,scr,tiles}=A; 1402 const inF=toAB(unit.x+unit.w*.5,unit.y+unit.d),inZ=UH*.45; // where messages enter: the unit's left face 1403 const per=[4.4,1.75]; // the member of staff, in front of the unit 1404 chB.forEach(b=>track(P,[[.45,b,.1],[1.2,b,.1],[inF[0]-.35,inF[1]+(b-inF[1])*.12,inZ]],[[0,.72]])); 1405 const ft=RM?30:t,n0=Math.floor(ft/DT);const hot=[0,0,0,0,0,0];let glow=0,dash=0;const inbound=[]; 1406 for(let i=n0;i>n0-Math.ceil((T1+2.6)/DT)-2;i--){const age=ft-i*DT;if(age<0)continue;const c=ORD[((i%8)+8)%8]; 1407 const pin=[[.45,chB[c],.28],[1.2,chB[c],.28],[inF[0]-.12,inF[1],inZ]]; 1408 if(age<T1){const q=easeIO(clamp(age/T1,0,1)),p=along(pin,q*plen(pin));inbound.push([pt(P,p[0],p[1],p[2]),clamp(age/.3,0,1)*clamp((T1-age)/.18,0,1)]);continue;} 1409 const a2=age-T1,pulse=(t0,d)=>a2>t0&&a2<t0+d?Math.sin(Math.PI*(a2-t0)/d):0; 1410 // first the module that answers (chat or call), then the one that follows up (a booking or a follow-up) 1411 const m1=c===3?1:0,m2=((i%2)+2)%2?3:2; 1412 hot[m1]=Math.max(hot[m1],pulse(0,.8));hot[m2]=Math.max(hot[m2],pulse(.55,.8)); 1413 // then it lands on the dashboard, and a member of staff approves it 1414 dash=Math.max(dash,pulse(1.15,1.1));glow=Math.max(glow,pulse(1.3,1.2));} 1415 // marketing posts and review replies run on their own rhythm 1416 if(!RM){const mk=(ft%5.8)/5.8,rv=((ft+2.9)%5.8)/5.8;hot[4]=Math.max(hot[4],mk<.16?Math.sin(Math.PI*mk/.16):0);hot[5]=Math.max(hot[5],rv<.16?Math.sin(Math.PI*rv/.16):0);} 1417 tiles.forEach(b=>drawBlk(P,b,t)); 1418 drawBlk(P,unit,t); 1419 ctx.globalAlpha=la; 1420 for(let k=0;k<6;k++){const [z0,z1]=rowZ(k),q=quadX(P,unit,.12,unit.d-.12,z0,z1);fillQ(q,hot[k]>.05?mixC('#CFC5B0','#324E48',hot[k]):'rgba(18,29,26,.13)');} 1421 // the unit's little "on" light 1422 fillQ(faceQuad(P,unit,.14,.3,UH-.28,UH-.14),ORANGE);ctx.globalAlpha=1; 1423 drawBlk(P,scr,t); 1424 // the dashboard: rows of work; the newest glows sage while it waits for approval 1425 {ctx.globalAlpha=la;const m=.12,rw=scr.w-2*m;for(let r=0;r<3;r++){const z1=scr.h-.16-r*.26,z0=z1-.13,len=r===0?1:r===1?.72:.86; 1426 fillQ(faceQuad(P,scr,m,m+rw*len,z0,z1),r===0&&dash>.05?mixC('#80827A','#324E48',dash):'rgba(18,29,26,.5)');}ctx.globalAlpha=1;} 1427 ctx.globalAlpha=la;person(P,per[0],per[1],glow);ctx.globalAlpha=1; 1428 inbound.forEach(([s,al])=>env(s[0],s[1],e,false,al*la)); 1429 ctx.globalAlpha=la;const fs=S.fs;
1430 CH.forEach((c,k)=>{const p=pt(P,-.45,chB[k],.09);haloLabel(ctx,c,p[0]-6,p[1],INK,BONE,'right',fs);}); 1431 const rc=projPt(P,unit.x+unit.w,unit.y,0); 1432 MODS.forEach((m,k)=>{const [z0,z1]=rowZ(k),p=projPt(P,unit.x+unit.w,unit.y+.12,(z0+z1)/2);haloLabel(ctx,m,rc[0]+9,p[1],hot[k]>.5?HI_L:INK,BONE,'left',fs);}); 1433 let q=topOf(P,scr);haloLabel(ctx,'One dashboard',q[0],q[1]-12,INK,BONE,'center',fs); 1434 q=pt(P,per[0],per[1],0);haloLabel(ctx,'Staff approve',q[0],q[1]+15,INK,BONE,'center',fs); 1435 ctx.globalAlpha=1; 1436 }; 1437 1438 /* ---------- ADROTEAM ---------- */ 1439 const drawTeam=(t,la)=>{const {ctx}=S,P=S.P,u=P.u,ft=RM?30:t,e=clamp(u*.3,7,15),B=Bt; 1440 const sR=BA+BS*C30,xR=sR+.45,chkB=BA+4.3,doneL=BA+6.5-.95*C30,bb=.35; 1441 for(let k=0;k<4;k++){const z=k*FH+.1+.3;track(P,[[sR,0,z],[xR,0,z],[xR,bb,0]],[]);} 1442 track(P,[[xR,bb],[chkB,bb],[doneL,bb]],[[0,.55],[1,.55]]); 1443 const posts=(k,which)=>{const z0=k*FH+.1,z1=(k+1)*FH,x0=B.slabs[0].x,y0=B.slabs[0].y;ctx.strokeStyle=INK;ctx.lineWidth=S.mob?1.2:1.5;ctx.beginPath(); 1444 which.forEach(([dx,dy])=>{const a=projPt(P,x0+dx*BS,y0+dy*BS,z0),c=projPt(P,x0+dx*BS,y0+dy*BS,z1);ctx.moveTo(a[0],a[1]);ctx.lineTo(c[0],c[1]);});ctx.stroke();}; 1445 const Le=es*u*.36; 1446 for(let k=0;k<5;k++){const sl=B.slabs[k];drawBlk(P,sl,t);if(k===4)break; 1447 posts(k,[[0,0]]); 1448 const z=k*FH+.1,x0=sl.x,y0=sl.y,ag=RM?1:ss(1+k*.3,1.8+k*.3,t);ctx.globalAlpha=ag;ctx.strokeStyle=INK;ctx.lineWidth=(S.mob?1.2:1.5)+1.3;ctx.lineCap='round';ctx.beginPath(); 1449 for(let n=0;n<4;n++){const ph=((RM?0:t)*.09*(k%2?1:-1)+n/4+k*.13)%1,q=((ph%1)+1)%1*4,sd=Math.floor(q),f=q-sd,m1=.28,m2=BS-.2; 1450 const c=[[m1,m1],[m2,m1],[m2,m2],[m1,m2]],p=c[sd],r=c[(sd+1)%4],lx=x0+p[0]+(r[0]-p[0])*f,ly=y0+p[1]+(r[1]-p[1])*f,an=isoAng(r[0]-p[0],r[1]-p[1],0); 1451 const h=projPt(P,lx,ly,z+.02),cc=Math.cos(an)*Le*1.5,s=Math.sin(an)*Le*1.5;ctx.moveTo(h[0]-cc,h[1]-s);ctx.lineTo(h[0]+cc,h[1]+s);} 1452 ctx.stroke();ctx.globalAlpha=1;posts(k,[[1,0],[1,1],[0,1]]);} 1453 // finished work: out of its floor, down to the lane, along to the person, a real PAUSE there while they check it 1454 // (the check appears), and only then on, sage, to Done. Items are staggered so the queue never jams the gate. 1455 const bout=[];let chk2=0;const D1=.45,D2=.8,V2=1.25,TP=1.3,Lg=chkB-xR,Lr=doneL-chkB,TBt=D1+D2+Lg/V2+TP+Lr/V2,CYC=10,OFF=[0,2.5,5.2,7.6],FLR=[3,1,0,2]; 1456 const c0=Math.floor(ft/CYC); 1457 for(let c=c0;c>=c0-2;c--)OFF.forEach((o,n)=>{const age=ft-(c*CYC+o);if(age<0||age>TBt)return;const k=FLR[n],z=k*FH+.1+.3;let a,b,zz,ok=0; 1458 const tG=D1+D2+Lg/V2; 1459 if(age<D1){const q=age/D1;a=sR+(xR-sR)*q;b=0;zz=z;} 1460 else if(age<D1+D2){const q=easeIO((age-D1)/D2);a=xR;b=bb*q;zz=z*(1-q);} 1461 else if(age<tG){a=xR+(age-D1-D2)*V2;b=bb;zz=0;} 1462 else if(age<tG+TP){const w=age-tG;a=chkB;b=bb;zz=0;chk2=Math.max(chk2,ss(.3,.65,w));ok=ss(TP-.3,TP,w);} 1463 else{const w=age-tG-TP;a=chkB+w*V2;b=bb;zz=0;ok=1;chk2=Math.max(chk2,1-ss(0,.45,w));} 1464 bout.push([pt(P,a,b,zz+.3),ok,clamp(age/.25,0,1)*clamp((TBt-age)/.2,0,1)]);}); 1465 drawBlk(P,B.check,t,chk2>.5); 1466 drawBlk(P,B.done,t); 1467 ctx.globalAlpha=la;person(P,chkB+.05,bb-.78,chk2);ctx.globalAlpha=1; 1468 bout.forEach(([s,ok,al])=>sheet(s[0],s[1],e*.95,ok,al*la)); 1469 ctx.globalAlpha=la; 1470 S.dep.forEach((d,i)=>{const k=3-i,p=pt(P,BA-BS*C30,0,k*FH+.1+FH*.4),ls=d.split('\n');ctx.strokeStyle=INK;ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(p[0]-7,p[1]);ctx.lineTo(p[0]-1,p[1]);ctx.stroke();ls.forEach((l,j)=>haloLabel(ctx,l,p[0]-11,p[1]+(j-(ls.length-1)/2)*(S.fs+4),INK,BONE,'right',S.fs));}); 1471 lab(P,chkB+.05,bb-.78,0,'A person approves','center',-u*.95-12); 1472 lab(P,BA+6.5,bb-.48,.22,'Done','center',-14); 1473 ctx.globalAlpha=1; 1474 }; 1475 S.draw=()=>{layout();const {ctx,W,H}=S,t=RM?30:S.t;ctx.setTransform(S.dpr,0,0,S.dpr,0,0);ctx.clearRect(0,0,W,H); 1476 const la=RM?1:ss(1.2,2,t); 1477 if(kind==='suite')drawSuite(t,la);else drawTeam(t,la); 1478 }; 1479 return S; 1480} 1481 1482/* ============================================================ 1483 BOOT: every page runs the same script; each part starts only if its elements are on the page. 1484 ============================================================ */ 1485const $=s=>document.querySelector(s),$$=s=>[...document.querySelectorAll(s)]; 1486const BOX_A=[-80,1030,-120,1060],BOX_E=[-215,1161,-365,1245],BOX_EM=[-160,1106,-250,1165]; 1487// 1 Oct 2026: BOX_E grew 100 units at the top (more room between the pieces); the whole mark keeps its old size 1488const KE=(1245+365)/(1245+265),KEM=(1165+250)/(1165+180); 1489const heroFS=W=>clamp(W*.081,52,150); 1490// the world's cube size comes from the WINDOW, not the canvas (1 Oct 2026, founder: the pages' heroes are shorter than the 1491// home screen, so their cubes came out smaller); the dividers use the same size (worldU) 1492const worldU=mob=>{const vw=innerWidth,vh=innerHeight;return mob?clamp(vw/17,17,24):clamp(Math.min(vw/40,vh/25),22,38)}; 1493const WORLD={edgeFrac:.46,gridStroke:'rgba(18,29,26,.1)',unit:(W,H,mob)=>worldU(mob)}; 1494const STAGES=[];let S0=null,S1=null,SPC=null; 1495// concept J: the chat screen and the journey meet with no seam. Both worlds are the same lattice; the journey's is shifted 1496// so its rows continue the chat screen's, and a band of flat ground on either side of the join hides the cut. 1497const jBandB=(sy,H)=>clamp((H*.93-sy)/(H*.1),0,1),jBandT=(sy,H)=>clamp((sy-H*.05)/(H*.1),0,1); 1498// phones: room kept free at the bottom of the journey for the chat bar (set by chat-ui.js once a conversation exists) 1499const jPadB=()=>window.__jPadB||0; 1500 1501// ---- concept I landing: the world fills the screen, the swarm mark sits small above the chat box (placed on .land-mark), 1502// and the world goes flat in the middle so the headline and the box read on a quiet plate ---- 1503const landCv=$('#land-stage'),landMark=$('.land-mark'); 1504// once the conversation starts (.land.talking) the swarm fades out and the crisp logo (.lm-img, CSS) fades in: a swarm 1505// that small reads blurry and jagged 1506const landEl=$('#land'),landFade={v:null,t:0}; 1507// 1 Oct 2026: the landing's mark is the reference swarm (p2b-ref.js), in the band above the headline; S0 keeps the world 1508if(landCv&&landMark&&REFM)RefHero(landEl,$('.land-sub')); 1509if(landCv&&landMark){S0=new Stage(landCv,Object.assign({},WORLD,{nMin:620,nMax:1000,nMob:460,noMark:REFM, 1510 markA:()=>{const tg=landEl&&landEl.classList.contains('talking')?0:1,now=performance.now(); 1511 if(landFade.v===null||RM||FREEZE)landFade.v=tg;else{landFade.v+=(tg-landFade.v)*Math.min(1,Math.min(.1,(now-landFade.t)/1000)*7);if(Math.abs(tg-landFade.v)<.01)landFade.v=tg;} 1512 landFade.t=now;return landFade.v}, 1513 fits:()=>{const r=landMark.getBoundingClientRect(),c=landCv.getBoundingClientRect(),h=Math.max(8,r.height),cx=r.left+r.
1513width/2-c.left; 1514 return{A:FB(BOX_A,[cx-h,r.top-c.top,cx+h,r.top-c.top+h])}}, 1515 // J, phones: the swarm assembles in place, quickly, from just around the mark (a full-screen flight crosses the headline, 1516 // the box and the notes for seconds, and the empty slot reads as a dark blot until it lands); its halo comes in with it 1517 introK:S=>S.mob?.55:1,entryD:S=>S.mob?Math.max(60,landMark.getBoundingClientRect().height*.7):0, 1518 haloK:S=>S.mob&&!RM?ss(.2,1.6,S.t):1, 1519 // J: the bottom rows stay flat too, so the grid runs on into the journey's stage below with no seam 1520 flat:(sx,sy,W,H,mob)=>{const d=Math.hypot((sx-W/2)/(mob?W*.62:Math.max(520,W*.4)),(sy-H*.52)/(H*.46)); 1521 // 1 Oct 2026: and the band above the headline, where the reference swarm writes its shapes, is calm too 1522 const band=REFM?Math.max(clamp((Math.abs(sx-W/2)-W*(mob?.5:.34))/(W*.1),0,1),clamp((sy-H*.42)/(H*.08),0,1)):1; 1523 return clamp((d-.72)/.42,0,1)*band*jBandB(sy,H)}}));STAGES.push(S0);} 1524 1525 1526 1527// ---- landing: the journey, H's pinned track. f: 0 whole (J: already formed, centred), 1..5 one piece each, 6 whole and big, 1528// 7 moved aside for the last screen ---- 1529const track=$('.track'),stageBox=track&&track.querySelector('.stage');
1530const steps=$$('.track .step'),cards=$$('.pcard'),fin=$('.fin'),finIn=$('.fin-in'),stageNav=stageBox&&stageBox.querySelector('.nav'); 1531if(track){ 1532 S1=new Stage($('#stage'),Object.assign({},WORLD,{nMob:640,cards,refFormed:1,lsMax:7.2,lwMax:3.1,chevS:1, 1533 fits:(W,H,mob,S)=>{const below=W<1024,ch=S.cardH||300;let E,A; 1534 const pb=mob?jPadB():0; 1535 if(below){const r=[10,mob?66:84,W-10,H-ch-14-20-pb];E=FB(mob?BOX_EM:BOX_E,r);A=rescale(FB(BOX_A,mob?[10,66,W-10,H-24-pb]:r),E.s*1.12*(mob?KEM:KE));} // J, phones: whole, it sits in the middle (the labels are not up yet) // top clear of the nav, as G 1536 else{const m=clamp(W*.24,320,480);E=FB(BOX_E,[m,52,W-m,H-24]);A=rescale(FB(BOX_A,[m,72,W-m,H-24]),E.s*1.16*KE);} 1537 const B=FB(BOX_A,[W*.1,mob?24:36,W*.9,H-(mob?24:28)]); 1538 let C;if(mob){const h=Math.min(H*.24,200);C=FB(BOX_A,[W*.2,18,W*.8,18+h]);} 1539 else{C=FB(BOX_A,[W*.53,64,W-Math.max(44,W*.045),H-56]);if(C.s>B.s*.84)C=rescale(C,B.s*.84);} 1540 return{A,E,B,C};}, 1541 count:S=>S.mob?Math.round(clamp(4200*S.FT.B.s,S.o.nMob,1600)):Math.round(clamp(3400*S.FT.B.s,1800,3200)), // round 3: phones get enough agents for the big whole mark to read as lines, not dashes 1542 // wide screens: the left half stays flat, so the labels and the last screen's text sit on a quiet plate; phones: the lower 1543 // part stays flat under the label list (as G). J: the top rows stay flat, where the chat screen's world ends 1544 flat:(sx,sy,W,H,mob)=>(mob?clamp((H*.62-sy)/(H*.12),0,1):clamp((sx-W*.46)/(W*.12),0,1))*jBandT(sy,H), 1545 // J: shift the lattice so its rows continue the chat screen's rows above it 1546 oyAdj:(S,u)=>{if(!landCv)return 0;const Hl=landCv.clientHeight||S.H,d=(Hl*.5-5*u-Hl)-(S.H*.5-5*u);return((d%u)+u)%u}, 1547 onResize:S=>document.documentElement.style.setProperty('--mkh',Math.round((BOX_A[3]-BOX_A[2])*S.FT.C.s)+'px'), 1548 phase:f=>{ 1549 if(RM){const r=Math.round(f),w=[0];for(let k=1;k<=NSTEP;k++)w.push(r===k?1:0); 1550 return{e:r>=1&&r<=NSTEP?1:0,w,fit:r<=0?['A','A',0]:r<=NSTEP?['E','E',0]:r===6?['B','B',0]:['C','C',0]}} 1551 const e=ss(.18,.86,f)*(1-ss(NSTEP+.1,NSTEP+.68,f)); 1552 const w=[0];for(let k=1;k<=NSTEP;k++)w.push(1-ss(.2,.56,Math.abs(f-k))); 1553 const fit=f<1?['A','E',easeIO(ss(.18,.86,f))]:f<NSTEP+.05?['E','E',0]:f<6.04?['E','B',easeIO(ss(NSTEP+.1,NSTEP+.92,f))]:['B','C',easeIO(ss(6.1,6.94,f))]; 1554 return{e,w,fit};}})); 1555 STAGES.push(S1); 1556 // J: the mark is already whole when the journey comes into view (the chat screen above has had the intro) 1557 S1.t=INTRO_END*1+2; 1558 // QA: the stage's transform and its agents in mark units (the swarm-fidelity check, ll/logo/swarmfid.py) 1559 window.__swarm=()=>({s:S1.s,ox:S1.ox,oy:S1.oy,e:S1.e,rise:S1.rise,ag:S1.agents.filter(a=>a.on).map(a=>[+((a.x-S1.ox)/S1.s).toFixed(1),+((a.y-S1.oy)/S1.s).toFixed(1),a.edge?1:0,a.tone])}); 1560 // J: where the mark is on screen right now (chat-ui.js keeps the corner chat off it when it can) 1561 window.__jMark=()=>{const r=S1.cv.getBoundingClientRect();if(r.bottom<=0||r.top>=innerHeight)return null;let x0=1e9,y0=1e9,x1=-1e9,y1=-1e9; 1562 for(let k=0;k<PARTS.length;k++)S1.partPolys(k).forEach(q=>q.forEach(p=>{x0=Math.min(x0,p[0]);y0=Math.min(y0,p[1]);x1=Math.max(x1,p[0]);y1=Math.max(y1,p[1]);})); 1563 return{left:r.left+x0,top:r.top+y0,right:r.left+x1,bottom:r.top+y1,width:x1-x0}}; 1564} 1565// scroll -> phase f, measured from where each step's centre crosses the middle of the screen (the last step: its first screen) 1566const stepC=()=>steps.map((s,i)=>{const r=s.getBoundingClientRect();return i===steps.length-1?r.top+Math.min(r.height,innerHeight)/2:r.top+r.height/2}); 1567function readScroll(){if(!S1)return; 1568 let f;const mid=innerHeight/2,c=stepC(); 1569 if(mid<=c[0])f=0;else if(mid>=c[c.length-1])f=c.length-1; 1570 else{for(let k=0;k<c.length-1;k++)if(mid>=c[k]&&mid<c[k+1]){f=k+(mid-c[k])/Math.max(1,c[k+1]-c[k]);break}} 1571 S1.fT=f; 1572 // J: the journey's own nav shows once the stage is pinned (the chat screen's nav has scrolled away by then) 1573 if(stageNav){const on=track.getBoundingClientRect().top<=1;if(on!==stageNav._on){stageNav._on=on;stageNav.classList.toggle('shown',on);}} 1574 // once the last screen is scrolled past its first screenful, the small mark moves with it 1575 S1.shiftY=Math.min(0,fin.getBoundingClientRect().top-stageBox.getBoundingClientRect().top); 1576 if(finIn){const o=RM?1:ss(6.2,6.8,f);finIn.style.opacity=o.toFixed(3);} 1577}
1577 1578// for screenshots: scroll to where the phase is f 1579window.__toF=f=>{const c=stepC().map(v=>v+scrollY),k=clamp(Math.floor(f),0,c.length-2),q=f-k;scrollTo({top:c[k]+(c[k+1]-c[k])*q-innerHeight/2,behavior:'instant'});readScroll();if(S1)S1.f=S1.fT;}; 1580if(S1){ 1581 // cursor over a piece of the broken-apart mark: a pointer, and its label lights up; a click opens its page 1582 const setHot=hp=>{if(hp!==S1.hot){S1.hot=hp;S1.cv.style.cursor=hp?'pointer':'';S1.placeCards();}}; 1583 addEventListener('pointermove',ev=>{const r=S1.cv.getBoundingClientRect();if(ev.clientY<r.top||ev.clientY>r.bottom)return; 1584 if(ev.target.closest&&ev.target.closest('.pcard'))return; 1585 setHot(S1.e>.6?S1.hitPart(ev.clientX-r.left,ev.clientY-r.top):null);},{passive:true}); 1586 S1.cv.addEventListener('click',ev=>{if(S1.e<.6)return;const r=S1.cv.getBoundingClientRect(),hp=S1.hitPart(ev.clientX-r.left,ev.clientY-r.top);if(hp){const el=cards.find(l=>l.dataset.part===hp);if(el)el.click();}}); 1587 cards.forEach(el=>{el.addEventListener('pointerenter',()=>setHot(el.dataset.part));el.addEventListener('pointerleave',()=>setHot(null)); 1588 // keyboard: tabbing onto a label scrolls the breakdown to its piece, so its card opens beside it 1589 el.addEventListener('focus',()=>{const q=+Object.keys(STEP_PART).find(n=>STEP_PART[n]===el.dataset.part);if(Math.abs(S1.f-q)>.15){const c=stepC(),y=scrollY+c[q]-innerHeight/2;scrollTo({top:y,behavior:RM?'auto':'smooth'});}});}); 1590} 1591 1592// ---- the pages: the page's own piece of the official mark ---- 1593const pieceCv=$('canvas.piece'); 1594if(pieceCv){const id=pieceCv.dataset.part==='all'?null:pieceCv.dataset.part; 1595 // the reading pages (articles, privacy, terms, 404: .doc-hero) centre their text in a container, so the piece sits in that 1596 // container's right part (and a little into the margin), below the nav, instead of at the far right of a wide screen 1597 const docIn=pieceCv.closest('.doc-hero')&&pieceCv.parentNode.querySelector('.phero-in'); 1598 let DX=null;const docX=W=>{const c=pieceCv.getBoundingClientRect(),r=docIn.getBoundingClientRect(),L=r.left-c.left,R=r.right-c.left;return [L+r.width*.66,Math.min(W-Math.max(28,W*.03),R+(W-R)*.45)]}; 1599 const box=id?partBox(id,46):BOX_A,region=(W,H,mob)=>{if(mob)return [16,74,W-16,74+Math.min(W*.58,220)]; 1600 if(docIn){const [x0,x1]=DX=docX(W);return [x0,104,x1,H-34]}return [W*.55,86,W-Math.max(44,W*.045),H-30]}; 1601 SPC=new Stage(pieceCv,Object.assign({},WORLD,{only:id, 1602 fits:(W,H,mob)=>{const F=FB(box,region(W,H,mob));return{A:id?F:rescale(F,Math.min(F.s,.5))}}, 1603 count:S=>Math.round(clamp((id?4600:3000)*S.FT.A.s*(S.mob?.62:1),S.mob?600:900,id?9000:2000)), 1604 flat:(sx,sy,W,H,mob)=>mob?clamp((74+Math.min(W*.58,220)+16-sy)/40,0,1):docIn?clamp((sx-(DX||docX(W))[0]+W*.1)/(W*.1),0,1):clamp((sx-W*.42)/(W*.14),0,1)})); 1605 STAGES.push(SPC);} 1606 1607// ---- small scenes ---- 1608const D=$$('canvas.divider').map((cv,ix)=>Object.assign({cv,ix,from:cv.dataset.from,to:cv.dataset.to,pal:cv.dataset.pal},setupCanvas(cv))); 1609const G=$$('canvas[data-glyph]').map(cv=>Object.assign({cv,kind:cv.dataset.glyph,hot:false,t0:0},setupCanvas(cv))); 1610G.forEach(g=>{const li=g.cv.closest('.belief');li.addEventListener('pointerenter',()=>{g.hot=true;g.t0=performance.now()/1000});li.addEventListener('pointerleave',()=>{g.hot=false;drawGlyph(g,0,true)});}); 1611const floorCv=document.getElementById('floor'),FS=floorCv?FloorScene(floorCv):null,floorEl=$('.floor'); 1612const onScreen=(el,m)=>{const r=el.getBoundingClientRect();return r.bottom>-(m||0)&&r.top<innerHeight+(m||0)&&r.width>0}; 1613const SC=$$('canvas[data-scene]').map(SwarmScene); 1614const OF=$$('canvas[data-office]').map(OfficeScene); 1615 1616// newsletter: POST /api/subscribe (share3/api/subscribe.js: Resend contacts, list "ADROLAB lab notes"); a real success or an honest error 1617$$('form.news').forEach(nf=>{const i=nf.querySelector('input[type=email]'),m=nf.querySelector('.news-ok'),b=nf.querySelector('button[type=submit]');let busy=false; 1618 const say=(h,cls)=>{m.innerHTML=h;m.className='news-ok'+(cls?' '+cls:'')}; 1619 nf.addEventListener('submit',async ev=>{ev.preventDefault();if(busy)return; 1620 if(!i.value||!i.checkValidity()){say('Please enter a valid email.','bad');i.focus();return} 1621 busy=true;b.disabled=true;say('Subscribingâ¦');const hp=nf.querySelector('input[name="company_website"]');let j=null; 1622 try{const r=await fetch('/api/subscribe',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:i.value.trim(),company_website:hp?hp.value:''})});try{j=await r.json()}catch(e){}}catch(e){j=null} 1623 busy=false;b.disabled=false; 1624 if(j&&j.ok){say(j.already?"You're already on the list. Thanks.":j.welcome?"You're on the list. Check your inbox.":"You're on the list.",'good');i.value='';} // 1 Oct 2026: "check your inbox" only when the welcome email really went out 1625 else if(j&&j.reason==='invalid'){say('Please enter a valid email.','bad');i.focus();} 1626 else say('Something went wrong. Please email <a href="mailto:[email protected]?subject=Lab%20notes">[email protected]</a>.','bad');});}); 1627 1628// the reading pages' rail: "On this page" marks the section being read (only while the rail is shown, from 1180px) 1629{const tl=$$('.rail-toc a');if(tl.length){const hs=tl.map(a=>document.getElementById(a.getAttribute('href').slice(1)));let cur=-1,raf=0; 1630 const upd=()=>{raf=0;if(!tl[0].offsetParent)return;let k=-1;hs.forEach((h,i)=>{if(h&&h.getBoundingClientRect().top<innerHeight*.35)k=i}); 1631 if(k!==cur){cur=k;tl.forEach((a,i)=>i===k?a.setAttribute('aria-current','true'):a.removeAttribute('aria-current'))}}; 1632 addEventListener('scroll',()=>{if(!raf)raf=requestAnimationFrame(upd)},{passive:true});addEventListener('resize',upd);upd();}} 1633
1634// the menu button (narrow screens) 1635$$('.burger').forEach(b=>{const m=document.getElementById(b.getAttribute('aria-controls')); 1636 const set=o=>{b.setAttribute('aria-expanded',String(o));b.setAttribute('aria-label',o?'Close menu':'Menu');m.hidden=!o;}; 1637 b.addEventListener('click',()=>{const o=b.getAttribute('aria-expanded')!=='true';set(o);if(o){const a=m.querySelector('a');a&&a.focus({preventScroll:true});}}); 1638 addEventListener('keydown',e=>{if(e.key==='Escape'&&b.getAttribute('aria-expanded')==='true'){set(false);b.focus();}}); 1639 // while the menu is open, Tab and Shift+Tab stay inside it (the close button plus the page links) 1640 addEventListener('keydown',e=>{if(e.key!=='Tab'||b.getAttribute('aria-expanded')!=='true')return;const f=[b,...m.querySelectorAll('a[href]')],i=f.indexOf(document.activeElement); 1641 if(i<0){e.preventDefault();f[e.shiftKey?f.length-1:0].focus();return} 1642 if(e.shiftKey&&i===0){e.preventDefault();f[f.length-1].focus()}else if(!e.shiftKey&&i===f.length-1){e.preventDefault();f[0].focus()}}); 1643 document.addEventListener('click',e=>{if(b.getAttribute('aria-expanded')==='true'&&!m.contains(e.target)&&!b.contains(e.target))set(false);}); 1644 if(Q.has('menu'))set(true);}); 1645 1646// cursor: a gentle nudge to the agents and a small lift in the world, on whichever stage it is over 1647addEventListener('pointermove',ev=>{for(const S of STAGES){const r=S.cv.getBoundingClientRect(); 1648 if(ev.clientX>=r.left&&ev.clientX<=r.right&&ev.clientY>=r.top&&ev.clientY<=r.bottom){S.mouse=[ev.clientX-r.left,ev.clientY-r.top];S.mt=performance.now();}}},{passive:true}); 1649addEventListener('scroll',()=>{readScroll();if(RM||FREEZE)drawAll(performance.now(),0)},{passive:true}); 1650readScroll();if(S1)S1.f=S1.fT; 1651/* THE QUIZ: "Which one fits you?" (from G's hy/quiz.js; H adds an icon per answer and tactile tiles, and drops the company-size row) 1652 Three questions, all in the page: nothing is sent anywhere. The result is shown on the comparison table itself. 1653 ?quiz=q1|q2|q3|suite|team|team51 sets a state for screenshots. */ 1654(()=>{ 1655const qzBody=document.getElementById('qz-body'),qzFill=document.getElementById('qz-fill'),fitEl=document.getElementById('fit'),cmpEl=document.getElementById('cmp'); 1656if(!qzBody||!cmpEl)return; 1657const ARR='<svg viewBox="0 0 14 14" aria-hidden="true"><path d="M2 7h9.5M7.5 3l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.8"/></svg>'; 1658const EXT='<svg viewBox="0 0 14 14" aria-hidden="true"><path d="M3.5 10.5l7-7M5 3.5h5.5V9" fill="none" stroke="currentColor" stroke-width="1.8"/></svg>'; 1659const CHK='<svg class="ck" viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5l3.6 3.4 7.4-8" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="square"/></svg>'; 1660// one small line icon per answer (24px grid, drawn in the site's stroke) 1661const IC={ 1662 p1:'<circle cx="12" cy="8" r="3.5"/><path d="M5 20.5v-1.5a7 7 0 0 1 14 0v1.5"/>', 1663 p2:'<circle cx="9" cy="8.5" r="3"/><path d="M3 20.5v-1a6 6 0 0 1 12 0v1"/><circle cx="16.5" cy="7.5" r="2.6"/><path d="M16 13.1a5 5 0 0 1 5.5 5v1.4"/>', 1664 p3:'<circle cx="12" cy="7.5" r="2.8"/><path d="M6.5 20.5v-1a5.5 5.5 0 0 1 11 0v1"/><circle cx="5" cy="10.5" r="2.2"/><path d="M1.5 19.5a4 4 0 0 1 4.3-4.4"/><circle cx="19" cy="10.5" r="2.2"/><path d="M22.5 19.5a4 4 0 0 0-4.3-4.4"/>', 1665 b1:'<path d="M6 21V4h10v17M16 9.5h3.5V21M3 21h18M9 8h1M12.5 8h1M9 12h1M12.5 12h1M9 16h1M12.5 16h1"/>', 1666 b2:'<path d="M3.5 21V7h7v14M10.5 21V3h8v18M1.5 21h21M6 11h2M6 15h2M13.5 7h2M13.5 11h2M13.5 15h2"/>', 1667 b3:'<path d="M2 21V11h5v10M7 21V4h6.5v17M13.5 21V8H18v13M18 21v-7h4v7M1 21h22M9.5 8h1.5M9.5 12h1.5M9.5 16h1.5"/>', 1668 chat:'<path d="M4 4.5h16v11.5h-9.5L6 20v-4H4z"/><path d="M8 9h8M8 12h5"/>', 1669 phone:'<path d="M5.5 3.5h3.5l1.8 4.6-2.3 1.4a11 11 0 0 0 6 6l1.4-2.3 4.6 1.8v3.5a1.6 1.6 0 0 1-1.7 1.6A16.5 16.5 0 0 1 3.9 5.2 1.6 1.6 0 0 1 5.5 3.5z"/>', 1670 cal:'<rect x="3.5" y="5" width="17" height="15.5"/><path d="M3.5 10h17M8 3v4M16 3v4M7.5 14h2M11 14h2M14.5 14h2M7.5 17.3h2M11 17.3h2"/>', 1671 loop:'<path d="M19.5 12a7.5 7.5 0 1 1-2.3-5.4"/><path d="M18 2.8v4.4h-4.4"/><path d="M9.5 12.2l2 2 3.5-4"/>', 1672 mega:'<path d="M3 9.5v5h3.5L15 19.5v-15L6.5 9.5z"/><path d="M18.5 8.5a4.5 4.5 0 0 1 0 7M6.5 14.5l1.6 6h3.2l-1.3-4.7"/>', 1673 star:'<path d="M12 3.2l2.7 5.6 6.1.8-4.5 4.2 1.2 6.1L12 17l-5.5 2.9 1.2-6.1-4.5-4.2 6.1-.8z"/>', 1674 doc:'<path d="M6 2.8h8.8l4.2 4.2v14.2H6z"/><path d="M14.5 3v4.3h4.3M9 11.5h7M9 14.5h7M9 17.5h4"/>', 1675 head:'<path d="M4 14v-2a8 8 0 0 1 16 0v2"/><rect x="3" y="13.5" width="4" height="6"/><rect x="17" y="13.5" width="4" height="6"/><path d="M19 19.5v.5a2.3 2.3 0 0 1-2.3 2.3H13.5"/>', 1676 gear:'<circle cx="12" cy="12" r="3"/><path d="M12 2.8v2.6M12 18.6v2.6M2.8 12h2.6M18.6 12h2.6M5.5 5.5l1.9 1.9M16.6 16.6l1.9 1.9M5.5 18.5l1.9-1.9M16.6 7.4l1.9-1.9"/><circle cx="12" cy="12" r="6.6"/>', 1677 chart:'<path d="M3 20.5h18M6.5 20.5v-6M11 20.5V9.5M15.5 20.5v-4M20 20.5V5"/>', 1678 video:'<rect x="2.5" y="6" width="13" height="12"/><path d="M15.5 10.4l6-3.4v10l-6-3.4z"/>
1678', 1679 cap:'<path d="M1.8 9L12 4l10.2 5L12 14z"/><path d="M6 11v5.2c3.3 2.6 8.7 2.6 12 0V11M22.2 9v6"/>', 1680 calck:'<rect x="3.5" y="5" width="17" height="15.5"/><path d="M3.5 10h17M8 3v4M16 3v4M8.5 15l2.4 2.4 4.6-4.6"/>', 1681 shop:'<path d="M3 9l1.6-5h14.8L21 9"/><path d="M3 9h18v1.8a3 3 0 0 1-6 0 3 3 0 0 1-6 0 3 3 0 0 1-6 0z"/><path d="M4.5 13.4V21h15v-7.6M10 21v-4.8h4V21"/>', 1682 cart:'<path d="M1.8 3.5h3l2.6 12h11.2L21 7.2H6"/><circle cx="9" cy="19.6" r="1.5"/><circle cx="17" cy="19.6" r="1.5"/>', 1683 card:'<rect x="2.5" y="5" width="19" height="14"/><circle cx="8.5" cy="10.8" r="2.2"/><path d="M5 16.6a3.5 3.5 0 0 1 7 0M14.5 10h4.5M14.5 13.5h4.5"/>', 1684 house:'<path d="M2.8 11.3L12 3.6l9.2 7.7M5.5 9.2v11.3h13V9.2"/><path d="M10 20.5v-5.8h4v5.8"/>', 1685 clip:'<rect x="5" y="4.5" width="14" height="17"/><path d="M9 4.5V2.8h6v1.7M8.5 11l2.2 2.2 4.5-4.5M8.5 17h7"/>', 1686 sign:'<path d="M6 2.8h8.8l4.2 4.2v14.2H6z"/><path d="M14.5 3v4.3h4.3M9 10h7M9 13h5M8.5 18c1.4-2 2.4-2 3 0s1.5 1 2.4-.5 1.6-.5 2.6.6"/>', 1687 fact:'<path d="M2.5 21V11l5 3v-3l5 3v-3l5 3V3.5h4V21z"/><path d="M1.5 21h21M6.5 17.5h2M11.5 17.5h2"/>', 1688 truck:'<path d="M1.8 5.8h12.4V16H1.8zM14.2 9.8h4.4l3.2 3.6V16h-7.6"/><circle cx="6" cy="18.2" r="2"/><circle cx="17.6" cy="18.2" r="2"/>', 1689 brief:'<rect x="2.5" y="7" width="19" height="13.5"/><path d="M8.5 7V3.8h7V7M2.5 12.6h19M10.8 11v3.2h2.4V11"/>', 1690 net:'<circle cx="5" cy="5" r="2.2"/><circle cx="5" cy="19" r="2.2"/><circle cx="19" cy="12" r="2.4"/><path d="M5 7.2v9.6M7 6l9.8 5M7 18l9.8-5"/>', 1691 dots:'<circle cx="12" cy="12" r="9"/><circle cx="7.8" cy="12" r=".6"/><circle cx="12" cy="12" r=".6"/><circle cx="16.2" cy="12" r=".6"/>' 1692}; 1693const ICON=k=>`<svg viewBox="0 0 24 24">${IC[k]||IC.dots}</svg>`; 1694const SUITE='https://clinics.adrolab.ai'; 1695// each option: label, and what it points to. sv/tv: points; row/side: where its tick goes on the table; why: the tick's words 1696const {QS,sentence,list}=window.ADRO_QUIZ;const score=()=>window.ADRO_QUIZ.score(ans); 1697const NQ=QS.length;let step=0,ans=[null,[],null]; 1698const esc=t=>t.replace(/&/g,'&').replace(/</g,'<'); 1699function render(focus){ 1700 qzFill.style.width=(step/NQ*100)+'%'; 1701 if(step>=NQ){qzBody.innerHTML=`<p class="qz-done">Your fit is marked on the table below. <button type="button" class="qz-back" data-act="reset">Start over</button></p>`;return;} 1702 const Q=QS[step]; 1703 let h=`<div class="qz-top"><span class="qz-count">Question ${step+1} of ${NQ}</span>${step>0?'<button type="button" class="qz-back" data-act="back">Back</button>':''}</div>`; 1704 h+=`<h5 class="qz-q" id="qz-q" tabindex="-1">${Q.q}</h5>`+(Q.hint?`<p class="qz-hint" id="qz-hint">${Q.hint}</p>`:''); 1705 h+=`<div class="qz-opts${Q.multi?' multi':''}${Q.wide?' wide':''}" role="group" aria-labelledby="qz-q"${Q.hint?' aria-describedby="qz-hint"':''}>`; 1706 Q.opts.forEach((o,i)=>{const on=Q.multi?ans[1].includes(i):ans[step]===i; 1707 h+=`<button type="button" class="qz-opt${on?' sel':''}" data-i="${i}"${Q.multi?` aria-pressed="${on}"`:''}><span class="qz-ic" aria-hidden="true">${ICON(o.ic)}</span><span class="qz-l">${esc(o.l)}${o.d?`<small>${esc(o.d)}</small>`:''}</span><span class="qz-end" aria-hidden="true">${Q.multi?'<i class="bx"></i>':ARR.replace('<svg','<svg class="ar"')+CHK}</span></button>`;}); 1708 h+='</div>'; 1709 if(Q.multi)h+=`<button type="button" class="btn btn-o qz-go" data-act="next"${ans[1].length?'':' disabled'}>Continue ${ARR}</button>`; 1710 qzBody.innerHTML=h; 1711 if(focus){const q=document.getElementById('qz-q');q&&q.focus({preventScroll:true});} 1712} 1713qzBody.addEventListener('click',ev=>{const b=ev.target.closest('button');if(!b)return; 1714 const act=b.dataset.act; 1715 if(act==='back'){step=Math.max(0,step-1);render(true);return} 1716 if(act==='reset'){reset(true);return} 1717 if(act==='next'){if(ans[1].length){step++;render(true)}return} 1718 const i=+b.dataset.i,Q=QS[step]; 1719 if(Q.multi){const k=ans[1].indexOf(i);if(k<0)ans[1].push(i);else ans[1].splice(k,1); 1720 b.classList.toggle('sel',k<0);b.setAttribute('aria-pressed',String(k<0));qzBody.querySelector('.qz-go').disabled=!ans[1].length;return} 1721 ans[step]=i;b.classList.add('sel'); 1722 setTimeout(()=>{step++;if(step>=NQ){render(false);show(true)}else render(true)},RM?0:170); 1723}); 1724// ---- scoring ---- 1725// ---- the result, on the table ---- 1726const rowCell=(k,side)=>{const r=cmpEl.querySelector(`.cmp-r[data-k="${k}"]`);return r&&r.querySelector(side==='S'?'.su':'.tm')}; 1727function clear(){cmpEl.classList.remove('rec-S','rec-T');
1727cmpEl.querySelectorAll('.why,.badge,.alt').forEach(e=>e.remove());fitEl.innerHTML=''; 1728 const bs=document.getElementById('btn-suite'),bt=document.getElementById('btn-team'); 1729 bs.className='btn';bs.innerHTML='Talk to us about ADROSUITE '+ARR;bt.className='btn btn-o';bt.href='mailto:[email protected]?subject=ADROTEAM';bt.innerHTML='Talk to us about ADROTEAM '+ARR; 1730 OF.forEach(o=>o.paused=false);} 1731function show(scroll){const r=score();clear();const side=r.rec;cmpEl.classList.add('rec-'+side); 1732 // ticks on the matching rows of the recommended column 1733 const ticks={};const add=(k,w)=>{(ticks[k]||(ticks[k]=[])).push(w)}; 1734 r.picks.forEach(o=>{if(o.k===side)add(o.row,o.why);else if(side==='T'&&o.k==='S')add('ch',o.why);}); 1735 if(r.q3.k===side)add('who',r.q3.why); 1736 for(const k in ticks){const c=rowCell(k,side);if(!c)continue;ticks[k].slice(0,2).forEach(w=>{const e=document.createElement('span');e.className='why';e.textContent='â '+w;c.appendChild(e);});} 1737 const hd=cmpEl.querySelector('.cmp-h');const hs=hd.querySelector(side==='S'?'.su':'.tm'),ho=hd.querySelector(side==='S'?'.tm':'.su'); 1738 hs.insertAdjacentHTML('afterbegin','<span class="badge">Best fit for you</span>');ho.insertAdjacentHTML('afterbegin','<span class="alt">Also an option</span>'); 1739 const bs=document.getElementById('btn-suite'),bt=document.getElementById('btn-team'); 1740 if(side==='S'){bs.className='btn btn-o';bt.className='btn btn-ghost';} 1741 else{bt.className='btn btn-o';bs.className='btn btn-ghost';} 1742 // the recommended figure keeps moving; the other one pauses 1743 OF.forEach(o=>o.paused=(o.kind==='suite')!==(side==='S')); 1744 let h=`<div class="fit-line"><p class="fit-s" tabindex="-1" id="fit-s">${esc(sentence(r))}</p><div class="fit-notes">`; 1745 if(r.mix)h+=`<p>For something live this week, <a href="mailto:[email protected]?subject=ADROSUITE" data-chat="I'd like to talk to someone about ADROSUITE">talk to us about ADROSUITE</a>.</p>`; 1746 h+=`<p><button type="button" class="qz-back" data-act="reset">Start over</button></p></div></div>`; 1747 fitEl.innerHTML=h; 1748 if(scroll){const y=fitEl.getBoundingClientRect().top+scrollY-20;scrollTo({top:y,behavior:RM?'auto':'smooth'});const f=document.getElementById('fit-s');f&&f.focus({preventScroll:true});} 1749} 1750fitEl.addEventListener('click',ev=>{const b=ev.target.closest('[data-act="reset"]');if(b)reset(true);}); 1751function reset(focus){step=0;ans=[null,[],null];clear();render(false); 1752 if(focus){const y=document.getElementById('quiz').getBoundingClientRect().top+scrollY-20;scrollTo({top:y,behavior:RM?'auto':'smooth'});const q=document.getElementById('qz-q');q&&q.focus({preventScroll:true});}} 1753render(false); 1754const qzBox=document.getElementById('qz'); 1755if(!RM&&qzBox&&'IntersectionObserver' in window){const io=new IntersectionObserver(es=>{if(es.some(e=>e.isIntersecting)){io.disconnect();qzBox.classList.add('nudge');setTimeout(()=>qzBox.classList.remove('nudge'),1600);}},{threshold:.45});io.observe(qzBox);} 1756if(Q.has('nudge')&&qzBox)qzBox.classList.add('nudge'); 1757// screenshot helpers 1758const QZ=Q.get('quiz'); 1759// q2/q3: that question, with the earlier ones answered; suite/team: a result; team51: 51â100 people with only ADROSUITE answers (still ADROTEAM) 1760const PRE={suite:[0,[0,1,2],0],team:[3,[6,7,8],10],team51:[3,[0,1,2],0]}; 1761if(PRE[QZ]){ans=[PRE[QZ][0],PRE[QZ][1].slice(),PRE[QZ][2]];step=NQ;render(false);show(false);} 1762else if(QZ==='q2'||QZ==='q3'){ans=[0,QZ==='q3'?[0,1]:[],null];step=QZ==='q3'?2:1;render(false);} 1763// concept I: the chat agent runs the same quiz in the conversation and hands its answers over, so the page shows the same result. 1764// window.__quizApply([q1,[q2...],q3]) applies them; ?fit=q1.q2a-q2b.q3 does the same from a link (e.g. ?fit=3.6-7.10) 1765const apply=a=>{if(!a||a[0]==null||a[2]==null)return false;ans=[+a[0],(a[1]||[]).map(Number),+a[2]];step=NQ;render(false);show(false);return true}; 1766window.__quizApply=apply; 1767if(Q.get('fit')){const p=Q.get('fit').split('.');apply([p[0],p[1]?p[1].split('-').filter(Boolean):[],p[2]]);} 1768})(); 1769/* end of the quiz */ 1770 1771// agent counts, for QA 1772window.__bench=n=>{const r=[];for(const S of STAGES){const t0=performance.now();for(let i=0;i<n;i++){S.update(1/60);S.draw();}S.ctx.getImageData(0,0,1,1);r.push(+((performance.now()-t0)/n).toFixed(2))}return r}; 1773window.__adro=()=>({stages:STAGES.map(S=>S.agents.length),floor:FS?FS.nAg:0,scenes:SC.map(s=>s.kind+':'+s.n).join(' ')}); 1774// ?hover=<n>: show belief row n (0-based) in its hover state, for screenshots
1775if(Q.has('hover')){const li=$$('.belief')[+Q.get('hover')||0];if(li){li.classList.add('is-hover');const g=G.find(g=>g.cv.closest('.belief')===li);if(g){g.hot=true;g.t0=performance.now()/1000}}} 1776const resizeAll=()=>{STAGES.forEach(S=>S.resize());if(FS)FS.resize();SC.forEach(s=>s.resize());OF.forEach(s=>s.resize());D.forEach(d=>{Object.assign(d,setupCanvas(d.cv));d.P=null});G.forEach(g=>Object.assign(g,setupCanvas(g.cv)));readScroll();drawAll(performance.now(),0)}; 1777let rt;addEventListener('resize',()=>{clearTimeout(rt);rt=setTimeout(resizeAll,140)}); 1778// guard: if a canvas's box changed without a resize event (fonts, iframes, zoom), resize it 1779const sizeOK=S=>Math.abs(S.cv.clientWidth-S.W)<2&&Math.abs(S.cv.clientHeight-S.H)<2; 1780 1781function drawAll(now,dt){ 1782 const t=now/1000; 1783 if(STAGES.some(S=>!sizeOK(S))){resizeAll();return} 1784 for(const S of STAGES){const on=onScreen(S.cv);if(on)S.started=true;if(on||dt===0){S.update(dt);S.draw();}} 1785 if(FS&&(onScreen(floorEl,100)||dt===0)){if(dt&&!RM)FS.step(dt);FS.draw();} 1786 for(const s of OF){const on=onScreen(s.cv,60);if(on)s.started=true;if(on||dt===0){if(!s.paused)s.step(dt);s.draw();}} 1787 for(const s of SC){const on=onScreen(s.cv,60);if(on)s.started=true;if(on||dt===0){s.step(dt);s.draw();}} 1788 for(const d of D)if(dt===0||onScreen(d.cv,100))drawDivider(d,RM?0:t); 1789 if(dt===0)G.forEach(g=>drawGlyph(g,0,true)); 1790 else G.forEach(g=>{if(g.hot)drawGlyph(g,t-g.t0+.2,false)}); 1791} 1792// fast-forward for screenshots 1793if(FT&&!RM){if(FS)FS.t=FT;STAGES.forEach(S=>{S.started=true;for(let i=0;i<Math.round(FT*60);i++)S.update(1/60);});SC.forEach(s=>{s.started=true;s.t=FT});OF.forEach(s=>{s.started=true;s.t=FT});} 1794drawAll(performance.now(),0); 1795if(document.fonts)document.fonts.ready.then(()=>{resizeAll()}); 1796// ?scroll=<px|N%|Nvh>: jump there and run the real scroll path; ?f=<phase>: jump to that point of the landing breakdown 1797if(Q.has('scroll')||Q.has('f')){const v=Q.get('scroll')||'0',H=()=>document.documentElement.scrollHeight-innerHeight; 1798 const go=()=>{if(Q.has('f'))__toF(parseFloat(Q.get('f')));else{const y=v.endsWith('%')?parseFloat(v)/100*H():v.endsWith('vh')?parseFloat(v)*innerHeight/100:parseFloat(v)||0;scrollTo({top:y,behavior:'instant'});}readScroll();if(S1)S1.f=S1.fT;drawAll(performance.now(),0)}; 1799 go();addEventListener('load',go);} 1800if(!RM&&!FREEZE){let last=performance.now();const loop=now=>{const dt=Math.min(.05,(now-last)/1000);last=now;drawAll(now,dt);requestAnimationFrame(loop)};requestAnimationFrame(loop);} 1801// QA (round 7): the swarm's coverage of the official ink. __swCov(i): stage i's agents where they are now, whole mark, 1802// rasterised at 2 px per mark unit as discs of the particle's size, against the official ink. A gap = ink farther than one 1803// particle spacing from every particle (a hole wider than two spacings). Homes by default (the ink agents' home points); 1804// tt: their live positions at time tt instead. Returns the numbers and a picture (gaps in red). 1805window.__swCov=(si,tt)=>{const S=STAGES[si||0];if(!S||!SWM)return null;const R=2,W=950*R,H=1000*R,sR=S.swsR,g=S.swg,P0=SWP[MK]; 1806 for(const a of S.agents)swHome(a,tt==null?0:tt,S.G,MK,S.swFsp);const X=a=>a.mk&&tt==null?a.bx:a.hx,Y=a=>a.mk&&tt==null?a.by:a.hy; 1807 const sp=S.agents.esp||Math.sqrt(P0.eArea*g*g/(sR*sR)),rho=(P0.eL*g/sR*.5+P0.elw*g/sR*.5); // spacing and particle radius, mark units 1808 const fp=PARTS[PI.frame].faces[0].p,list=S.agents.filter(a=>a.edge&&a.tone!=='V'&&a.tone!=='CS'&&a.tone!=='CI'&&!(a.core&&inPoly(a.hx,a.hy,fp))); 1809 const mkc=()=>{const c=document.createElement('canvas');c.width=W;c.height=H;const x=c.getContext('2d');x.setTransform(R,0,0,R,0,0);return[c,x]}; 1810 const [ci,xi]=mkc();xi.fillStyle='#000';xi.fill(MARK_FULL,'evenodd'); 1811 const disc=(r)=>{const [c,x]=mkc();x.fillStyle='#000';x.beginPath();for(const a of list){x.moveTo(X(a)+r,Y(a));x.arc(X(a),Y(a),r,0,TAU)}x.fill();return x.getImageData(0,0,W,H).data}; 1812 const I=xi.getImageData(0,0,W,H).data,C1=disc(rho),C2=disc(rho+sp); 1813 let ink=0,cov=0,gap=0,out=0;const offBy={};const G=new Uint8Array(W*H); 1814 for(let i=0;i<W*H;i++){if(I[i*4+3]<128)continue;ink++;if(C1[i*4+3]>=128)cov++;if(C2[i*4+3]<128){G[i]=1;gap++}} 1815 for(const a of list){const i=Math.floor(X(a)*R),j=Math.floor(Y(a)*R);if(i<0||j<0||i>=W||j>=H||I[(j*W+i)*4+3]<128){out++;const q=(a.core?'core-'+a.tone:a.rim?'rim':'fill');offBy[q]=(offBy[q]||0)+1}} 1816 // gap regions (4-connected) 1817 const regs=[];const st=[];for(let i=0;i<W*H;i++){if(G[i]!==1)continue;let n=0,x0=1e9,x1=-1,y0=1e9,y1=-1;G[i]=2;st.push(i); 1818 while(st.length){const q=st.pop(),x=q%W,y=(q/W)|0;n++;x0=Math.min(x0,x);x1=Math.max(x1,x);y0=Math.min(y0,y);y1=Math.max(y1,y); 1819 for(const d of[q-1,q+1,q-W,q+W])if(d>=0&&d<W*H&&G[d]===1){G[d]=2;st.push(d)}} 1820 regs.push({x:(x0+x1)/2/R,y:(y0+y1)/2/R,w:(x1-x0+1)/R,h:(y1-y0+1)/R,px:n})} 1821 // the picture: cream, the ink pale, the particles' discs dark green, the gaps red (circled) 1822 const [co,xo]=mkc();xo.setTransform(1,0,0,1,0,0);const im=xo.createImageData(W,H),d=im.data; 1823 for(let i=0;i<W*H;i++){let c=[239,231,218];if(I[i*4+3]>=128)c=C1[i*4+3]>=128?[18,29,26]:G[i]?[220,30,30]:[196,206,199];else if(C1[i*4+3]>=128)c=[120,120,220];d[i*4]=c[0];d[i*4+1]=c[1];d[i*4+2]=c[2];d[i*4+3]=255} 1824 xo.putImageData(im,0,0);xo.setTransform(R,0,0,R,0,0);xo.strokeStyle='#dc1e1e';xo.lineWidth=2;
1824regs.forEach(r=>{xo.beginPath();xo.arc(r.x,r.y,Math.max(r.w,r.h)/2+10,0,TAU);xo.stroke()}); 1825 xo.setTransform(1,0,0,1,0,0);xo.fillStyle='#121D1A';xo.font='600 34px sans-serif'; 1826 xo.fillText(`mk=4 coverage (${tt==null?'home positions':'live, t='+tt+'s'}): ${list.length} stroke agents, spacing ${sp.toFixed(2)} u, particle r ${rho.toFixed(2)} u; ink covered by particles ${(100*cov/ink).toFixed(1)}%; gaps wider than 2 spacings: ${regs.length}; agents off the ink: ${out}`,24,1980); 1827 return{agents:list.length,sp:+sp.toFixed(2),rho:+rho.toFixed(2),covered:+(100*cov/ink).toFixed(2),gapPx:gap,gaps:regs.length,regs:regs.slice(0,20),off:out,offBy,png:co.toDataURL('image/png')}}; 1828window.__stages=STAGES;window.__parts=PARTS; // QA handles 1829})();
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.