PageSourceSearch

https://verkhovskiy.ai/token-banner-widget.js

js verkhovskiy.ai collected 2026-10-05 14:46:48 UTC 11,108 bytes, 339 lines download raw bytes

1(function () {
2  "use strict";
3
4  // ─── Configuration ───────────────────────────────────────────────────────────
5  let TOKENS_PER_DAY = 60e12;   // 60 trillion tokens/day
6  let BLENDED_COST_PER_M = 0.50; // $0.50 per 1M tokens
7  const YEAR_START = new Date("2026-01-01T00:00:00Z");
8  const UPDATE_INTERVAL = 100;
9  const DATA_URL = "/data/token-monitor.json";
10  const LINK_URL = "/analytics/";
11
12  let tokensPerMs = TOKENS_PER_DAY / 86400 / 1000;
13  let dollarPerMs = (TOKENS_PER_DAY / 1e6 * BLENDED_COST_PER_M) / 86400 / 1000;
14
15  function applyConfig(data) {
16    if (data && data.tokensPerDay) {
17      TOKENS_PER_DAY = data.tokensPerDay;
18      tokensPerMs = TOKENS_PER_DAY / 86400 / 1000;
19    }
20    if (data && data.blendedCostPerM) {
21      BLENDED_COST_PER_M = data.blendedCostPerM;
22    }
23    dollarPerMs = (TOKENS_PER_DAY / 1e6 * BLENDED_COST_PER_M) / 86400 / 1000;
24  }
25
26  // ─── Helpers ─────────────────────────────────────────────────────────────────
27  function getTokensToday() {
28    const now = new Date();
29    const midnight = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
30    return (now - midnight) * tokensPerMs;
31  }
32  function getTokensYear() {
33    return (Date.now() - YEAR_START.getTime()) * tokensPerMs;
34  }
35  function getDollarToday() {
36    const now = new Date();
37    const midnight = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
38    return (now - midnight) * dollarPerMs;
39  }
40  function getDollarYear() {
41    return (Date.now() - YEAR_START.getTime()) * dollarPerMs;
42  }
43
44  function fmtTokens(n) {
45    if (n >= 1e15) return (n / 1e15).toFixed(2) + " Q";
46    if (n >= 1e12) return (n / 1e12).toFixed(2) + " T";
47    if (n >= 1e9)  return (n / 1e9).toFixed(1) + " B";
48    return Math.floor(n).toLocaleString("en-US");
49  }
50  function fmtDollar(n) {
51    if (n >= 1e9) return "$" + (n / 1e9).toFixed(2) + "B";
52    if (n >= 1e6) return "$" + (n / 1e6).toFixed(1) + "M";
53    return "$" + Math.floor(n).toLocaleString("en-US");
54  }
55
56  // ─── Styles ──────────────────────────────────────────────────────────────────
57  const STYLES = `
58    @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&display=swap');
59
60    .tkb-banner {
61      display: flex;
62      align-items: center;
63      justify-content: space-between;
64      gap: 8px;
65      background: #141820;
66      border: 1px solid #252c33;
67      border-radius: 10px;
68      padding: 10px 20px;
69      margin-bottom: 16px;
70      cursor: pointer;
71      text-decoration: none;
72      transition: border-color 0.2s, background 0.2s;
73      min-height: 56px;
74    }
75    .tkb-banner:hover {
76      border-color: #00e5a0;
77      background: #181e28;
78    }
79
80    .tkb-left {
81      display: flex;
82      align-items: center;
83      gap: 10px;
84      flex-shrink: 0;
85    }
86    .tkb-live {
87      display: flex;
88      align-items: center;
89      gap: 5px;
90    }
91    .tkb-live-dot {
92      width: 6px; height: 6px;
93      border-radius: 50%;
94      background: #00e5a0;
95      animation: tkb-pulse 2s ease-in-out infinite;
96    }
97    @keyframes tkb-pulse {
98      0%, 100% { opacity: 1; transform: scale(1); }
99      50% { opacity: 0.4; transform: scale(1.5); }
100    }
101    .tkb-live-text {
102      font-size: 10px;
103      font-weight: 600;
104      text-transform: uppercase;
105      letter-spacing: 0.08em;
106      color: #00e5a0;
107    }
108    .tkb-label {
109      font-size: 10px;
110      font-weight: 500;
111      letter-spacing: 0.05em;
112      text-transform: uppercase;
113      color: #6b7a8d;
114      white-space: nowrap;
115    }
116
117    .tkb-counters {
118      display: flex;
119      align-items: center;
120      gap: 0;
121      flex: 1;
122      justify-content: center;
123    }
124    .tkb-counter-item {
125      display: flex;
126      flex-direction: column;
127      align-items: center;
128      padding: 0 16px;
129      border-right: 1px solid #252c33;
130    }
131    .tkb-counter-item:last-child { border-right: none; }
132    .tkb-counter-title {
133      font-size: 9px;
134      font-weight: 500;
135      text-transform: uppercase;
136      letter-spacing: 0.06em;
137      color: #6b7a8d;
138      margin-bottom: 2px;
139      white-space: nowrap;
140    }
141    .tkb-counter-value {
142      font-family: 'JetBrains Mono', monospace;
143      font-size: 16px;
144      font-weight: 600;
145      white-space: nowrap;
146    }
147    .tkb-counter-value.cyan {
148      color: #00d4ff;
149      text-shadow: 0 0 8px rgba(0,212,255,0.35);
150    }
151    .tkb-counter-value.green {
152      color: #00e5a0;
153      text-shadow: 0 0 8px rgba(0,229,160,0.3);
154    }
155    .tkb-counter-value.gold {
156      color: #ffd700;
157      text-shadow: 0 0 8px rgba(255,215,0,0.35);
158    }
159
160    .tkb-arrow {
161      font-size: 14px;
162      color: #6b7a8d;
163      flex-shrink: 0;
164      transition: transform 0.2s, color 0.2s;
165    }
166    .tkb-banner:hover .tkb-arrow {
167      color: #00e5a0;
168      transform: translateX(3px);
169    }
170
171    /* Responsive */
172    @media (max-width: 900px) {
173      .tkb-label { display: none; }
174      .tkb-counter-item { padding: 0 10px; }
175      .tkb-counter-value { font-size: 14px; }
176    }
177    @media (max-width: 600px) {
178      .tkb-banner { flex-wrap: wrap; padding: 8px 12px; gap: 6px; }
179      .tkb-counters { flex-wrap: wrap; gap: 4px; }
180      .tkb-counter-item { border-right: none; padding: 2px 8px; }
181      .tkb-counter-value { font-size: 13px; }
182      .tkb-left { width: 100%; justify-content: space-between; }
183      .tkb-arrow { display: none; }
184    }
185  `;
186
187  // ─── Injection ───────────────────────────────────────────────────────────────
188  function findTargetGrid() {
189    // Look for the main grid container on the first screen
190    // Strategy: find heading containing "Главные события" or "Models Pulse"
191    // and walk up to the grid parent, then insert before it
192    const allEls = document.querySelectorAll("h2, h3, [class*='grid']");
193    
194    // Method 1: Find by heading text
195    for (const el of document.querySelectorAll("h2, h3, span, div")) {
196      const text = el.textContent.trim();
197      if (text === "Главные события" || text === "Key Events" || text === "Models Pulse") {
198        // Walk up to find a grid container
199        let parent = el.parentElement;
200        for (let i = 0; i < 8; i++) {
201          if (!parent) break;
202          if (parent.className && (
203            parent.className.includes("grid-cols") ||
204            parent.className.includes("grid ")
205          )) {
206            return parent;
207          }
208          parent = parent.parentElement;
209        }
210      }
211    }
212
213    // Method 2: Find any element with grid-cols-2 or grid-cols-3 class
214    const grids = document.querySelectorAll("[class*='grid-cols']");
215    if (grids.length > 0) return grids[0];
216
217    return null;
218  }
219
220  function inject() {
221    // Only inject on the main page
222    const path = window.location.pathname;
223    if (path !== "/" && path !== "/index.html" && path !== "") return;
224
225    // Don't double-inject
226    if (document.getElementById("tkb-banner")) return;
227
228    const grid = findTargetGrid();
229    if (!grid) return;
230
231    // Inject styles
232    if (!document.getElementById("tkb-styles")) {
233      const styleEl = document.createElement("style");
234      styleEl.id = "tkb-styles";
235      styleEl.textContent = STYLES;
236      document.head.appendChild(styleEl);
237    }
238
239    // Create banner
240    const banner = document.createElement("a");
241    banner.id = "tkb-banner";
242    banner.className = "tkb-banner";
243    banner.href = LINK_URL;
244    banner.innerHTML = `
245      <div class="tkb-left">
246        <div class="tkb-live">
247          <div class="tkb-live-dot"></div>
248          <span class="tkb-live-text">Live</span>
249        </div>
250        <span class="tkb-label">Global AI Tokens</span>
251      </div>
252      <div class="tkb-counters">
253        <div class="tkb-counter-item">
254          <div class="tkb-counter-title">Today</div>
255          <div class="tkb-counter-value cyan" id="tkb-tokens-today">—</div>
256        </div>
257        <div class="tkb-counter-item">
258          <div class="tkb-counter-title">This year</div>
259          <div class="tkb-counter-value green" id="tkb-tokens-year">—</div>
260        </div>
261        <div class="tkb-counter-item">
262          <div class="tkb-counter-title">$ Today</div>
263          <div class="tkb-counter-value gold" id="tkb-spend-today">—</div>
264        </div>
265        <div class="tkb-counter-item">
266          <div class="tkb-counter-title">$ This year</div>
267          <div class="tkb-counter-value gold" id="tkb-spend-year">—</div>
268        </div>
269      </div>
270      <div class="tkb-arrow">→</div>
271    `;
272
273    // Insert before the grid
274    grid.parentNode.insertBefore(banner, grid);
275
276    // Start counter animation
277    const elToday = document.getElementById("tkb-tokens-today");
278    const elYear  = document.getElementById("tkb-tokens-year");
279    const elSpendToday = document.getElementById("tkb-spend-today");
280    const elSpendYear  = document.getElementById("tkb-spend-year");
281
282    function update() {
283      elToday.textContent = fmtTokens(getTokensToday());
284      elYear.textContent  = fmtTokens(getTokensYear());
285      elSpendToday.textContent = fmtDollar(getDollarToday());
286      elSpendYear.textContent  = fmtDollar(getDollarYear());
287    }
288    update();
289    setInterval(update, UPDATE_INTERVAL);
290  }
291
292  // ─── Bootstrap ───────────────────────────────────────────────────────────────
293  function bootstrap() {
294    // Try to load dynamic config
295    fetch(DATA_URL).then(r => r.json()).then(data => {
296      applyConfig(data);
297    }).catch(() => {}).finally(() => {
298      waitAndInject();
299    });
300  }
301
302  function waitAndInject() {
303    // The SPA may not have rendered yet, use MutationObserver
304    if (findTargetGrid()) {
305      inject();
306      return;
307    }
308
309    let attempts = 0;
310    const maxAttempts = 80; // 8 seconds max
311    const observer = new MutationObserver(() => {
312      attempts++;
313      if (findTargetGrid()) {
314        observer.disconnect();
315        inject();
316      } else if (attempts >= maxAttempts) {
317        observer.disconnect();
318      }
319    });
320    observer.observe(document.body, { childList: true, subtree: true });
321
322    // Also try with a timer as fallback
323    const interval = setInterval(() => {
324      attempts++;
325      if (findTargetGrid()) {
326        clearInterval(interval);
327        inject();
328      } else if (attempts >= maxAttempts) {
329        clearInterval(interval);
330      }
331    }, 100);
332  }
333
334  if (document.readyState === "loading") {
335    document.addEventListener("DOMContentLoaded", bootstrap);
336  } else {
337    bootstrap();
338  }
339})();

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.