@@ -138,6 +138,9 @@ const yTicks = [1.0, 0.5, 0.2, 0.05].map(frac => {
138138<!-- HERO -->
139139<section class =" hero" >
140140 <div class =" gridbg" aria-hidden =" true" ></div >
141+ <svg class =" hero-boids" id =" hero-boids" aria-hidden =" true" viewBox =" 0 0 1200 600" preserveAspectRatio =" xMidYMid slice" >
142+ <g class =" hero-boids-flock" ></g >
143+ </svg >
141144 <div class =" wrap" >
142145 <div class =" hero-top" >
143146 <div class =" chip" id =" exchange-chip" aria-live =" polite" >
@@ -512,7 +515,7 @@ const yTicks = [1.0, 0.5, 0.2, 0.05].map(frac => {
512515 <div class =" chart-head" >
513516 <div >
514517 <div class =" num" >Agents · weekly</div >
515- <div class =" title" >The network, <span class =" accent" ><span data-live =" agents-long" >{ liveAgents } </span > agents</span > in { weeksLabel } weeks</div >
518+ <div class =" title" >The network, <span class =" accent" ><span data-live =" agents-long" >{ liveAgents } </span > agents</span > in < span data-live = " weeks " > { weeksLabel } </ span > weeks</div >
516519 </div >
517520 <div class =" legend" >
518521 <span ><span class =" swatch sw-a" ></span >Pilot agents</span >
@@ -803,6 +806,92 @@ const yTicks = [1.0, 0.5, 0.2, 0.05].map(frac => {
803806 setInterval(shootPacket, 260);
804807})();
805808
809+ // Hero boids - decorative flock of triangle-shaped boids drifting behind the
810+ // headline. Reynolds separation/alignment/cohesion in a 1200x600 viewBox with
811+ // a soft edge cushion. Reduced-motion renders a static frame.
812+ (function(){
813+ var svg = document .getElementById (' hero-boids' );
814+ if (! svg ) return ;
815+ var flockG = svg .querySelector (' .hero-boids-flock' );
816+ var W = 1200 , H = 600 ;
817+ var TRI_POINTS = ' -5,-3.2 -5,3.2 8.5,0' ;
818+
819+ var seed = 7 ;
820+ function rand(){ seed = (seed * 9301 + 49297 ) % 233280 ; return seed / 233280 ; }
821+
822+ var N = 50 ;
823+ var boids = [];
824+ for (var i = 0 ; i < N ; i ++ ) {
825+ var ang0 = rand () * Math .PI * 2 ;
826+ var sp0 = 0.8 + rand () * 0.6 ;
827+ boids .push ({
828+ x: rand () * W ,
829+ y: rand () * H ,
830+ vx: Math .cos (ang0 ) * sp0 ,
831+ vy: Math .sin (ang0 ) * sp0 ,
832+ scale: 0.7 + rand () * 0.8
833+ });
834+ }
835+
836+ var tris = boids .map (function (b ){
837+ var t = document .createElementNS (' http://www.w3.org/2000/svg' ,' polygon' );
838+ t .setAttribute (' points' , TRI_POINTS );
839+ flockG .appendChild (t );
840+ return t ;
841+ });
842+
843+ function paint(){
844+ for (var i = 0 ; i < boids .length ; i ++ ) {
845+ var b = boids [i ];
846+ var ang = Math .atan2 (b .vy , b .vx ) * 180 / Math .PI ;
847+ tris [i ].setAttribute (' transform' , ' translate(' + b .x .toFixed (1 )+ ' ' + b .y .toFixed (1 )+ ' ) rotate(' + ang .toFixed (1 )+ ' ) scale(' + b .scale .toFixed (2 )+ ' )' );
848+ }
849+ }
850+ paint ();
851+
852+ if (window .matchMedia && window .matchMedia (' (prefers-reduced-motion: reduce)' ).matches ) return ;
853+
854+ var SEP_R = 28 , ALIGN_R = 70 , COH_R = 110 ;
855+ var SEP_W = 0.10 , ALIGN_W = 0.05 , COH_W = 0.00025 ;
856+ var EDGE_MARGIN = 80 , EDGE_W = 0.0028 ;
857+ var MAX_SPEED = 1.5 , MIN_SPEED = 0.55 ;
858+
859+ function tick(){
860+ for (var i = 0 ; i < boids .length ; i ++ ) {
861+ var b = boids [i ];
862+ var sx = 0 , sy = 0 ;
863+ var ax = 0 , ay = 0 , an = 0 ;
864+ var cx = 0 , cy = 0 , cn = 0 ;
865+ for (var j = 0 ; j < boids .length ; j ++ ) {
866+ if (i === j ) continue ;
867+ var o = boids [j ];
868+ var dx = o .x - b .x , dy = o .y - b .y ;
869+ var d2 = dx * dx + dy * dy ;
870+ if (d2 < 0.01 ) continue ;
871+ if (d2 < SEP_R * SEP_R ) { var d = Math .sqrt (d2 ); sx -= dx / d ; sy -= dy / d ; }
872+ if (d2 < ALIGN_R * ALIGN_R ) { ax += o .vx ; ay += o .vy ; an ++ ; }
873+ if (d2 < COH_R * COH_R ) { cx += o .x ; cy += o .y ; cn ++ ; }
874+ }
875+ var fx = sx * SEP_W , fy = sy * SEP_W ;
876+ if (an > 0 ) { fx += (ax / an - b .vx ) * ALIGN_W ; fy += (ay / an - b .vy ) * ALIGN_W ; }
877+ if (cn > 0 ) { fx += (cx / cn - b .x ) * COH_W ; fy += (cy / cn - b .y ) * COH_W ; }
878+ if (b .x < EDGE_MARGIN ) fx += (EDGE_MARGIN - b .x ) * EDGE_W ;
879+ else if (b .x > W - EDGE_MARGIN ) fx -= (b .x - (W - EDGE_MARGIN )) * EDGE_W ;
880+ if (b .y < EDGE_MARGIN ) fy += (EDGE_MARGIN - b .y ) * EDGE_W ;
881+ else if (b .y > H - EDGE_MARGIN ) fy -= (b .y - (H - EDGE_MARGIN )) * EDGE_W ;
882+
883+ b .vx += fx ; b .vy += fy ;
884+ var sp = Math .sqrt (b .vx * b .vx + b .vy * b .vy );
885+ if (sp > MAX_SPEED ) { b .vx = b .vx / sp * MAX_SPEED ; b .vy = b .vy / sp * MAX_SPEED ; }
886+ else if (sp < MIN_SPEED && sp > 0 ) { b .vx = b .vx / sp * MIN_SPEED ; b .vy = b .vy / sp * MIN_SPEED ; }
887+ b .x += b .vx ; b .y += b .vy ;
888+ }
889+ paint ();
890+ requestAnimationFrame (tick );
891+ }
892+ requestAnimationFrame (tick );
893+ } )();
894+
806895// Live exchange counter - today's exchange volume on the network.
807896// Purely local simulation: derives today's baseline from wall-clock and
808897// drifts it forward in-page. No server calls. Tick interval intentionally
@@ -833,6 +922,14 @@ const yTicks = [1.0, 0.5, 0.2, 0.05].map(frac => {
833922// Live stats refresh - fetches polo on page load and every 60s so numbers
834923// stay current between site deploys. Build-time values are the fallback.
835924(function(){
925+ var LAUNCH_TS = new Date (' 2026-02-09T00:00:00Z' ).getTime () / 1000 ;
926+ var TICKS = 10 , EXP_K = 4.2 ;
927+ var SVG_W = 1200 , PAD_L = 60 , PAD_R = 60 , PAD_T = 40 , PAD_B = 40 ;
928+ var SVG_H = 360 ;
929+ var PLOT_W = SVG_W - PAD_L - PAD_R ;
930+ var PLOT_H = SVG_H - PAD_T - PAD_B ;
931+ var WORDS = [' zero' ,' one' ,' two' ,' three' ,' four' ,' five' ,' six' ,' seven' ,' eight' ,' nine' ,' ten' ,' eleven' ,' twelve' ,' thirteen' ,' fourteen' ,' fifteen' ,' sixteen' ,' seventeen' ,' eighteen' ,' nineteen' ,' twenty' ];
932+
836933 function fmtAgentsLong(n ){
837934 if (n >= 1_000_000 ) return ' ~' + (n / 1_000_000 ).toFixed (1 ).replace (/ \. 0$ / , ' ' ) + ' M' ;
838935 if (n >= 10_000 ) return ' ~' + Math .round (n / 1000 ) + ' ,000' ;
@@ -844,6 +941,11 @@ const yTicks = [1.0, 0.5, 0.2, 0.05].map(frac => {
844941 if (n >= 1_000_000 ) return Math .round (n / 1_000_000 ) + ' M+' ;
845942 return String (n );
846943 }
944+ function fmtAxis(v ){
945+ if (v >= 1_000_000 ) return (v / 1_000_000 ).toFixed (1 ).replace (/ \. 0$ / , ' ' ) + ' M' ;
946+ if (v >= 1_000 ) return (v / 1000 ).toFixed (v >= 10_000 ? 0 : 1 ).replace (/ \. 0$ / , ' ' ) + ' K' ;
947+ return String (Math .round (v ));
948+ }
847949 function growth7d(daily ){
848950 if (! Array .isArray (daily ) || daily .length < 2 ) return null ;
849951 var last = daily [daily .length - 1 ];
@@ -863,6 +965,76 @@ const yTicks = [1.0, 0.5, 0.2, 0.05].map(frac => {
863965 function setAll(sel , value ){
864966 document .querySelectorAll (sel ).forEach (function (el ){ el .textContent = value ; });
865967 }
968+ function rebuildChart(finalVal ){
969+ if (! Number .isFinite (finalVal ) || finalVal <= 0 ) return ;
970+ var svg = document .querySelector (' .chart-wrap svg' );
971+ if (! svg ) return ;
972+
973+ var weeksSinceLaunch = Math .max (1 , Math .round ((Date .now () / 1000 - LAUNCH_TS ) / (7 * 86400 )));
974+ var expDenom = Math .exp (EXP_K ) - 1 ;
975+ var pts = [];
976+ for (var i = 0 ; i < TICKS ; i ++ ) {
977+ var t = i / (TICKS - 1 );
978+ var v = finalVal * (Math .exp (EXP_K * t ) - 1 ) / expDenom ;
979+ var label = i === TICKS - 1 ? ' NOW' : (' W' + (1 + Math .round (((weeksSinceLaunch - 1 ) * i ) / (TICKS - 1 ))));
980+ pts .push ({ label: label , value: Math .round (v ) });
981+ }
982+ var chartMax = pts [pts .length - 1 ].value || 1 ;
983+ var xFor = function (i ){ return PAD_L + (i / (pts .length - 1 )) * PLOT_W ; };
984+ var yFor = function (v ){ return PAD_T + (1 - v / chartMax ) * PLOT_H ; };
985+ var coords = pts .map (function (p , i ){ return { x: xFor (i ), y: yFor (p .value ), label: p .label , value: p .value }; });
986+
987+ var SAMPLES = 200 ;
988+ var linePath = ' ' ;
989+ for (var j = 0 ; j < SAMPLES ; j ++ ) {
990+ var tt = j / (SAMPLES - 1 );
991+ var xx = PAD_L + tt * PLOT_W ;
992+ var vv = finalVal * (Math .exp (EXP_K * tt ) - 1 ) / expDenom ;
993+ var yy = yFor (vv );
994+ linePath += (j === 0 ? ' M' : ' L' ) + xx .toFixed (1 ) + ' ' + yy .toFixed (1 );
995+ }
996+ var areaPath = linePath + ' L ' + (PAD_L + PLOT_W ).toFixed (1 ) + ' ' + (PAD_T + PLOT_H ).toFixed (1 ) + ' L ' + PAD_L .toFixed (1 ) + ' ' + (PAD_T + PLOT_H ).toFixed (1 ) + ' Z' ;
997+
998+ var yTicks = [1.0 , 0.5 , 0.2 , 0.05 ].map (function (frac ){
999+ var v = chartMax * frac ;
1000+ return { label: fmtAxis (v ), y: yFor (v ) };
1001+ });
1002+
1003+ var grid = svg .querySelector (' .chart-grid' );
1004+ if (grid ) grid .innerHTML = yTicks .map (function (t ){ return ' <line x1="0" y1="' + t .y + ' " x2="1200" y2="' + t .y + ' "></line>' ; }).join (' ' );
1005+
1006+ var area = svg .querySelector (' .chart-area' );
1007+ if (area ) area .setAttribute (' d' , areaPath );
1008+ var line = svg .querySelector (' .chart-line' );
1009+ if (line ) line .setAttribute (' d' , linePath );
1010+
1011+ var dots = svg .querySelector (' .chart-dots' );
1012+ if (dots ) dots .innerHTML = coords .map (function (c , i ){
1013+ var isLast = i === coords .length - 1 ;
1014+ return ' <circle cx="' + c .x + ' " cy="' + c .y + ' " r="' + (isLast ? 4 : 3 )+ ' "' + (isLast ? ' class="chart-dot-now"' : ' ' ) + ' ></circle>' ;
1015+ }).join (' ' );
1016+
1017+ var labels = svg .querySelector (' .chart-labels' );
1018+ if (labels ) labels .innerHTML = coords .map (function (c , i ){
1019+ var anchor = i === 0 ? ' start' : (i === coords .length - 1 ? ' end' : ' middle' );
1020+ return ' <text x="' + c .x + ' " y="335" text-anchor="' + anchor + ' ">' + c .label + ' </text>' ;
1021+ }).join (' ' );
1022+
1023+ var yaxis = svg .querySelector (' .chart-yaxis' );
1024+ if (yaxis ) yaxis .innerHTML = yTicks .map (function (t ){
1025+ return ' <text x="12" y="' + (t .y + 4 )+ ' " text-anchor="start">' + t .label + ' </text>' ;
1026+ }).join (' ' );
1027+
1028+ var finalDot = coords [coords .length - 1 ];
1029+ var finalLabel = svg .querySelector (' [data-live-chart-label]' );
1030+ if (finalLabel ) {
1031+ finalLabel .setAttribute (' x' , String (finalDot .x - 10 ));
1032+ finalLabel .setAttribute (' y' , String (finalDot .y - 14 ));
1033+ }
1034+
1035+ var weeksWord = WORDS [weeksSinceLaunch ] || String (weeksSinceLaunch );
1036+ setAll (' [data-live="weeks"]' , weeksWord );
1037+ }
8661038 function refresh(){
8671039 fetch (' https://polo.pilotprotocol.network/api/stats' , { cache: ' no-store' })
8681040 .then (function (r ){ return r .ok ? r .json () : null ; })
@@ -877,6 +1049,7 @@ const yTicks = [1.0, 0.5, 0.2, 0.05].map(frac => {
8771049 document .querySelectorAll (' [data-live-chart-label]' ).forEach (function (el ){
8781050 el .textContent = longStr + ' agents' ;
8791051 });
1052+ rebuildChart (activeN );
8801053 }
8811054 if (typeof s .total_requests === ' number' ) {
8821055 setAll (' [data-live="requests"]' , fmtRequests (s .total_requests ));
0 commit comments