PageSourceSearch

https://worldswave.com/themes/wowonder/javascript/ww-stats-col.js?v=1788927141

js worldswave.com collected 2026-10-02 15:34:13 UTC 25,283 bytes, 462 lines download raw bytes

1/* WWSTATCOL: a fourth column to the right of the feed and the existing sidebar.
2   Injected here rather than in the .phtml because the row is assembled across
3   several templates (home/content.phtml opens it, sidebar/content.phtml floats the
4   right column inside a full-width wrapper) - adding a sibling server-side means
5   editing all of them, while the card's contents are fetched over AJAX anyway.
6   First card: Weekly Stats, cycling through five metrics with week-on-week change. */
7(function () {
8  if (window.__wwStatCol) { return; }
9  window.__wwStatCol = 1;
10
11  var ROT = 4500;
12  /* Module level, so a rebuilt column cannot leave the old card's rotation
13     ticking against a detached node. */
14  var statTimer = null;
15
16  function css() {
17    if (document.getElementById('ww-statcol-css')) { return; }
18    var s = document.createElement('style');
19    s.id = 'ww-statcol-css';
20    s.textContent = [
21      '.ww-statcol{float:left;padding:0 8px 0 0;min-height:1px}',
22      '@media (min-width:1280px){html body .row.wo_user_profile > .col-md-8{width:52% !important;zoom:.9}html body .row.wo_user_profile > .col-md-4{width:31% !important}html body .row.wo_user_profile > .ww-statcol{width:17% !important;padding:0 0 0 8px}}',
23      '.ww-wd-btns{display:flex;gap:6px}',
24      '.ww-wd-btns a{flex:1 1 0;min-width:0;font-size:11px;font-weight:600;line-height:1;white-space:nowrap;padding:7px 4px;border-radius:7px;text-decoration:none;text-align:center;transition:background .15s,color .15s,border-color .15s}',
25      '.ww-wd-dash{color:#ff7a7a;background:linear-gradient(180deg,rgba(255,45,45,.22),rgba(255,45,45,.12));border:1px solid rgba(255,45,45,.38)}',
26      '.ww-wd-dash:hover{background:linear-gradient(180deg,rgba(255,45,45,.34),rgba(255,45,45,.20));border-color:rgba(255,45,45,.55);color:#ff9d9d;text-decoration:none}',
27      '.ww-wd-studio{color:#dcdce4;background:linear-gradient(180deg,rgba(255,255,255,.16),rgba(255,255,255,.07));border:1px solid rgba(255,255,255,.22)}',
28      '.ww-wd-studio:hover{background:linear-gradient(180deg,rgba(255,255,255,.26),rgba(255,255,255,.13));border-color:rgba(255,255,255,.34);color:#fff;text-decoration:none}',
29      '.ww-upcard{background:linear-gradient(180deg,rgba(245,197,24,.20),rgba(245,197,24,.07));border:1px solid rgba(245,197,24,.42);border-radius:var(--ww-card-radius,12px);box-shadow:var(--ww-card-shadow,0 1px 2px rgba(0,0,0,.45));padding:11px 11px 12px;margin-bottom:14px;display:none}',
30      '.ww-upcard.on{display:block}',
31      '.ww-upcard h4{margin:0 0 8px;font-size:11px;font-weight:700;letter-spacing:.3px;text-transform:uppercase;color:#f7cf3a}',
32      '.ww-up-thumb{display:block;width:calc(100% + 22px);max-width:none;margin:0 -11px;aspect-ratio:16/9;height:auto;object-fit:cover;border-radius:0;border-top:1px solid rgba(245,197,24,.42);border-bottom:1px solid rgba(245,197,24,.42);background:rgba(0,0,0,.4)}',
33      '.ww-up-title{margin:9px 0 3px;font-size:11px;font-weight:600;line-height:1.3;color:#f2f2f5;text-decoration:none;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}',
34      '.ww-up-title:hover{color:#fff;text-decoration:none}',
35      '.ww-up-when{display:block;font-size:10.5px;font-weight:600;color:#f7cf3a}',
36      '.ww-up-list{display:flex;flex-direction:column;gap:14px;margin:0 0 12px}',
37      '.ww-up-item{display:block;text-decoration:none}',
38      '.ww-up-item:hover b{color:#fff}',
39      '.ww-up-item img{display:block;width:calc(100% + 22px);max-width:none;margin:0 -11px;aspect-ratio:16/9;height:auto;object-fit:cover;border-radius:0;border-top:1px solid rgba(245,197,24,.42);border-bottom:1px solid rgba(245,197,24,.42);background:rgba(0,0,0,.4)}',
40      '.ww-up-meta{display:block;min-width:0;margin-top:7px}',
41      '.ww-up-meta b{font-size:11px;font-weight:600;line-height:1.3;color:#f2f2f5;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}',
42      '.ww-up-meta span{display:block;margin-top:3px;font-size:10px;font-weight:600;color:#f7cf3a}',
43      '.ww-up-none{font-size:10.5px;color:#d8cfa8;margin:0 0 10px}',
44      '.ww-up-btn{display:block;text-align:center;font-size:11px;font-weight:600;line-height:1;padding:7px 4px;border-radius:7px;text-decoration:none;color:#f7cf3a;background:linear-gradient(180deg,rgba(245,197,24,.22),rgba(245,197,24,.10));border:1px solid rgba(245,197,24,.42);transition:background .15s,color .15s,border-color .15s}',
45      '.ww-up-btn:hover{background:linear-gradient(180deg,rgba(245,197,24,.34),rgba(245,197,24,.18));color:#ffdf6b;border-color:rgba(245,197,24,.6);text-decoration:none}',
46      '.ww-statcard{background:var(--ww-card-bg,rgba(20,20,20,.6));border:1px solid var(--ww-card-line,rgba(255,255,255,.1));border-radius:var(--ww-card-radius,12px);box-shadow:var(--ww-card-shadow,0 1px 2px rgba(0,0,0,.45));overflow:hidden}',
47      '.ww-statcard-h{display:block;padding:9px 10px;border-bottom:1px solid rgba(255,255,255,.07);color:#e8e8e8}',
48      '.ww-stat-row{display:flex;align-items:center;justify-content:space-between;gap:5px}',
49      '.ww-stat-row b{font-size:11.5px;font-weight:700;white-space:nowrap;letter-spacing:-.2px}',
50      '.ww-statcard-h .ww-sub{display:block;margin-top:2px;white-space:nowrap}',
51      '.ww-stat-more{flex:0 0 auto;font-size:9.5px;font-weight:600;line-height:1;white-space:nowrap;text-decoration:none;color:#ff7a7a;background:linear-gradient(180deg,rgba(255,45,45,.22),rgba(255,45,45,.12));border:1px solid rgba(255,45,45,.38);border-radius:7px;padding:5px 5px;letter-spacing:-.2px;transition:background .15s,color .15s,border-color .15s}',
52      '.ww-stat-more:hover{background:linear-gradient(180deg,rgba(255,45,45,.34),rgba(255,45,45,.20));border-color:rgba(255,45,45,.55);color:#ff9d9d;text-decoration:none}',
53      '.ww-statcard-h .ww-sub{font-size:11px;font-weight:500;color:#8a8a8a}',
54      '.ww-stat-body{position:relative;padding:16px 14px 12px;min-height:132px;text-align:center}',
55      '.ww-stat-slide{display:none}',
56      '.ww-stat-slide.on{display:block}',
57      '.ww-stat-lab{font-size:12px;letter-spacing:.6px;text-transform:uppercase;color:#8a8a8a;margin-bottom:6px}',
58      '.ww-stat-val{font-size:30px;font-weight:800;color:#fff;line-height:1.1}',
59      '.ww-stat-chg{display:inline-flex;align-items:center;gap:5px;margin-top:9px;font-size:12px;font-weight:700;padding:3px 9px;border-radius:20px}',
60      '.ww-stat-chg.up{color:#4caf50;background:rgba(76,175,80,.12)}',
61      '.ww-stat-chg.down{color:#e53935;background:rgba(229,57,53,.12)}',
62      '.ww-stat-chg.flat{color:#8a8a8a;background:rgba(255,255,255,.06)}',
63      '.ww-stat-prev{margin-top:7px;font-size:11px;color:#7a7a7a}',
64      '.ww-stat-dots{display:flex;justify-content:center;gap:6px;padding:4px 0 10px}',
65      '.ww-stat-dots i{width:6px;height:6px;border-radius:50%;background:rgba(255,255,255,.22);cursor:pointer;transition:background .15s}',
66      '.ww-stat-dots i.on{background:#e53935}',
67      '.ww-stat-empty{color:#8a8a8a;font-size:12px;padding:16px 0}',
68      '.ww-folcard{display:none;margin-top:14px}',
69      '.ww-folcard.on{display:block}',
70      '.ww-fol-body{padding:5px 0 1px}',
71      '.ww-fol-page{display:none}',
72      '.ww-fol-page.on{display:block}',
73      '.ww-fol-item{display:flex;align-items:center;gap:8px;padding:5px 10px;text-decoration:none}',
74      '.ww-fol-item:hover{background:rgba(255,255,255,.05);text-decoration:none}',
75      '.ww-fol-av{position:relative;flex:0 0 auto;width:28px;height:28px}',
76      '.ww-fol-av img{display:block;width:28px;height:28px;border-radius:50%;object-fit:cover;background:rgba(255,255,255,.06)}',
77      '.ww-fol-meta{flex:1 1 auto;min-width:0}',
78      '.ww-fol-line{display:flex;align-items:center;min-width:0;font-size:11.5px;font-weight:600;line-height:1.25;color:#e8e8e8}',
79      '.ww-fol-line > *{flex:0 0 auto}',
80      '.ww-fol-nm{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
81      '.ww-fol-tags{display:inline-flex;align-items:center}',
82      '.ww-folcard span.ww-role-badge{font-size:8px;padding:0 3px;line-height:12px;margin-right:4px;letter-spacing:0}',
83      '.ww-folcard .ww-live-badge{font-size:8px;padding:1px 4px;margin-left:4px}',
84      '.ww-folcard svg.ww-live-check{width:11px;height:11px;margin-left:3px}',
85      '.ww-fol-stat{display:flex;align-items:center;gap:5px;margin-top:2px;font-size:10px;font-weight:600;line-height:1;white-space:nowrap}',
86      '.ww-fol-stat i{flex:0 0 auto;font-size:11px;line-height:1}',
87      '.ww-fol-empty{padding:14px 10px;text-align:center;color:#8a8a8a;font-size:12px}',
88      '.ww-fol-dots{display:flex;justify-content:center;gap:6px;padding:6px 0 10px}',
89      '.ww-fol-dots:empty{display:none}',
90      '.ww-fol-dots i{width:6px;height:6px;border-radius:50%;background:rgba(255,255,255,.22);cursor:pointer;transition:background .15s}',
91      '.ww-fol-dots i.on{background:#e53935}'
92    ].join('\n');
93    document.head.appendChild(s);
94  }
95
96  function esc(t) { return String(t == null ? '' : t).replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; }); }
97
98  function nfmt(n) {
99    n = Number(n) || 0;
100    if (n >= 1000000) { return (n / 1000000).toFixed(n % 1000000 === 0 ? 0 : 1) + 'M'; }
101    if (n >= 1000) { return (n / 1000).toFixed(n % 1000 === 0 ? 0 : 1) + 'K'; }
102    return (Math.round(n * 10) / 10).toString();
103  }
104
105  function render(host, rows) {
106    if (!rows || !rows.length) {
107      host.innerHTML = '<div class="ww-stat-empty">No activity yet this week.</div>';
108      return;
109    }
110    var body = '', dots = '';
111    for (var i = 0; i < rows.length; i++) {
112      var r = rows[i];
113      var cls = 'flat', arrow = '&middot;', txt = 'no change';
114      if (r.pct === null || r.pct === undefined) { txt = 'no data last week'; }
115      else if (r.pct > 0) { cls = 'up';   arrow = '&#9650;'; txt = r.pct + '%'; }
116      else if (r.pct < 0) { cls = 'down'; arrow = '&#9660;'; txt = Math.abs(r.pct) + '%'; }
117      else { txt = '0%'; }
118      body += '<div class="ww-stat-slide' + (i === 0 ? ' on' : '') + '">' +
119                '<div class="ww-stat-lab">' + esc(r.label) + '</div>' +
120                '<div class="ww-stat-val">' + nfmt(r.value) + '</div>' +
121                '<div class="ww-stat-chg ' + cls + '">' + arrow + ' ' + txt + '</div>' +
122                '<div class="ww-stat-prev">' + nfmt(r.prev) + ' last week</div>' +
123              '</div>';
124      dots += '<i data-i="' + i + '"' + (i === 0 ? ' class="on"' : '') + '></i>';
125    }
126    host.innerHTML = body;
127    var dotWrap = host.parentNode.querySelector('.ww-stat-dots');
128    dotWrap.innerHTML = dots;
129
130    var at = 0;
131    clearInterval(statTimer);
132    function show(n) {
133      var slides = host.querySelectorAll('.ww-stat-slide');
134      var ds = dotWrap.querySelectorAll('i');
135      at = (n + slides.length) % slides.length;
136      for (var k = 0; k < slides.length; k++) {
137        slides[k].className = 'ww-stat-slide' + (k === at ? ' on' : '');
138        if (ds[k]) { ds[k].className = (k === at ? 'on' : ''); }
139      }
140    }
141    function play() { clearInterval(statTimer); statTimer = setInterval(function () { show(at + 1); }, ROT); }
142
143    dotWrap.addEventListener('click', function (e) {
144      var d = e.target.getAttribute ? e.target.getAttribute('data-i') : null;
145      if (d === null) { return; }
146      show(parseInt(d, 10)); play();
147    });
148    var card = host.closest ? host.closest('.ww-statcard') : null;
149    if (card) {
150      card.addEventListener('mouseenter', function () { clearInterval(statTimer); });
151      card.addEventListener('mouseleave', play);
152    }
153    play();
154  }
155
156
157  function wwUpEsc(s) {
158    return String(s == null ? "" : s).replace(/[&<>"]/g, function (c) {
159      return { "&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;" }[c];
160    });
161  }
162
163  function wwUpWhen(ts) {
164    var d = new Date(ts * 1000);
165    var mo = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][d.getMonth()];
166    var h = d.getHours(), ap = h >= 12 ? "PM" : "AM";
167    h = h % 12; if (!h) { h = 12; }
168    var mi = d.getMinutes(); mi = mi < 10 ? "0" + mi : "" + mi;
169    return mo + " " + d.getDate() + ", " + h + ":" + mi + " " + ap;
170  }
171
172  function wwUpcoming() {
173    var host = document.getElementById("ww-upcard");
174    if (!host) { return; }
175    var x = new XMLHttpRequest();
176    x.open("GET", "/requests.php" + "?f=ww_upcoming", true);
177    x.setRequestHeader("X-Requested-With", "XMLHttpRequest");
178    x.onload = function () {
179      var j;
180      try { j = JSON.parse(x.responseText); } catch (e) { return; }
181      if (!j || j.status !== 200) { return; }
182      var h = "", i, o;
183      if (j.mine) {
184        h = "<h4>Upcoming Stream</h4>" +
185            "<a href=\"/livenow\"><img class=\"ww-up-thumb\" src=\"" + wwUpEsc(j.mine.thumb) + "\" alt=\"\" onerror=\"this.style.opacity=.25\"></a>" +
186            "<a class=\"ww-up-title\" href=\"/livenow\">" + wwUpEsc(j.mine.title) + "</a>" +
187            "<span class=\"ww-up-when\">" + wwUpWhen(j.mine.at) + "</span>";
188      } else if (j.others && j.others.length) {
189        h = "<h4>Upcoming Streams</h4><div class=\"ww-up-list\">";
190        for (i = 0; i < j.others.length; i++) {
191          o = j.others[i];
192          h += "<a class=\"ww-up-item\" href=\"/livenow\">" +
193               "<img src=\"" + wwUpEsc(o.thumb) + "\" alt=\"\" onerror=\"this.style.opacity=.25\">" +
194               "<span class=\"ww-up-meta\"><b>" + wwUpEsc(o.title) + "</b><span>" + wwUpWhen(o.at) +
195               (o.user ? " &middot;
195 " + wwUpEsc(o.user) : "") + "</span></span></a>";
196        }
197        h += "</div><a class=\"ww-up-btn\" href=\"/schedule-stream\">Schedule Stream</a>";
198      } else {
199        h = "<h4>Upcoming Streams</h4><div class=\"ww-up-none\">Nothing scheduled yet.</div>" +
200            "<a class=\"ww-up-btn\" href=\"/schedule-stream\">Schedule Stream</a>";
201      }
202      host.innerHTML = h;
203      host.className = "ww-upcard on";
204    };
205    x.send();
206  }
207
208  /* ---------------------------------------------------------------- Following card
209     Everyone the viewer follows, five at a time, paging every 5s. The row markup is
210     shaped so ww-role-badges.js decorates it for free: the anchor points at /username
211     and carries a .sidebar-listed-user-name, which is exactly what its profile-link
212     pass looks for, so staff badges stay defined in one place. The verified tick and
213     the LIVE badge are ours because that pass only reads usernames. */
214  var FOL_PER = 5, FOL_ROT = 5000, FOL_REFRESH = 120000;
215  var FOL_COLOR = { live: '#ff3b30', idle: '#ffc107', online: '#60d465', offline: '#CDCDCD' };
216  var FOL_LABEL = { live: 'Live', idle: 'Idle', online: 'Online', offline: 'Offline' };
217  var FOL_ICON  = { android: 'fa-android', ios: 'fa-apple', desktop: 'fa-desktop' };
218  var FOL_DEV   = { android: 'Android', ios: 'iPhone', desktop: 'Web' };
219  var FOL_CHECK = '<svg class="ww-live-check" viewBox="0 0 24 24">' +
220                    '<circle cx="12" cy="12" r="11" fill="#1d9bf0"></circle>' +
221                    '<path d="M17.2 9.1l-6 6-3-3 1.4-1.4 1.6 1.6 4.6-4.6z" fill="#fff"></path>' +
222                  '</svg>';
223
224  var folTimer = null, folRefresh = null, folAt = 0, folVer = null;
225
226  function folVerified(id) {
227    if (folVer === null) {
228      folVer = {};
229      var v = window.WW_VERIFIED_IDS || [];
230      for (var i = 0; i < v.length; i++) { folVer[String(v[i])] = 1; }
231    }
232    return folVer[String(id)] === 1;
233  }
234
235  function folRow(u) {
236    var st  = FOL_LABEL[u.state] ? u.state : 'offline';
237    var dev = FOL_ICON[u.device] ? u.device : '';
238    var txt = FOL_LABEL[st] + (dev ? ' \u00b7 ' + FOL_DEV[dev] : '');
239    return '<a class="ww-fol-item" href="/' + encodeURIComponent(u.user) + '" title="' + esc(u.name) + ' \u2014 ' + txt + '">' +
240             '<span class="ww-fol-av' + (st === 'live' ? ' ww-live-ring' : '') + '">' +
241               '<img src="' + esc(u.avatar) + '" alt="" onerror="this.style.visibility=\'hidden\'">' +
242             '</span>' +
243             '<span class="ww-fol-meta">' +
244               '<span class="ww-fol-line sidebar-listed-user-name">' +
245                 '<span class="ww-fol-nm">' + esc(u.name) + '</span>' +
246                 '<span class="ww-fol-tags">' +
247                   (folVerified(u.id) ? FOL_CHECK : '') +
248                   (st === 'live' ? '<span class="ww-live-badge" title="Live now">LIVE</span>' : '') +
249                 '</span>' +
250               '</span>' +
251               '<span class="ww-fol-stat" style="color:' + FOL_COLOR[st] + '">' +
252                 (dev ? '<i class="fa ' + FOL_ICON[dev] + '"></i>' : '') +
253                 '<span>' + txt + '</span>' +
254               '</span>' +
255             '</span>' +
256           '</a>';
257  }
258
259  function folRender(host, users) {
260    var pages = [], i, k;
261    for (i = 0; i < users.length; i += FOL_PER) { pages.push(users.slice(i, i + FOL_PER)); }
262
263    var body = '', dots = '';
264    for (i = 0; i < pages.length; i++) {
265      var rows = '';
266      for (k = 0; k < pages[i].length; k++) { rows += folRow(pages[i][k]); }
267      body += '<div class="ww-fol-page">' + rows + '</div>';
268      dots += '<i data-i="' + i + '"></i>';
269    }
270    host.innerHTML = body;
271
272    var dotWrap = host.parentNode.querySelector('.ww-fol-dots');
273    dotWrap.innerHTML = (pages.length > 1) ? dots : '';
274
275    clearInterval(folTimer);
276    if (folAt >= pages.length) { folAt = 0; }   /* a refresh may have shortened the list */
277
278    function show(n) {
279      var slides = host.querySelectorAll('.ww-fol-page');
280      var ds = dotWrap.querySelectorAll('i');
281      folAt = (n + slides.length) % slides.length;
282      for (var j = 0; j < slides.length; j++) {
283        slides[j].className = 'ww-fol-page' + (j === folAt ? ' on' : '');
284        if (ds[j]) { ds[j].className = (j === folAt ? 'on' : ''); }
285      }
286    }
287    function play() {
288      clearInterval(folTimer);
289      if (pages.length < 2) { return; }
290      folTimer = setInterval(function () { show(folAt + 1); }, FOL_ROT);
291    }
292    show(folAt);
293
294    if (!dotWrap.getAttribute('data-ww-bound')) {
295      dotWrap.setAttribute('data-ww-bound', '1');
296      dotWrap.addEventListener('click', function (e) {
297        var d = e.target.getAttribute ? e.target.getAttribute('data-i') : null;
298        if (d === null) { return; }
299        show(parseInt(d, 10)); play();
300      });
301      var card = document.getElementById('ww-folcard');
302      if (card) {
303        card.addEventListener('mouseenter', function () { clearInterval(folTimer); });
304        card.addEventListener('mouseleave', play);
305      }
306    }
307    play();
308  }
309
310  function wwFollowing() {
311    var card = document.getElementById('ww-folcard');
312    if (!card) { return; }
313    var body = card.querySelector('.ww-fol-body');
314    var x = new XMLHttpRequest();
315    x.open('GET', '/requests.php?f=ww_following', true);
316    x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
317    x.onload = function () {
318      var j;
319      try { j = JSON.parse(x.responseText); } catch (e) { return; }
320      if (!j || j.status !== 200 || !j.users) { return; }
321      /* An empty list is a real state now that offline rows are dropped, and a refresh
322         can land on it - so empty means hide the card, not leave the old list standing. */
323      if (!j.users.length) {
324        clearInterval(folTimer);
325        folTimer = null;
326        card.className = 'ww-statcard ww-folcard';
327        return;
328      }
329      var all = card.querySelector('#ww-fol-all');
330      if (all && j.me) { all.setAttribute('href', '/' + encodeURIComponent(j.me) + '/following'); }
331      var sub = card.querySelector('#ww-fol-sub');
332      if (sub) { sub.innerHTML = nfmt(j.online) + ' online now'; }
333      folRender(body, j.users);
334      card.className = 'ww-statcard ww-folcard on';
335    };
336    x.send();
337  }
338
339  function build() {
340    /* members only - <body> carries ww-guest when logged out */
341    if (document.body && document.body.classList.contains('ww-guest')) { return; }
342
343    var row = wwStatMount();
344    if (!row) { return; }
345    if (row.querySelector('.ww-statcol')) { return; }
346
347    css();
348    var col = document.createElement('div');
349    col.className = 'ww-statcol';
350    col.innerHTML =
351      /* WW-WDCARD-REMOVED */
352      '<div class="ww-upcard" id="ww-upcard"></div>' +
353      '<div class="ww-statcard">' +
354        '<div class="ww-statcard-h">' +
355          '<div class="ww-stat-row"><b>Weekly Stats</b><a class="ww-stat-more" href="/dashboard?page=stats-analytics">More Stats</a></div>' +
356          '<span class="ww-sub">vs last week</span>' +
357        '</div>' +
358        '<div class="ww-stat-body"><div class="ww-stat-empty">Loading&hellip;</div></div>' +
359        '<div class="ww-stat-dots"></div>' +
360      '</div>' +
361      '<div class="ww-statcard ww-folcard" id="ww-folcard">' +
362        '<div class="ww-statcard-h">' +
363          '<div class="ww-stat-row"><b>Following</b><a class="ww-stat-more" id="ww-fol-all" href="/">View All</a></div>' +
364          '<span class="ww-sub" id="ww-fol-sub">Loading&hellip;</span>' +
365        '</div>' +
366        '<div class="ww-fol-body"><div class="ww-fol-empty">Loading&hellip;</div></div>' +
367        '<div class="ww-fol-dots"></div>' +
368      '</div>';
369    row.appendChild(col);
370    wwUpcoming();
371    wwFollowing();
372    /* One refresh loop for the life of the page - presence goes stale after 2 min -
373       even if the column itself gets rebuilt underneath it. */
374    if (folRefresh === null) { folRefresh = setInterval(wwFollowing, FOL_REFRESH); }
375
376    var body = col.querySelector('.ww-stat-body');
377    var xhr = new XMLHttpRequest();
378    xhr.open('GET', '/requests.php?f=ww_weekly_stats', true);
379    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
380    xhr.onload = function () {
381      try {
382        var j = JSON.parse(xhr.responseText);
383        render(body, j && j.data);
384      } catch (e) {
385        body.innerHTML = '<div class="ww-stat-empty">Stats unavailable.</div>';
386      }
387    };
388    xhr.onerror = function () { body.innerHTML = '<div class="ww-stat-empty">Stats unavailable.</div>'; };
389    xhr.send();
390  }
391
392  /* WWSTATCOL-BOOT: the column used to be built on DOMContentLoaded and window load and
393     nowhere else, so a single miss was permanent - if the feed row was not in place at
394     either of those two instants, nothing ever looked again and the column stayed gone
395     until the user reloaded by hand. Keep trying on a bounded schedule instead, and stop
396     the moment the column is up. */
397  var BOOT_EVERY = 400, BOOT_FOR = 20000;
398  var bootUntil = 0, bootTimer = null;
399
400  /* WWSTATCOL-MOUNT: the single answer to "where does the column go on the page
401     as it stands right now".
402       - waves supplies its own host, because that page has no feed row at all
403         (the stage is a fixed full-viewport overlay)
404       - everywhere else, anchor on the feed column and walk up to its row rather
405         than taking the FIRST .row in the container: a header ad or promo above
406         the layout row used to win that query and the column was skipped
407       - profile pages resolve to null on purpose, so the timeline keeps the width */
408  function wwStatMount() {
409    var h = document.getElementById('ww-waves-statcol');
410    if (h) { return h; }
411    var mid = document.querySelector('.content-container .row .col-md-7.middlecol');
412    if (!mid) { return null; }
413    return mid.closest ? mid.closest('.row') : mid.parentNode;
414  }
415
416  /* WWSTATCOL-SPA: ask whether THIS page's mount holds a column, not whether one
417     exists anywhere in the document. The old global check is what made the column
418     miss when you clicked Waves in the sidebar: the site navigates by ajax, so the
419     home feed's column was still in memory when build() was asked again, built()
420     said "yes" and the boot loop shut down for the life of the tab. A hard load of
421     the same URL worked, which is exactly the "only when I click it from the
422     sidebar" shape of the bug. */
423  function built() {
424    var m = wwStatMount();
425    return !!(m && m.querySelector('.ww-statcol'));
426  }
427
428  function kick() {
429    build();
430    if (built()) {
431      if (bootTimer) { clearInterval(bootTimer); bootTimer = null; }
432      return;
433    }
434    bootUntil = Date.now() + BOOT_FOR;
435    if (bootTimer) { return; }
436    bootTimer = setInterval(function () {
437      build();
438      if (built() || Date.now() > bootUntil) { clearInterval(bootTimer); bootTimer = null; }
439    }, BOOT_EVERY);
440  }
441
442  if (document.readyState === 'loading') {
443    document.addEventListener('DOMContentLoaded', kick);
444  } else { kick(); }
445  window.addEventListener('load', kick);
446
447  /* WWSTATCOL-SPA: kick again whenever the app navigates without a page load.
448     In-app links go through pushState, so wrapping that (plus popstate for
449     back/forward) covers every route change. kick() no-ops when this page's mount
450     already holds a column, and it brings its own retry loop, which absorbs the
451     gap between the URL changing and the new content actually landing. */
452  ['pushState', 'replaceState'].forEach(function (m) {
453    var orig = history[m];
454    if (typeof orig !== 'function') { return; }
455    history[m] = function () {
456      var r = orig.apply(this, arguments);
457      try { setTimeout(kick, 0); } catch (e) {}
458      return r;
459    };
460  });
461  window.addEventListener('popstate', function () { setTimeout(kick, 0); });
462})();

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.