159 lines
6.3 KiB
JavaScript
159 lines
6.3 KiB
JavaScript
/* VYNDR embeddable widget — single self-contained file, ~3.5 KB minified.
|
|
*
|
|
* <script src="https://vyndr.app/widget.js" data-sport="nba" data-theme="dark"></script>
|
|
*
|
|
* Mounts a 300px card at the script tag's location, refreshes every 30
|
|
* minutes, links each grade to https://vyndr.app (new tab). Two themes:
|
|
* dark (default), light.
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
var SCRIPT = document.currentScript;
|
|
if (!SCRIPT) {
|
|
var all = document.getElementsByTagName('script');
|
|
for (var i = all.length - 1; i >= 0; i--) {
|
|
if (/widget\.js(\?|$)/.test(all[i].src)) { SCRIPT = all[i]; break; }
|
|
}
|
|
}
|
|
if (!SCRIPT) return;
|
|
|
|
var SPORT = (SCRIPT.getAttribute('data-sport') || 'nba').toLowerCase();
|
|
var THEME = (SCRIPT.getAttribute('data-theme') || 'dark').toLowerCase();
|
|
if (['nba', 'wnba', 'mlb'].indexOf(SPORT) < 0) SPORT = 'nba';
|
|
if (['dark', 'light'].indexOf(THEME) < 0) THEME = 'dark';
|
|
|
|
var API = (SCRIPT.getAttribute('data-api') || 'https://api.vyndr.app') + '/api/widget';
|
|
var REFRESH_MS = 30 * 60 * 1000;
|
|
|
|
// ── styles ─────────────────────────────────────────────────────────────
|
|
var PALETTE = THEME === 'light' ? {
|
|
bg: '#FFFFFF', border: '#E5E5EC', text: '#0E0E16',
|
|
textDim: '#4A4A5E', accent: '#0F3D2E',
|
|
} : {
|
|
bg: '#06060B', border: '#1E1E2E', text: '#E8E8F0',
|
|
textDim: '#7A7A8E', accent: '#00D4A0',
|
|
};
|
|
var GRADE_COLORS = {
|
|
'A+': '#00FFB8', 'A': '#00D4A0', 'A-': '#00D4A0',
|
|
'B+': '#4A9EFF', 'B': '#4A9EFF', 'B-': '#4A9EFF',
|
|
'C+': '#FFB347', 'C': '#FFB347', 'C-': '#FFB347',
|
|
'D': '#FF5252', 'F': '#FF5252',
|
|
};
|
|
|
|
function el(tag, css, text) {
|
|
var n = document.createElement(tag);
|
|
if (css) n.style.cssText = css;
|
|
if (text != null) n.textContent = text;
|
|
return n;
|
|
}
|
|
|
|
function gradeColor(g) { return GRADE_COLORS[g] || PALETTE.textDim; }
|
|
|
|
// ── mount ─────────────────────────────────────────────────────────────
|
|
var host = el('div', [
|
|
'box-sizing:border-box',
|
|
'width:300px',
|
|
'background:' + PALETTE.bg,
|
|
'color:' + PALETTE.text,
|
|
'border:1px solid ' + PALETTE.border,
|
|
'border-radius:14px',
|
|
'padding:16px',
|
|
'font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",sans-serif',
|
|
'font-size:14px',
|
|
'line-height:1.4',
|
|
].join(';') + ';');
|
|
host.setAttribute('data-vyndr-widget', SPORT);
|
|
|
|
var header = el('div', 'display:flex;justify-content:space-between;align-items:baseline;margin-bottom:10px;');
|
|
var brand = el('span', 'font-family:"IBM Plex Mono","JetBrains Mono","SF Mono",ui-monospace,monospace;font-weight:800;letter-spacing:2px;color:' + PALETTE.text + ';');
|
|
brand.appendChild(document.createTextNode('VYND'));
|
|
var r = el('span', 'color:' + PALETTE.accent + ';text-shadow:0 0 6px rgba(0,212,160,.45);', 'R');
|
|
brand.appendChild(r);
|
|
header.appendChild(brand);
|
|
header.appendChild(el('span', 'font-family:"IBM Plex Mono",monospace;font-size:10px;letter-spacing:2px;color:' + PALETTE.textDim + ';', 'TOP ' + SPORT.toUpperCase()));
|
|
host.appendChild(header);
|
|
|
|
var list = el('div', '');
|
|
host.appendChild(list);
|
|
|
|
var footer = el('div', 'margin-top:12px;font-family:"IBM Plex Mono",monospace;font-size:10px;letter-spacing:2px;color:' + PALETTE.textDim + ';text-align:center;');
|
|
var foot = el('a', 'color:inherit;text-decoration:none;');
|
|
foot.href = 'https://vyndr.app';
|
|
foot.target = '_blank';
|
|
foot.rel = 'noopener noreferrer';
|
|
foot.textContent = 'Powered by VYNDR · vyndr.app';
|
|
footer.appendChild(foot);
|
|
host.appendChild(footer);
|
|
|
|
if (SCRIPT.parentNode) SCRIPT.parentNode.insertBefore(host, SCRIPT);
|
|
|
|
// ── render ────────────────────────────────────────────────────────────
|
|
function row(g) {
|
|
var a = el('a', [
|
|
'display:flex',
|
|
'justify-content:space-between',
|
|
'align-items:center',
|
|
'padding:10px 12px',
|
|
'margin-bottom:6px',
|
|
'border-radius:8px',
|
|
'text-decoration:none',
|
|
'color:' + PALETTE.text,
|
|
'border:1px solid ' + PALETTE.border,
|
|
'background:' + (THEME === 'light' ? '#F8F8FC' : '#0E0E16'),
|
|
].join(';') + ';');
|
|
a.href = 'https://vyndr.app';
|
|
a.target = '_blank';
|
|
a.rel = 'noopener noreferrer';
|
|
|
|
var left = el('div', 'min-width:0;');
|
|
var name = el('div', 'font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;', String(g.player || ''));
|
|
var sub = el('div', 'font-family:"IBM Plex Mono",monospace;font-size:11px;color:' + PALETTE.textDim + ';',
|
|
[g.stat || '', String(g.direction || '').toUpperCase(), g.line ?? ''].join(' '));
|
|
left.appendChild(name);
|
|
left.appendChild(sub);
|
|
|
|
var grade = el('div', [
|
|
'font-family:"IBM Plex Mono",monospace',
|
|
'font-weight:800',
|
|
'font-size:22px',
|
|
'margin-left:8px',
|
|
'color:' + gradeColor(g.grade),
|
|
'text-shadow:0 0 6px ' + gradeColor(g.grade) + '55',
|
|
].join(';') + ';', String(g.grade || ''));
|
|
|
|
a.appendChild(left);
|
|
a.appendChild(grade);
|
|
return a;
|
|
}
|
|
|
|
function setError(msg) {
|
|
list.innerHTML = '';
|
|
var p = el('div', 'padding:10px 0;color:' + PALETTE.textDim + ';font-size:12px;', msg);
|
|
list.appendChild(p);
|
|
}
|
|
|
|
function fetchAndRender() {
|
|
var url = API + '?sport=' + encodeURIComponent(SPORT) + '&_=' + Date.now();
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('GET', url, true);
|
|
xhr.timeout = 8000;
|
|
xhr.onreadystatechange = function () {
|
|
if (xhr.readyState !== 4) return;
|
|
if (xhr.status < 200 || xhr.status >= 300) return setError('Signal unavailable. Try later.');
|
|
try {
|
|
var data = JSON.parse(xhr.responseText);
|
|
list.innerHTML = '';
|
|
var props = (data && data.props) || [];
|
|
if (!props.length) return setError('No grades on the slate yet.');
|
|
for (var i = 0; i < props.length; i++) list.appendChild(row(props[i]));
|
|
} catch (e) { setError('Could not parse response.'); }
|
|
};
|
|
xhr.ontimeout = function () { setError('Signal timed out.'); };
|
|
xhr.send();
|
|
}
|
|
|
|
fetchAndRender();
|
|
setInterval(fetchAndRender, REFRESH_MS);
|
|
})();
|