PageSourceSearch

https://zerorank.ai/assets/js/home.js?v=20260923-7

js zerorank.ai collected 2026-09-30 10:43:50 UTC 19,448 bytes, 566 lines download raw bytes

1/* ZeroRank home.js — the homepage-only sections (radar, bento, workflow, growth curve, MCP transmission). */
2(function () {
3  "use strict";
4
5  var radar = document.getElementById("radar");
6  var sweep = document.getElementById("sweep");
7  var hub   = document.getElementById("hub");
8  /* the chrome is shared with pages that have no radar — bail rather than
9     throw, or the reveal observer below never gets to run */
10  if (!radar) return;
11  var nodes = [].slice.call(radar.querySelectorAll(".node"));
12  var still = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
13
14  /* ── rays ──────────────────────────────────────────────────────────────
15     [ left %, glow centre y %, glow y-radius % ] — measured off node
16     9377:6376. Columns sit 95.072px apart; the glow centre and radius vary
17     column to column, which is what keeps the wash from looking mechanical.
18     ────────────────────────────────────────────────────────────────────── */
19  var RAYS = [
20    [ 0.0339, 44.526, 54.515],
21    [ 7.9504, 44.526, 54.515],
22    [15.8673, 44.526, 54.515],
23    [23.7838, 44.526, 54.515],
24    [31.7344, 44.526, 54.515],
25    [39.6571, 34.351, 49.414],
26    [47.5800, 46.806, 54.515],
27    [55.5025, 46.806, 54.515],
28    [63.4252, 56.155, 49.414],
29    [71.3478, 46.806, 54.515],
30    [79.2704, 46.806, 54.515],
31    [87.1931, 56.155, 49.414],
32    [95.1005, 57.238, 49.414]
33  ];
34
35  var raysEl = document.getElementById("rays");
36  RAYS.forEach(function (r, i) {
37    var el = document.createElement("div");
38    el.className = "ray";
39    el.style.left = r[0] + "%";
40    el.style.setProperty("--cy", r[1] + "%");
41    el.style.setProperty("--ry", r[2] + "%");
42    el.style.setProperty("--dur", (38 + (i % 5) * 9) + "s");
43    el.style.setProperty("--delay", (i * -4.7) + "s");
44    raysEl.appendChild(el);
45  });
46
47  /* ── nodes ─────────────────────────────────────────────────────────────
48     Each node is built twice: an amber question-mark chip for the unknown
49     state, and the resolved prompt pill. They share one grid cell so they
50     swap in place.
51     ────────────────────────────────────────────────────────────────────── */
52  var QMARK =
53    '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"' +
54    ' stroke-linecap="round" stroke-linejoin="round">' +
55    '<circle cx="12" cy="12" r="10"/>' +
56    '<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>' +
57    '<path d="M12 17h.01"/></svg>';
58
59  nodes.forEach(function (n, i) {
60    n.innerHTML =
61      '<div class="node__pills">' +
62        '<div class="pill pill--unknown"><span class="pill__icon">' + QMARK + '</span></div>' +
63        '<div class="pill pill--prompt">' +
64          '<span class="pill__icon pill__icon--ai"></span>' +
65          '<span>' + n.dataset.prompt + '</span>' +
66        '</div>' +
67      '</div>' +
68      '<div class="node__dot"></div>';
69
70    n.__angle = parseFloat(n.dataset.angle);
71    n.__hit   = false;
72    n.__delay = 120 + i * 160;
73  });
74
75  if (still) {
76    nodes.forEach(function (n) { n.classList.add("is-live", "is-resolved"); });
77    return;
78  }
79
80  var PERIOD   = 9000;   /* ~4.5s of sweep, ~4.5s to read the result */
81  var CLEAR_AT = 0.88;
82  var start    = null;
83  var cycle    = -1;
84  var rafId    = null;
85  var running  = false;
86
87  function reset() {
88    radar.classList.add("is-instant");
89    radar.classList.remove("is-clearing");
90    nodes.forEach(function (n) {
91      n.classList.remove("is-live", "is-resolved");
92      n.__hit = false;
93    });
94    void radar.offsetWidth;
95    radar.classList.remove("is-instant");
96
97    nodes.forEach(function (n) {
98      setTimeout(function () { n.classList.add("is-live"); }, n.__delay);
99    });
100
101    hub.classList.remove("is-pulsing");
102    void hub.offsetWidth;
103    hub.classList.add("is-pulsing");
104  }
105
106  function frame(now) {
107    if (start === null) start = now;
108
109    var elapsed   = now - start;
110    var t         = (elapsed % PERIOD) / PERIOD;
111    var thisCycle = Math.floor(elapsed / PERIOD);
112    var angle     = -90 + t * 360;
113
114    if (thisCycle !== cycle) { cycle = thisCycle; reset(); }
115
116    sweep.style.transform = "rotate(" + angle + "deg)";
117
118    nodes.forEach(function (n) {
119      if (!n.__hit && angle >= n.__angle) {
120        n.__hit = true;
121        n.classList.add("is-resolved");
122      }
123    });
124
125    if (t > CLEAR_AT) radar.classList.add("is-clearing");
126
127    rafId = requestAnimationFrame(frame);
128  }
129
130  /* ── playback gate ──────────────────────────────────────────────────
131     Left ungated, the sweep starts at page load and you arrive mid-cycle —
132     usually just in time to watch it clear. It now starts from zero once a
133     fifth of the radar is on screen, and rewinds when it leaves. */
134  function play() {
135    if (running) return;
136    running = true;
137    start = null;
138    cycle = -1;
139    rafId = requestAnimationFrame(frame);
140  }
141  function stop() {
142    running = false;
143    if (rafId) cancelAnimationFrame(rafId);
144    rafId = null;
145  }
146
147  if ("IntersectionObserver" in window) {
148    new IntersectionObserver(function (entries) {
149      entries.forEach(function (e) { e.isIntersecting ? play() : stop(); });
150    }, { threshold: 0.2 }).observe(radar);
151  } else {
152    play();
153  }
154})();
155
156
157/* ---------------------------------------------------------------- */
158
159
160(function () {
161  var section = document.querySelector('.ll-bento');
162  if (!section) return;
163
164  var reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
165  var cards  = [].slice.call(section.querySelectorAll('.ll-bento__card'));
166  var gauge  = section.querySelector('.ll-gauge__dial');
167  var loop   = section.dataset.loop !== 'false';
168
169  /* offset-path is what keeps the chart's head glued to the leading edge.
170     Without it the head would drift off the curve, so hide it instead. */
171  if (!(window.CSS && CSS.supports && CSS.supports('offset-path', 'path("M 0,0 L 1,1")'))) {
172    var head = section.querySelector('.ll-chart__head');
173    if (head) head.style.display = 'none';
174  }
175
176  /* ---------- counters + needle, driven off one clock so the number and
177       the dial can never disagree with each other ---------------------- */
178
179  var easeOut = function (t) { return 1 - Math.pow(1 - t, 3); };
180
181  function makeDriver(el, cycle, onFrame) {
182    var t0 = null, raf = null, live = false;
183
184    function frame(now) {
185      if (t0 === null) t0 = now;
186      var t = (now - t0) / cycle;
187      if (!loop && t >= 1) { onFrame(1); raf = null; return; }
188      onFrame(t % 1);
189      raf = requestAnimationFrame(frame);
190    }
191    return {
192      start: function () { if (live) return; live = true; t0 = null; raf = requestAnimationFrame(frame); },
193      stop:  function () { live = false; if (raf) cancelAnimationFrame(raf); raf = null; },
194      reset: function () {
195        t0 = null;
196        if (live && !raf) raf = requestAnimationFrame(frame);
197      }
198    };
199  }
200
201  /* 0 -> peak (eased), hold, then fall away so the loop can restart clean.
202     In play-once mode there is nothing to restart, so it rises and holds. */
203  function phase(t) {
204    if (!loop) return easeOut(Math.min(1, t / .52));
205    if (t < .52) return easeOut(t / .52);
206    if (t < .84) return 1;
207    return 1 - easeOut(Math.min(1, (t - .84) / .12));
208  }
209
210  var drivers = {};
211
212  /* --- the dial --- */
213  if (gauge) {
214    var pctEl = section.querySelector('.ll-gauge__pct');
215    var TARGET = 0.80;
216    drivers.gauge = makeDriver(gauge, parseFloat(getComputedStyle(section).getPropertyValue('--ll-gauge-loop')) * 1000 || 5800,
217      function (t) {
218        var p = phase(t) * TARGET;
219        gauge.style.setProperty('--p', p.toFixed(4));
220        if (pctEl) pctEl.textContent = Math.round(p * 100) + '%';
221      });
222  }
223
224  /* --- the big number --- */
225  var volVal  = section.querySelector('[data-count-to="2.4"]');
226  var volGlow = section.querySelector('.ll-vol__glow');
227  if (volVal) {
228    drivers.volume = makeDriver(volVal, parseFloat(getComputedStyle(section).getPropertyValue('--ll-count-loop')) * 1000 || 6600,
229      function (t) {
230        var txt = (phase(t) * 2.4).toFixed(1) + 'M';
231        volVal.textContent = txt;
232        if (volGlow) volGlow.textContent = txt;
233      });
234  }
235
236  function play()  { for (var k in drivers) drivers[k].start(); }
237  function pause() { for (var k in drivers) drivers[k].stop(); }
238
239  /* ---------- in view -> run. out of view -> stop and rewind ---------- */
240
241  if (reduce.matches) {
242    section.classList.add('is-live');
243    if (gauge) gauge.style.setProperty('--p', '0.8');
244    var p1 = section.querySelector('.ll-gauge__pct'); if (p1) p1.textContent = '80%';
245    if (volVal) { volVal.textContent = '2.4M'; if (volGlow) volGlow.textContent = '2.4M'; }
246    return;
247  }
248
249  var io = new IntersectionObserver(function (entries) {
250    entries.forEach(function (e) {
251      if (e.isIntersecting) { section.classList.add('is-live'); play(); }
252      else                  { section.classList.remove('is-live'); pause(); }
253    });
254  }, { threshold: .18 });
255  io.observe(section);
256
257  /* The cards used to replay from the top on hover. They loop on their own, so
258     that only ever fought the loop already running — removed. */
259}());
260
261
262/* ---------------------------------------------------------------- */
263
264
265(function(){
266  var flow  = document.querySelector('.wf .flow');
267  var stage = document.querySelector('.wf .flow-stage');
268  if(!flow || !stage) return;
269
270  var $ = function(id){ return document.getElementById(id); };
271  var reduced = window.matchMedia('(prefers-reduced-motion: reduce)');
272
273  /* ---------- fit the fixed-geometry diagram to its column ---------- */
274  var FLOW_W = 550.314, FLOW_H = 474.686;
275  function fit(){
276    var stacked = window.matchMedia('(max-width:1179px)').matches;
277    if(!stacked){
278      flow.style.setProperty('--fs', 1);
279      stage.style.height = '';
280      return;
281    }
282    var s = Math.min(1, stage.clientWidth / FLOW_W);
283    flow.style.setProperty('--fs', s);
284    stage.style.height = (FLOW_H * s) + 'px';
285  }
286  fit();
287  if(window.ResizeObserver){
288    /* fit() writes stage.style.height. Observing document.body meant that
289       write resized the observed element, and Chrome reports the re-entry as
290       "ResizeObserver loop completed with undelivered notifications".
291       Watching the stage's parent and deferring the write by one frame
292       breaks the cycle. */
293    var fitQueued = false;
294    var ro = new ResizeObserver(function(){
295      if (fitQueued) return;
296      fitQueued = true;
297      requestAnimationFrame(function(){ fitQueued = false; fit(); });
298    });
299    ro.observe(stage.parentNode || stage);
300  }
301  window.addEventListener('resize', fit);
302
303  /* ---------- the run ---------- */
304  var NODES = ['n1','n2','n3','n4','n5'];
305  var CONNS = ['c1','c2','c4'];
306  var timers = [];
307
308  function clear(){ timers.forEach(clearTimeout); timers = []; }
309  function at(ms, fn){ timers.push(setTimeout(fn, ms)); }
310
311  function set(id, state){ $(id).dataset.state = state; }
312  function fire(nodeId, connId){
313    $(nodeId).dataset.fired = 'true';
314    $(connId).classList.add('is-on');
315  }
316
317  function reset(){
318    flow.classList.add('is-resetting');
319    NODES.forEach(function(id){
320      var n = $(id);
321      n.dataset.state = 'idle';
322      n.dataset.fired = 'false';
323    });
324    CONNS.forEach(function(id){ $(id).classList.remove('is-on'); });
325    void flow.offsetWidth;              /* commit the snap */
326    flow.classList.remove('is-resetting');
327  }
328
329  /* durations, ms */
330  var RUN = 1250, RUN_LONG = 1650, RUN_END = 1150,
331      DRAW = 750, DRAW_BRANCH = 950, HOLD = 2000;
332
333  function play(){
334    clear();
335    reset();
336
337    var t = 350;
338
339    at(t, function(){ set('n1','running'); });
340    t += RUN;
341    at(t, function(){ set('n1','done'); fire('n1','c1'); });
342    t += DRAW;
343
344    at(t, function(){ set('n2','running'); });
345    t += RUN;
346    at(t, function(){ set('n2','done'); fire('n2','c2'); });
347    t += DRAW;
348
349    at(t, function(){ set('n3','running'); });
350    t += RUN_LONG;
351    at(t, function(){ set('n3','done'); fire('n3','c4'); });
352    t += DRAW_BRANCH;
353
354    at(t, function(){ set('n4','running'); });
355    t += RUN_END;
356    at(t, function(){ set('n4','done'); });
357    t += HOLD;
358
359    at(t, play);
360  }
361
362  /* the design's resting state, for reduced motion */
363  function still(){
364    clear();
365    set('n1','done'); $('n1').dataset.fired = 'true'; $('c1').classList.add('is-on');
366    set('n2','done'); $('n2').dataset.fired = 'true'; $('c2').classList.add('is-on');
367    set('n3','running');
368    $('n3').dataset.fired = 'true'; $('c4').classList.add('is-on');
369  }
370
371  function start(){ reduced.matches ? still() : play(); }
372  if(reduced.addEventListener) reduced.addEventListener('change', start);
373
374  /* only run while on screen */
375  if(window.IntersectionObserver){
376    new IntersectionObserver(function(entries){
377      entries.forEach(function(e){ e.isIntersecting ? start() : clear(); });
378    }, {threshold:.15}).observe(flow);
379  } else {
380    start();
381  }
382})();
383
384
385/* ---------------------------------------------------------------- */
386
387
388(function () {
389  var NS = 'http://www.w3.org/2000/svg';
390  var W  = 1920, H = 882, X_END = 1918, BAR_W = 2;
391
392  /* The curve, straight out of Figma (node 9377:5979 vectorPaths). */
393  var C = [[0, 811.5], [595, 789], [1460.5, 811.5], [X_END, 5.5]];
394
395  function bez(t, a, b, c, d) {
396    var m = 1 - t;
397    return m*m*m*a + 3*m*m*t*b + 3*m*t*t*c + t*t*t*d;
398  }
399
400  /* x(t) is monotonic on this curve, so a bisection is exact enough and cheap. */
401  function yAtX(x) {
402    var lo = 0, hi = 1, t = 0.5;
403    for (var i = 0; i < 22; i++) {
404      t = (lo + hi) / 2;
405      if (bez(t, C[0][0], C[1][0], C[2][0], C[3][0]) < x) lo = t; else hi = t;
406    }
407    return bez(t, C[0][1], C[1][1], C[2][1], C[3][1]);
408  }
409
410  function buildBars(section) {
411    var g = section.querySelector('[data-ll-bars]');
412    if (!g) return;
413
414    var cs    = getComputedStyle(section);
415    var pitch = parseFloat(cs.getPropertyValue('--ll-bar-pitch')) || 10;
416    var lead  = parseFloat(cs.getPropertyValue('--ll-lead'))      || 0.68;
417
418    if (g.dataset.pitch === String(pitch)) return;   // nothing changed
419    g.dataset.pitch = String(pitch);
420
421    var frag = document.createDocumentFragment();
422    for (var x = 0; x <= X_END; x += pitch) {
423      var y = yAtX(x);
424      var r = document.createElementNS(NS, 'rect');
425      r.setAttribute('class', 'll-bar');
426      r.setAttribute('x', x);
427      r.setAttribute('y', y.toFixed(2));
428      r.setAttribute('width', BAR_W);
429      r.setAttribute('height', (H - y).toFixed(2));
430      r.setAttribute('fill', 'url(#llBarGrad)');
431      r.style.setProperty('--ll-d', (x / W * lead).toFixed(4));
432      frag.appendChild(r);
433    }
434
435    g.textContent = '';
436    g.appendChild(frag);
437
438    var stat = section.querySelector('[data-ll-static]');
439    if (stat) stat.remove();
440  }
441
442  var sections = document.querySelectorAll('[data-ll-growth]');
443  if (!sections.length) return;
444
445  var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
446
447  Array.prototype.forEach.call(sections, function (section) {
448    buildBars(section);
449
450    var rt;
451    addEventListener('resize', function () {
452      clearTimeout(rt);
453      rt = setTimeout(function () { buildBars(section); }, 200);
454    });
455
456    if (reduce) { section.classList.add('is-revealed'); return; }
457
458    if (section.getAttribute('data-ll-growth') === 'scrub') {
459      var ticking = false;
460
461      var update = function () {
462        ticking = false;
463        var r  = section.getBoundingClientRect();
464        var vh = window.innerHeight || document.documentElement.clientHeight;
465        var p = (vh * 0.9 - r.top) / (vh * 0.75);
466        p = Math.max(0, Math.min(1, p));
467        section.style.setProperty('--ll-p', p.toFixed(4));
468        section.classList.toggle('is-revealed', p > 0.02);
469      };
470
471      var onScroll = function () {
472        if (!ticking) { ticking = true; requestAnimationFrame(update); }
473      };
474
475      addEventListener('scroll', onScroll, { passive: true });
476      addEventListener('resize', onScroll);
477      update();
478      return;
479    }
480
481    if (!('IntersectionObserver' in window)) {
482      section.classList.add('is-revealed');
483      return;
484    }
485
486    var io = new IntersectionObserver(function (entries) {
487      entries.forEach(function (entry) {
488        if (!entry.isIntersecting) return;
489        entry.target.classList.add('is-revealed');
490        io.unobserve(entry.target);
491      });
492    }, { threshold: 0.2 });
493
494    io.observe(section);
495  });
496})();
497
498
499/* ---------------------------------------------------------------- */
500
501
502(function () {
503  var section = document.querySelector('[data-ll-mcp]');
504  var field   = section && section.querySelector('[data-mcp-stars]');
505  if (!section || !field) return;
506
507  /* Starfield. One star per ~3600px², capped, so density holds as the card
508     resizes. Sizes are deliberately sub-pixel-ish: most land 0.7–1.6px. */
509  var built = 0;
510
511  function build() {
512    var r = section.getBoundingClientRect();
513    if (!r.width) return;
514
515    var n = Math.max(50, Math.min(190, Math.round((r.width * r.height) / 3600)));
516    if (n === built) return;
517    built = n;
518
519    var frag = document.createDocumentFragment();
520
521    for (var i = 0; i < n; i++) {
522      var glint = i % 22 === 7;                       // ~1 in 22 gets a cross
523      var s = glint
524        ? 5 + Math.random() * 4                       // 5–9px glints
525        : 0.7 + Math.pow(Math.random(), 2.2) * 1.5;   // biased small
526
527      var el = document.createElement('span');
528      el.className = 'll-mcp__star' + (glint ? ' ll-mcp__star--glint' : '');
529      el.style.left   = (Math.random() * 100).toFixed(3) + '%';
530      el.style.top    = (Math.random() * 100).toFixed(3) + '%';
531      el.style.width  = s.toFixed(2) + 'px';
532      el.style.height = s.toFixed(2) + 'px';
533      el.style.setProperty('--a', (glint ? 0.5 + Math.random() * 0.4
534                                         : 0.3 + Math.random() * 0.7).toFixed(3));
535      el.style.setProperty('--d', (glint ? 2.6 + Math.random() * 3.4
536                                         : 1.3 + Math.random() * 2.9).toFixed(2) + 's');
537      // negative delay => every star starts mid-cycle, nothing blinks in unison
538      el.style.setProperty('--delay', (-Math.random() * 6).toFixed(2) + 's');
539      frag.appendChild(el);
540    }
541
542    field.replaceChildren(frag);
543  }
544
545  build();
546
547  var t;
548  addEventListener('resize', function () {
549    clearTimeout(t);
550    t = setTimeout(build, 220);
551  }, { passive: true });
552
553  /* Only run the transmission while the section is on screen. */
554  if ('IntersectionObserver' in window) {
555    new IntersectionObserver(function (entries) {
556      entries.forEach(function (e) {
557        section.classList.toggle('is-live', e.isIntersecting);
558      });
559    }, { threshold: 0.12 }).observe(section);
560  } else {
561    section.classList.add('is-live');
562  }
563})();
564
565
566/* ---------------------------------------------------------------- */

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.