@@ -9,9 +9,6 @@ CREATE TABLE questions ( | |||
total_correct int, | |||
question text UNIQUE, | |||
answer text, | |||
bonus_slot_1 text, | |||
bonus_slot_2 text, | |||
bonus_slot_3 text, | |||
answer_a_total int, | |||
answer_b_total int, | |||
answer_c_total int, | |||
@@ -100,6 +100,7 @@ | |||
tr.insertCell().appendChild(document.createTextNode(data.active.active_question)); | |||
// Bonus Slots | |||
/* | |||
tr = tbody.insertRow(); | |||
tr.insertCell().appendChild(document.createTextNode("First to Answer")); | |||
tr.insertCell().appendChild(document.createTextNode(data.active.bonus_slot_1 || "AVAILABLE!")); | |||
@@ -109,6 +110,7 @@ | |||
tr = tbody.insertRow(); | |||
tr.insertCell().appendChild(document.createTextNode("Third to Answer")); | |||
tr.insertCell().appendChild(document.createTextNode(data.active.bonus_slot_3 || "AVAILABLE!")); | |||
*/ | |||
// Percents | |||
if(data.active.answer_percents) { | |||
@@ -14,7 +14,7 @@ pool.on('error', (err, client) => { | |||
}); | |||
const config = { | |||
query_create_question: "INSERT INTO questions(question, answer, total_responses, total_valid, total_correct, bonus_slot_1, bonus_slot_2, bonus_slot_3, answer_a_total, answer_b_total, answer_c_total, answer_d_total, answer_a, answer_b, answer_c, answer_d, ended_at) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING id", | |||
query_create_question: "INSERT INTO questions(question, answer, total_responses, total_valid, total_correct, answer_a_total, answer_b_total, answer_c_total, answer_d_total, answer_a, answer_b, answer_c, answer_d, ended_at) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING id", | |||
query_create_viewer: "INSERT INTO viewers(username, points) VALUES ($1, $2) ON CONFLICT (username) DO UPDATE SET points = viewers.points + EXCLUDED.points RETURNING id, username", | |||
query_create_answer: "INSERT INTO answers(viewer_id, question_id, answer, points) VALUES ($1, $2, $3, $4) RETURNING id", | |||
@@ -95,9 +95,6 @@ var self = module.exports = { | |||
q.total_responses, | |||
q.total_valid, | |||
q.total_correct, | |||
q.bonus_slot_1, | |||
q.bonus_slot_2, | |||
q.bonus_slot_3, | |||
q.answer_totals['A'] || null, | |||
q.answer_totals['B'] || null, | |||
q.answer_totals['C'] || null, | |||
@@ -1,7 +1,48 @@ | |||
let config = { | |||
valid_answers: ['A', 'B', 'C', 'D'], | |||
standard_points: 20, | |||
bonus_slots: { | |||
one: { | |||
max: 5, | |||
points: 15, | |||
viewers: [], | |||
}, | |||
two: { | |||
max: 5, | |||
points: 14, | |||
viewers: [], | |||
}, | |||
three: { | |||
max: 10, | |||
points: 13, | |||
viewers: [], | |||
}, | |||
four: { | |||
max: 15, | |||
points: 12, | |||
viewers: [], | |||
}, | |||
five: { | |||
max: 20, | |||
points: 11, | |||
viewers: [], | |||
}, | |||
order: ['one', 'two', 'three', 'four', 'five'], | |||
}, | |||
leaderboard_max: 10, | |||
personal_leaderboard_max: 10, | |||
question_timeout_minutes: 3, | |||
} | |||
var quiz = { | |||
bonus_slot_1: null, | |||
bonus_slot_2: null, | |||
bonus_slot_3: null, | |||
bonus_viewers: { | |||
one: [], | |||
two: [], | |||
three: [], | |||
four: [], | |||
five: [], | |||
} | |||
}; | |||
module.exports = quiz; | |||
@@ -11,18 +52,11 @@ const db = require('./db'); | |||
const redis = require('./redis-brain'); | |||
const twitch = require('./twitch'); | |||
const valid_answers = ['A', 'B', 'C', 'D']; | |||
const bonus_slot_points = [100, 50, 25]; | |||
const standard_points = [20]; | |||
const leaderboard_max = 10; | |||
const personal_leaderboard_max = 10; | |||
const question_timer_minutes = 3; | |||
quiz.setCurrentQuestion = function(data) { | |||
return new Promise((resolve) => { | |||
redis.setActiveQuestion(...data).then(() => { | |||
twitch.sendNewQuestionMessage(...data); | |||
setTimeout(() => { quiz.endCurrentQuestion(); }, (1000 * (60 * question_timer_minutes))); | |||
setTimeout(() => { quiz.endCurrentQuestion(); }, (1000 * (60 * config.question_timeout_minutes))); | |||
resolve(); | |||
}).catch((error) => { | |||
// errlog | |||
@@ -44,10 +78,14 @@ quiz.endCurrentQuestion = function() { | |||
console.error("* [Quiz] Error writing question data to Postgres"); | |||
}); | |||
// TODO: Send bonus slot data to redis if we need to? dont lose it | |||
redis.clearActiveQuestion().then(() => { | |||
quiz.bonus_slot_1 = null; | |||
quiz.bonus_slot_2 = null; | |||
quiz.bonus_slot_3 = null; | |||
quiz.bonus_viewers.one = []; | |||
quiz.bonus_viewers.two = []; | |||
quiz.bonus_viewers.three = []; | |||
quiz.bonus_viewers.four = []; | |||
quiz.bonus_viewers.five = []; | |||
twitch.sendEndQuestionMessage(); | |||
@@ -66,13 +104,11 @@ quiz.handleViewerVote = function(username, answer) { | |||
redis.getActiveQuestion().then((aq_data) => { | |||
// If there is no active question, or there is no valid answer, nothing to do | |||
answer = answer.trim().toUpperCase(); | |||
if(!aq_data.question || !(answer) || !(valid_answers.includes(answer))) { | |||
if(!aq_data.question || !(answer) || !(config.valid_answers.includes(answer))) { | |||
redis.incrementResponseCounts(username, false, false); | |||
return resolve(); | |||
} | |||
console.log('PAST IT!!!'); | |||
// We have an (A|B|C|D), check it against the actual active answer | |||
redis.checkActiveAnswer(answer, username).then((is_correct_answer) => { | |||
if(is_correct_answer) { | |||
@@ -100,9 +136,9 @@ quiz.handleViewerVote = function(username, answer) { | |||
quiz.addViewerScore = function(username, nopoints=false) { | |||
if(nopoints) { | |||
// If it was the wrong answer | |||
// Wrong answer, set zero points in case we haven't seen user before | |||
redis.addViewerPoints(username, 0, null).then(() => { | |||
console.info(`* [quiz] Assigning 0 points to ${username} for an incorrect answer`); | |||
console.info(`* [quiz] Repeat or wrong answer from ${username}. Setting 0 points if not already present.`); | |||
}).catch((error) => { | |||
console.error(`* [quiz] Error adding no-points for ${username}`, error); | |||
}); | |||
@@ -110,28 +146,28 @@ quiz.addViewerScore = function(username, nopoints=false) { | |||
return; | |||
} | |||
// Check if any of our bonuses are still available | |||
let points = standard_points; | |||
let used_slot = null; | |||
if(!Boolean(quiz.bonus_slot_1 && quiz.bonus_slot_2 && quiz.bonus_slot_3)) { | |||
if(!quiz.bonus_slot_1) { | |||
points = bonus_slot_points[0]; | |||
used_slot = 1; | |||
quiz.bonus_slot_1 = username; | |||
} else if(!quiz.bonus_slot_2) { | |||
points = bonus_slot_points[1]; | |||
used_slot = 2; | |||
quiz.bonus_slot_2 = username; | |||
} else if(!quiz.bonus_slot_3) { | |||
points = bonus_slot_points[2]; | |||
used_slot = 3; | |||
quiz.bonus_slot_3 = username; | |||
function findAvailableBonusSlot() { | |||
for(let slot_key of config.bonus_slots.order) { | |||
let bs = quiz.bonus_viewers[slot_key]; | |||
console.log('***', slot_key, bs); | |||
if(bs.length < config.bonus_slots[slot_key].max) { | |||
return slot_key; | |||
} | |||
} | |||
return null; | |||
} | |||
let points = config.standard_points; | |||
let slot = findAvailableBonusSlot(); | |||
console.log('SLOT RETURNED: ', slot); | |||
if(slot) { | |||
quiz.bonus_viewers[slot].push(username); | |||
points = config.bonus_slots[slot].points; | |||
} | |||
redis.addViewerPoints(username, points, used_slot).then(() => { | |||
console.info(`* [quiz] Awarding ${points} points to ${username} for a correct first answer`); | |||
redis.addViewerPoints(username, points, slot).then(() => { | |||
console.info(`* [quiz] Correct answer for ${username}. Bonus slot ${slot}. Setting ${points} if not already present.`); | |||
}).catch((error) => { | |||
console.error(`* [quiz] Error adding ${points} for ${username}`, error); | |||
}); | |||
@@ -281,9 +317,11 @@ quiz.generateFakeAnswerData = function() { | |||
quiz.set_bot_state = function() { | |||
return new Promise((resolve) => { | |||
redis.checkBonusSlotAvailability().then((bs_data) => { | |||
quiz.bonus_slot_1 = bs_data[0]; | |||
quiz.bonus_slot_2 = bs_data[1]; | |||
quiz.bonus_slot_3 = bs_data[2]; | |||
quiz.bonus_viewers.one = bs_data[0] || []; | |||
quiz.bonus_viewers.two = bs_data[1] || []; | |||
quiz.bonus_viewers.three = bs_data[2] || []; | |||
quiz.bonus_viewers.four = bs_data[3] || []; | |||
quiz.bonus_viewers.five = bs_data[4] || []; | |||
}); | |||
}); | |||
}; | |||
@@ -295,13 +333,13 @@ function cleanLeaderboard(lb, username=null) { | |||
}); | |||
if(username) { | |||
for(let i = (personal_leaderboard_max - 1); i < lb.length; i++) { | |||
for(let i = (config.personal_leaderboard_max - 1); i < lb.length; i++) { | |||
if(lb[i].username === username) { | |||
lb[personal_leaderboard_max - 1] = lb[i]; | |||
lb[config.personal_leaderboard_max - 1] = lb[i]; | |||
} | |||
} | |||
} | |||
// Trim it back down to 10 folks total | |||
return lb.slice(0, username ? personal_leaderboard_max : leaderboard_max); | |||
return lb.slice(0, username ? config.personal_leaderboard_max : config.leaderboard_max); | |||
} |
@@ -15,13 +15,17 @@ const config = { | |||
answer_d_key: "twitch_bot_answer_d", | |||
votes_hash: "twitch_bot_votes_hash", | |||
points_hash: "twitch_bot_points_hash", | |||
bonus_slot_1_key: "twitch_bot_bonus_1", | |||
bonus_slot_2_key: "twitch_bot_bonus_2", | |||
bonus_slot_3_key: "twitch_bot_bonus_3", | |||
total_responses_key: "twitch_bot_total_responses", | |||
total_valid_key: "twitch_bot_total_valid_answers", | |||
total_correct_key: "twitch_bot_total_correct_answers", | |||
answer_totals: "twitch_bot_answer_totals_hash", | |||
/* Bonus Slots Keys */ | |||
bonus_slot_1_set: "twitch_bot_bonus_1", | |||
bonus_slot_2_set: "twitch_bot_bonus_2", | |||
bonus_slot_3_set: "twitch_bot_bonus_3", | |||
bonus_slot_4_set: "twitch_bot_bonus_4", | |||
bonus_slot_5_set: "twitch_bot_bonus_5", | |||
}; | |||
client.on("error", function(error) { | |||
@@ -54,9 +58,11 @@ var self = module.exports = { | |||
.del(config.active_answer_key, config.output) | |||
.del(config.votes_hash, config.output) | |||
.del(config.points_hash, config.output) | |||
.del(config.bonus_slot_1_key, config.output) | |||
.del(config.bonus_slot_2_key, config.output) | |||
.del(config.bonus_slot_3_key, config.output) | |||
.del(config.bonus_slot_1_set, config.output) | |||
.del(config.bonus_slot_2_set, config.output) | |||
.del(config.bonus_slot_3_set, config.output) | |||
.del(config.bonus_slot_4_set, config.output) | |||
.del(config.bonus_slot_5_set, config.output) | |||
.del(config.total_responses_key, config.output) | |||
.del(config.total_valid_key, config.output) | |||
.del(config.total_correct_key, config.output) | |||
@@ -93,9 +99,11 @@ var self = module.exports = { | |||
client.multi() | |||
.get(config.active_question_key) | |||
.get(config.active_answer_key) | |||
.get(config.bonus_slot_1_key) | |||
.get(config.bonus_slot_2_key) | |||
.get(config.bonus_slot_3_key) | |||
.smembers(config.bonus_slot_1_set) | |||
.smembers(config.bonus_slot_2_set) | |||
.smembers(config.bonus_slot_3_set) | |||
.smembers(config.bonus_slot_4_set) | |||
.smembers(config.bonus_slot_5_set) | |||
.hgetall(config.votes_hash) | |||
.hgetall(config.points_hash) | |||
.get(config.total_responses_key) | |||
@@ -109,48 +117,56 @@ var self = module.exports = { | |||
.get(config.overall_interactions) | |||
.scard(config.overall_viewers_set) | |||
.exec(function(err, replies) { | |||
var answer_totals = {}; | |||
var answer_percents = {}; | |||
if(replies[10]) { | |||
answer_totals = Object.fromEntries(Object.entries(replies[10]).map(([k, v]) => [k, Number(v)])); | |||
answer_percents = calculateAnswerPercents(answer_totals) | |||
} | |||
var answer_percents = replies[10] ? calculateAnswerPercents(replies[10]) : null; | |||
// Turn array into a dict for easier visual parsing | |||
let data = { | |||
reply_map = { | |||
active_question: replies[0], | |||
active_answer: replies[1], | |||
bonus_slots: { | |||
one: replies[2], | |||
two: replies[3], | |||
three: replies[4], | |||
four: replies[5], | |||
five: replies[6], | |||
}, | |||
active_votes: replies[7], | |||
active_points: replies[8], | |||
total_responses: intOrZero(replies[9]), | |||
total_valid: intOrZero(replies[10]), | |||
total_correct: intOrZero(replies[11]), | |||
total_incorrect: 0, | |||
answer_totals: replies[12], | |||
answers: { | |||
a: replies[11], | |||
b: replies[12], | |||
c: replies[13], | |||
d: replies[14], | |||
a: replies[13], | |||
b: replies[14], | |||
c: replies[15], | |||
d: replies[16], | |||
}, | |||
bonus_slot_1: replies[2], | |||
bonus_slot_2: replies[3], | |||
bonus_slot_3: replies[4], | |||
total_responses: replies[7] || 0, | |||
total_valid: replies[8] || 0, | |||
total_correct: replies[9] || 0, | |||
total_incorrect: (replies[8] - replies[9]).toString(), | |||
answer_totals: answer_totals, | |||
answer_percents: answer_percents, | |||
overall: { | |||
interactions: parseInt(replies[15]) || 0, | |||
unique_viewers: replies[16], | |||
} | |||
interactions: intOrZero(replies[17]), | |||
unique_viewers: replies[18], | |||
}, | |||
}; | |||
if(data.overall.interactions == NaN) { data.overall.interactions = 0; } | |||
reply_map.total_incorrect = reply_map.total_valid - reply_map.total_correct; | |||
if(reply_map.answer_totals) { | |||
reply_map.answer_totals = Object.fromEntries(Object.entries(reply_map.answer_totals).map(([k, v]) => [k, Number(v)])); | |||
reply_map.answer_percents = calculateAnswerPercents(reply_map.answer_totals) | |||
} | |||
if(trimmed) { | |||
// replace bonus-slot lists with used-slot count | |||
/* | |||
for([key, value] of Object.entries(reply_map.bonus_slots)) { | |||
reply_map.bonus_slots[key] = value ? value.length : 0; | |||
} | |||
*/ | |||
if(!trimmed) { | |||
data.active_votes = replies[5]; | |||
data.active_points = replies[6]; | |||
// delete vote and points maps (lengthy) | |||
delete reply_map.active_votes; | |||
delete reply_map.active_points; | |||
} | |||
resolve(data); | |||
resolve(reply_map); | |||
}); | |||
}); | |||
}, | |||
@@ -177,28 +193,37 @@ var self = module.exports = { | |||
points = points.toString(); | |||
if(slot_used) { | |||
// If we used a slot, we need to invalidate it | |||
// If we used a slot, we need to add the viewer to redis set | |||
let slot_key = null; | |||
if(slot_used === 1) { | |||
slot_key = config.bonus_slot_1_key; | |||
} else if (slot_used === 2) { | |||
slot_key = config.bonus_slot_2_key; | |||
} else if (slot_used === 3) { | |||
slot_key = config.bonus_slot_3_key; | |||
switch (slot_used) { | |||
case 'one': | |||
slot_key = config.bonus_slot_1_set; | |||
break; | |||
case 'two': | |||
slot_key = config.bonus_slot_2_set; | |||
break; | |||
case 'three': | |||
slot_key = config.bonus_slot_3_set; | |||
break; | |||
case 'four': | |||
slot_key = config.bonus_slot_4_set; | |||
break; | |||
case 'five': | |||
slot_key = config.bonus_slot_5_set; | |||
break; | |||
} | |||
client.multi() | |||
.set(slot_key, username, config.output) | |||
.hsetnx(config.points_hash, username, points) | |||
.exec(function(err, replies) { | |||
return resolve(); | |||
}); | |||
} else { | |||
// Otherwise just set the points | |||
client.hsetnx(config.points_hash, username, points, function(err) { | |||
return resolve(); | |||
}); | |||
client.sadd(slot_key, username, config.output); | |||
} | |||
// Add the user and their points to the active data | |||
client.hsetnx(config.points_hash, username, points, function(err) { | |||
if(err) { | |||
console.error("* [redis] Error setting user point value to points hash."); | |||
} | |||
return resolve(); | |||
}); | |||
}); | |||
}, | |||
checkActiveAnswer: function(submitted_answer, username) { | |||
@@ -226,9 +251,11 @@ var self = module.exports = { | |||
checkBonusSlotAvailability: function() { | |||
return new Promise((resolve) => { | |||
client.multi() | |||
.get(config.bonus_slot_1_key, config.output) | |||
.get(config.bonus_slot_2_key, config.output) | |||
.get(config.bonus_slot_3_key, config.output) | |||
.get(config.bonus_slot_1_set, config.output) | |||
.get(config.bonus_slot_2_set, config.output) | |||
.get(config.bonus_slot_3_set, config.output) | |||
.get(config.bonus_slot_4_set, config.output) | |||
.get(config.bonus_slot_5_set, config.output) | |||
.exec(function(err, replies) { | |||
resolve(replies); | |||
}); | |||
@@ -305,8 +332,13 @@ function calculateAnswerPercents(answer_totals) { | |||
for([key, value] of Object.entries(answer_totals)) { | |||
// percents[key] = Number.parseFloat(((Number(value)/total) * 100).toFixed(2)); | |||
percents[key] = Math.trunc((Number(value)/total) * 100); | |||
percents[key] = intOrZero(Math.trunc((Number(value)/total) * 100)); | |||
} | |||
return percents; | |||
} | |||
function intOrZero(value) { | |||
let v = parseInt(value); | |||
return v || 0; | |||
} |
@@ -18,49 +18,66 @@ | |||
</div> | |||
</form> | |||
<hr class="uk-divider-icon" /> | |||
<div class="uk-margin uk-flex uk-flex-around"> | |||
<div> | |||
<div class="uk-text-bold">Total Responses</div> | |||
<div class="uk-text-center">{{ total_responses }}</div> | |||
</div> | |||
<div> | |||
<div class="uk-text-bold">Total Valid</div> | |||
<div class="uk-text-center">{{ total_valid }}</div> | |||
</div> | |||
<div> | |||
<div class="uk-text-bold">Total Correct</div> | |||
<div class="uk-text-center">{{ total_correct }}</div> | |||
</div> | |||
<div> | |||
<div class="uk-text-bold">Total Incorrect</div> | |||
<div class="uk-text-center">{{ total_incorrect }}</div> | |||
<div class="uk-margin uk-text-center"> | |||
<h3>Question Totals</h3> | |||
<div class="uk-flex uk-flex-around"> | |||
<div> | |||
<div class="uk-text-bold">Total Responses</div> | |||
<div class="uk-text-center">{{ total_responses }}</div> | |||
</div> | |||
<div> | |||
<div class="uk-text-bold">Total Valid</div> | |||
<div class="uk-text-center">{{ total_valid }}</div> | |||
</div> | |||
<div> | |||
<div class="uk-text-bold">Total Correct</div> | |||
<div class="uk-text-center">{{ total_correct }}</div> | |||
</div> | |||
<div> | |||
<div class="uk-text-bold">Total Incorrect</div> | |||
<div class="uk-text-center">{{ total_incorrect }}</div> | |||
</div> | |||
</div> | |||
</div> | |||
<hr class="uk-divider-icon" /> | |||
{% if answer_percents %} | |||
<div class="uk-margin uk-flex uk-flex-around"> | |||
{% for ap in answer_percents | dictsort %} | |||
<div class="uk-text-center"> | |||
<div class="uk-text-bold">{{ ap[0] }}</div> | |||
<div class="{% if ap[0] == active_answer %}uk-text-warning{% endif %}">{{ ap[1] }}% ({{ answer_totals[ap[0]] }})</div> | |||
<div class="uk-margin uk-text-center"> | |||
<h3>Answer Trends</h3> | |||
<div class="uk-flex uk-flex-around"> | |||
{% for ap in answer_percents | dictsort %} | |||
<div class="uk-text-center"> | |||
<div class="uk-text-bold">{{ ap[0] }}</div> | |||
<div class="{% if ap[0] == active_answer %}uk-text-warning{% endif %}">{{ ap[1] }}% ({{ answer_totals[ap[0]] }})</div> | |||
</div> | |||
{% endfor %} | |||
</div> | |||
{% endfor %} | |||
</div> | |||
<hr class="uk-divider-icon" /> | |||
{% endif %} | |||
<div class="uk-margin uk-flex uk-flex-around"> | |||
<div class="uk-text-center"> | |||
<div class="uk-text-bold">1st</div> | |||
<div>{{ bonus_slot_1 or "Open" }}</div> | |||
</div> | |||
<div class="uk-text-center"> | |||
<div class="uk-text-bold">2nd</div> | |||
<div>{{ bonus_slot_2 or "Open" }}</div> | |||
</div> | |||
<div class="uk-text-center"> | |||
<div class="uk-text-bold">3rd</div> | |||
<div>{{ bonus_slot_3 or "Open"}}</div> | |||
</div> | |||
<div class="uk-margin uk-text-center"> | |||
<h3>Bonus Point Earners</h3> | |||
<ul class="uk-subnav uk-subnav-pill uk-flex uk-flex-around" uk-switcher> | |||
<li><a href="#">First</a></li> | |||
<li><a href="#">Second</a></li> | |||
<li><a href="#">Third</a></li> | |||
<li><a href="#">Fourth</a></li> | |||
<li><a href="#">Fifth</a></li> | |||
</ul> | |||
<ul class="uk-switcher uk-margin uk-text-left"> | |||
{% for slot in ['one', 'two', 'three', 'four', 'five'] %} | |||
<li> | |||
<ul class="uk-list uk-list-primary uk-list-striped uk-list-disc uk-width-1-2 uk-align-center"> | |||
{% if bonus_slots[slot].length %} | |||
{% for viewer in bonus_slots[slot] %} | |||
<li>{{ viewer }}</li> | |||
{% endfor %} | |||
{% else %} | |||
<span>Bonus slots are all available!</span> | |||
{% endif %} | |||
</ul> | |||
</li> | |||
{% endfor %} | |||
</ul> | |||
</div> | |||
<hr class="uk-divider-icon" /> | |||
@@ -8,16 +8,16 @@ | |||
<h2 class="uk-text-muted uk-margin-small-top">{{ username }}</h2> | |||
{% endif %} | |||
<div class="uk-margin-xlarge"> | |||
<h2 class="uk-text-center">Overall</h2> | |||
<div class="uk-margin-large"> | |||
<h3 class="uk-text-center">Overall</h3> | |||
<div class="uk-margin uk-flex uk-flex-around"> | |||
<div> | |||
<div class="uk-text-bold uk-text-large">Total Interactions</div> | |||
<div class="uk-text-center uk-text-large">{{ overall.interactions }}</div> | |||
<div class="uk-text-bold">Total Interactions</div> | |||
<div class="uk-text-center uk-text-primary uk-text-large">{{ overall.interactions }}</div> | |||
</div> | |||
<div> | |||
<div class="uk-text-bold uk-text-large">Total Unique Users</div> | |||
<div class="uk-text-center uk-text-large">{{ overall.unique_viewers }}</div> | |||
<div class="uk-text-bold">Total Unique Users</div> | |||
<div class="uk-text-center uk-text-primary uk-text-large">{{ overall.unique_viewers }}</div> | |||
</div> | |||
</div> | |||
</div> | |||
@@ -46,7 +46,7 @@ | |||
<hr class="uk-divider-icon" /> | |||
{% if active %} | |||
<div class="uk-margin"> | |||
<div class="uk-margin" hidden> | |||
<h3 class="uk-text-center">Current Question</h3> | |||
<div class="uk-margin uk-flex uk-flex-around"> | |||
<div> | |||
@@ -67,7 +67,7 @@ | |||
</div> | |||
</div> | |||
</div> | |||
<hr class="uk-divider-icon" /> | |||
<hr class="uk-divider-icon" hidden/> | |||
{% endif %} | |||
<div class="uk-margin" hidden> | |||