(function (global) {
var DEFAULTS = {
host: 'tcp://10.13.12.93:28960',
serverLabel: '[+] sexy Test',
mapName: 'campgrounds',
gameType: 'Clan Arena',
playerSlots: '6/9',
roundLimit: 9,
redTeam: ['Angel', 'Crash', 'Hunter'],
blueTeam: ['Mynx', 'Slash', 'Lucy'],
weapons: ['Railgun', 'Rocket Launcher', 'Plasma Gun', 'Lightning Gun', 'Shotgun'],
medals: ['IMPRESSIVE', 'EXCELLENT', 'REVENGE', 'MIDAIR'],
chatMessages: ['So much for foreplay...', 'We do not lose. We blend in.', 'The she-wolf reigns', 'Konnichiwa, campers', 'Not what I expected. Definitely not.', 'Just a flesh wound', 'Sayonara, babies', 'Oooooo! You like me. You really like me!', 'This is, like, so sweet', 'We are off to find the lizard', 'Do it again. But this time, with feeling.', 'Bizzzzaap!! Got me like a bug in a zap trap!', 'You flesh folk leak too much', 'Toodles', 'Great, premature eradication', '** winks and blows kisses **', 'Drinks are on me, boys!' ],
minDelay: 420,
maxDelay: 6900,
maxLines: 12,
pKill: 0.69,
pMedal: 0.1337,
spectators: [ 'anarki' ],
};
var colorizedNames = {
'anarki': 'anarki'
};
function colorizeSpectator(name) {
return colorizedNames[name.toLowerCase()] || name;
}
function buildDoc(cfg) {
var redRows = cfg.redTeam .map(function(p){ return '
0 ' + p + '
'; }).join('');
var blueRows = cfg.blueTeam.map(function(p){ return '0 ' + p + '
'; }).join('');
var label = cfg.serverLabel.split(' ');
var bracket = label[0] || '';
var cyan = label[1] || '';
var yellow = label.slice(2).join(' ') || '';
var css = [
'*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}',
'html,body{background:transparent;color:#ccc;font-family:"Courier New",Courier,monospace;font-size:12.5px;line-height:1.5;overflow:hidden}',
'.wrap{border:1px solid #333}',
'.header{padding:6px 12px 4px}',
'.titlebar{color:#ccc;margin-bottom:6px}',
'.infoline{display:grid;grid-template-columns:auto auto auto auto;margin-bottom:1px}',
'.teams{padding:8px 12px 4px;display:flex}',
'.team{width:50%}',
'.team-hdr{margin-bottom:3px}',
'.player{color:#fff;padding-left:2px}',
'.spectators{padding:4px 12px 2px;color:#ff0;font-weight:bold}',
'.divider{border:none;border-top:1px solid #444;margin:6px 0 0;display:block}',
'.feed-wrap{height:256px;overflow:hidden;padding:4px 12px;position:relative}',
'.fade{position:absolute;top:4px;left:0;right:0;height:30px;background:linear-gradient(to bottom,#0c0c0c,transparent);pointer-events:none;z-index:2}',
'.feed{display:flex;flex-direction:column}',
'.msg{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:0;transition:opacity .25s ease;line-height:1.6;display:block}',
'.msg.on{opacity:1}',
'.cmdline{border-top:1px solid #333;padding:4px 12px;display:flex;align-items:center;gap:4px;color:#ccc}',
'.cursor{display:inline-block;width:8px;height:14px;background:#ccc;vertical-align:middle;animation:blink 1s step-end infinite}',
'@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}',
'.cyan{color:#fff}.yellow{color:#0cc}.warmup{color:#f00}',
'.bold{color:#fff;font-weight:bold}',
'.rt{color:#f00;text-decoration:underline;font-weight:bold}',
'.bt{color:#09f;text-decoration:underline;font-weight:bold}',
'.score{color:#fff;font-weight:bold;margin-right:6px}',
'.ts{color:#ccc}.cr{color:#f00}.cb{color:#09f}.cp{color:#fff;font-weight:bold}',
'.cmt{color:#f0f;font-weight:bold}',
'.cst{color:#0c0;font-weight:bold}.css{color:#0f0}',
'.plain{color:#ccc}',
'.label{color:#ff0}',
].join('');
var html = [
'',
'',
'',
'',
'',
'
',
'
',
'
0RED TEAM
',
redRows,
'
',
'
',
'
0BLUE TEAM
',
blueRows,
'
',
'
',
'
Spectators: ' + cfg.spectators.map(colorizeSpectator).join(' ') + '
',
'
',
'
',
'
$
',
'
',
].join('');
/* inner script as a real function — no string escaping needed */
function iframeScript(cfg) {
var feed = document.getElementById('feed');
var timeEl = document.getElementById('qtime');
var rsEl = document.getElementById('rs');
var bsEl = document.getElementById('bs');
var lines = [], elapsed = 0, scores = { red: 0, blue: 0 };
function rand(a) { return a[Math.floor(Math.random() * a.length)]; }
function randInt(a,b){ return a + Math.floor(Math.random() * (b - a + 1)); }
function sp(c, t) { return '' + t + ''; }
function pad(n) { return n < 10 ? '0' + n : '' + n; }
function ts() {
var b = 33*60 + 34 + elapsed;
return '[' + pad(Math.floor(b/3600)) + ':' + pad(Math.floor((b%3600)/60)) + ':' + pad(b%60) + ']';
}
function kill() {
var rk = Math.random() < 0.5;
var k = rand(rk ? cfg.redTeam : cfg.blueTeam);
var v = rand(rk ? cfg.blueTeam : cfg.redTeam);
var w = rand(cfg.weapons);
var hp = randInt(25, 200);
if (Math.random() < 0.12) { if (rk) scores.red++; else scores.blue++; }
return sp('ts',ts()) + ' ' +
sp(rk?'cr':'cb', rk?'(RED)':'(BLUE)') + sp('cp', k) +
sp('plain', ' fragged ') +
sp(rk?'cb':'cr', rk?'(BLUE)':'(RED)') + sp('cp', v) +
sp('plain', ' with the ') +
(function(w){
var weaponColors = {
'Rocket Launcher':'#f33',
'Plasma Gun':'#f0f',
'Railgun':'#0f0',
'Lightning Gun':'#ff0',
'Shotgun':'#f60'
};
return '' + w + '';
})(w) + ' ' +
sp('chp', '(' + hp + ' HP)');
}
function medal() {
var r = Math.random() < 0.5;
return sp('ts',ts()) + ' ' +
sp('cmt', '[MEDAL]') + ' ' +
sp(r?'cr':'cb', r?'(RED)':'(BLUE)') +
sp('cp', rand(r ? cfg.redTeam : cfg.blueTeam)) +
sp('plain', ' got ') +
(function(m){
var medalColors = {
'EXCELLENT':'#ff0',
'MIDAIR':'#0f0',
'IMPRESSIVE':'#0cc',
'REVENGE':'#f00'
};
return '' + m + '';
})(rand(cfg.medals));
}
function say() {
var r = Math.random() < 0.5;
return sp('ts',ts()) + ' ' +
sp('cst', '[SAY]') + ' ' +
sp(r?'cr':'cb', r?'(RED)':'(BLUE)') +
sp('cp', rand(r ? cfg.redTeam : cfg.blueTeam)) +
': ' + sp('css', rand(cfg.chatMessages));
}
function addLine(h) {
var d = document.createElement('div');
d.className = 'msg';
d.innerHTML = h;
feed.appendChild(d);
lines.push(d);
requestAnimationFrame(function(){ d.classList.add('on'); });
if (lines.length > cfg.maxLines) lines.shift().remove();
rsEl.textContent = scores.red;
bsEl.textContent = scores.blue;
}
function schedule() {
setTimeout(function() {
var r = Math.random();
if (r < cfg.pKill) addLine(kill());
else if (r < cfg.pKill + cfg.pMedal) addLine(medal());
else addLine(say());
schedule();
}, randInt(cfg.minDelay, cfg.maxDelay));
}
addLine(sp('plain', '*** '));
addLine(sp('plain', '*** QL PyCon Version 0.8.1 starting ***'));
addLine(sp('plain', 'Stats stream connected - ready for game events'));
setInterval(function() {
elapsed++;
var m = Math.floor(elapsed / 60), s = elapsed % 60;
timeEl.textContent = m + ':' + (s < 10 ? '0' : '') + s;
}, 1000);
setTimeout(schedule, 900);
}
var scriptBody = '(' + iframeScript.toString() + ')(' + JSON.stringify(cfg) + ')';
return html + '' + scriptBody + '';
}
var instanceCount = 0;
function mount(root, cfg) {
var iframe = document.createElement('iframe');
iframe.style.cssText = 'width:100%;height:100%;border:none;display:block;';
iframe.setAttribute('scrolling', 'no');
iframe.setAttribute('title', 'Quake Live terminal');
root.appendChild(iframe);
var doc = iframe.contentDocument || iframe.contentWindow.document;
doc.open();
doc.write(buildDoc(cfg));
doc.close();
}
global.QuakeTerminal = {
init: function(selector, userConfig) {
var root = typeof selector === 'string'
? document.querySelector(selector)
: selector;
if (!root) { console.warn('QuakeTerminal: element not found —', selector); return; }
var cfg = {};
for (var k in DEFAULTS) cfg[k] = DEFAULTS[k];
for (var k in (userConfig || {})) cfg[k] = userConfig[k];
cfg._id = ++instanceCount;
var details = root.closest('details');
if (details && !details.open) {
details.addEventListener('toggle', function onToggle() {
if (details.open) {
details.removeEventListener('toggle', onToggle);
mount(root, cfg);
}
});
} else {
mount(root, cfg);
}
},
defaults: DEFAULTS,
};
}(window));