Started boopbox implementation

This commit is contained in:
Ed 2023-07-04 23:06:16 +02:00
parent 9759e377a9
commit a811965504
58 changed files with 559 additions and 46 deletions

86
boop.py Normal file
View File

@ -0,0 +1,86 @@
from sanic import Blueprint, exceptions, response
from time import time
from asyncio import Future
import asyncio
from datetime import datetime
from asyncio import Queue
from random import randint
from boop_process import boop_process
bp = Blueprint("boop", url_prefix="/boop")
bp.ctx.boopbox_queue = {'a': Queue(), 'b': Queue(), 'c': Queue()}
bp.ctx.busy = {'a': False, 'b': False, 'c': False}
bp.ctx.last_tag = {'a': None, 'b': None, 'c': None}
bp.ctx.repeats = {'a': None, 'b': None, 'c': None}
def enable_nfc(enable, boopbox_id):
log.info('NFC is ' + ('enabled' if enable else 'disabled'))
app.ctx.queue.put_nowait({'_': 'nfc', 'enabled': enable})
app.ctx.nfc_enabled = enable;
@bp.get("/refresh")
async def refresh_boops(request):
await boop_process(request.app.ctx.om.cache.values(), request.app.ctx.boop)
return response.text('ok')
@bp.get("/")
async def show_boopbox(request):
tpl = request.app.ctx.tpl.get_template('boopbox.html')
return response.html(tpl.render())
@bp.get("/getqueue/<boopbox_id>")
async def boop_queue(request, boopbox_id):
items = []
queue = bp.ctx.boopbox_queue[boopbox_id]
while 1:
try:
item = queue.get_nowait()
except asyncio.queues.QueueEmpty:
if items:
break
# Try one last time to get a task, then fail.
bp.ctx.busy[boopbox_id] = False
try:
item = await asyncio.wait_for(queue.get(), timeout=5)
except asyncio.exceptions.TimeoutError:
break
items.append(item)
if len(items):
bp.ctx.busy[boopbox_id] = True
return response.json(items)
@bp.post("/read")
async def handle_boop(request):
payload = request.json
queue = bp.ctx.boopbox_queue[payload['boopbox_id']]
if bp.ctx.busy[payload['boopbox_id']]: return response.text('busy')
await queue.put({'_': 'play', 'src': f"/res/snd/error.wav"})
if bp.ctx.last_tag[payload['boopbox_id']] == payload['id']:
bp.ctx.repeats[payload['boopbox_id']] += 1
else:
bp.ctx.last_tag[payload['boopbox_id']] = payload['id']
bp.ctx.repeats[payload['boopbox_id']] = 0
if bp.ctx.repeats[payload['boopbox_id']] > 5:
await queue.put({'_': 'play', 'src': f"/res/snd/ratelimit.wav"})
await queue.put({'_': 'bye'})
return response.text('ok')
if bp.ctx.repeats[payload['boopbox_id']] > 10:
await queue.put({'_': 'talk', 'who': 'tiger', 'msg': f"Hey! Stop that! You're not the only one here!"})
await queue.put({'_': 'bye'})
return response.text('ok')
request.app.ctx.boop.execute('INSERT INTO boop(tag_id, station, ts) VALUES (?,?,?)', (payload['id'], payload['boopbox_id'], int(time())))
request.app.ctx.boop.commit()
return response.text('ok')

42
boop_process.py Normal file
View File

@ -0,0 +1,42 @@
from datetime import datetime
from random import randint
from hashlib import md5
from base64 import b16encode
fibonacci = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]
current_date = datetime.today()
async def boop_process(orders, db):
print(f'Processing {len(orders)} orders')
db.execute('DELETE FROM player')
for o in orders:
tags = []
code = o.code
tag_id = o.nfc_id or b16encode(md5(code.encode()).digest()).decode()[:14]
birthday = o.birth_date
badge_id = o.badge_id or randint(0,200)
room = o.actual_room or randint(100,400)
country = o.country
name = o.first_name.lower().strip()
birth_date = datetime.strptime(birthday, "%Y-%m-%d").date()
age = current_date.year - birth_date.year
if (current_date.month, current_date.day) < (birth_date.month, birth_date.day):
age -= 1
# Check if the birthday is today
if (current_date.month, current_date.day) == (birth_date.month, birth_date.day):
tags.append('birthday')
# Check if the badge is a fib number
if badge_id in fibonacci:
tags.append('fibonacci')
db.execute('INSERT INTO player(tag_id, code, tags, birthday, badge_id, age, name, room, country) VALUES (?,?,?,?,?,?,?,?,?)',
(tag_id, code, ','.join(tags), birthday, badge_id, age, name, room, country)
)
db.commit()

0
boops.py Normal file
View File

BIN
res/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

95
res/boopbox.css Normal file
View File

@ -0,0 +1,95 @@
/* Fonts */
@font-face {font-family: 'Alegreya';src: url("font/alegreya-latin-600-normal.woff2") format('woff2');}
@font-face {font-family: 'Nexa Rust Sans'; src: url("font/NexaRustSans-Black.otf");}
@font-face {font-family: 'Ticketing'; src: url("font/ticketing.otf");}
@font-face {font-family: 'Trade Winds'; src: url("font/TradeWinds-Regular.ttf");}
* {margin:0;padding:0;border:0;box-sizing:border-box;}
body {font-family: 'Alegreya', serif;background:#000;color:#fff;overflow:hidden; /*cursor: none;*/}
em {color:#fc0;}
#border {border:red 1px dotted;position:absolute;top:50%;left:50%;transform:translateX(-640px) translateY(-400px);width:1280px;height:800px;z-index:1000;}
#main {position:absolute;top:50%;left:50%;transform:translateX(-640px) translateY(-400px);background:linear-gradient(
rgba(0, 0, 0, 0.5),
rgba(0, 0, 0, 0.5)
),url('bg.png');background-size:cover;background-repeat:no-repeat;width:1280px;height:800px;border:1px solid #000;overflow:hidden;}
#commands a {color:#fff;display:inline-block;padding:0.2em;margin:0 0.25em;background:#009;font-family:monospace;font-size:2em;line-height:1.2em;}
#debug {font-family:monospace;}
#error {font-family:'Ticketing';font-size:2em;position:absolute;top:50%;left:50%;transform:translateX(-640px) translateY(-400px);background:#333;width:1280px;height:800px;background:rgba(0,0,0,0.75);z-index:200;box-sizing:border-box;}
#error p {display:block;margin-top:10em;text-align:center;background:#00c;padding:0.5em 0;}
/* Personaggi */
.char {position:absolute;transform:translateX(0px);transition: all 0.3s;z-index:500;}
#wolf {width:400px;left:880px;top:240px;}
#tiger {width:500px;left:-60px;top:90px;}
#wolf[disabled] {transform:translateX(398px);}
#tiger[disabled] {transform:translateX(-440px);}
/* Nfc status in basso a dx */
#nfcstat {background:#090;position:absolute;right:32px;width:320px;top:716px;font-size:1.7em;padding: 8px 16px;text-align:center;border-radius:8px}
#nfcstat[disabled] {background:#a00;}
#nfcstat img {margin-left:0.2em;margin-top:-4px;height:0.9em;display:inline-block;vertical-align:middle; filter: invert(100%);}
.hidden {opacity: 0;}
.welcome-back {transition: all .25s;}
/* Message box */
#msgbox {backdrop-filter: blur(3px);position:absolute;width:1180px;height:190px;background:rgba(0.7,0,0,0.5);padding:26px;left:20px;bottom:20px;border-radius:8px;color:#fff;font-weight:bold;font-size:32px;opacity:1;transition: opacity .3s;z-index:600;}
#msgbox[disabled] {opacity:0;}
#msgbox img {height:60px;display:inline-block;vertical-align:middle; filter: invert(100%)}
#msgbox span {display:block;}
@keyframes touch {from{opacity:0.3;} to{opacity:0.9;}}
#touch {animation-name:touch;animation-duration:0.7s;animation-iteration-count:infinite;animation-direction:alternate;}
#touch[disabled] {display:none;}
/* Events */
#events > p {margin-bottom:1em;}
#events > p > a {display:inline-block;border-radius:16px;padding:0.1em 0.7em;margin-right:0.3em;text-decoration: none;color:#fff;font-size:1.3em;background:rgba(100,0,200,0.2);border:4px solid rgba(0,0,0,0);}
#events > p > a:hover {border:4px solid darkorchid;}
#eventsbox div, #column {backdrop-filter: blur(6px);background:rgba(0,0,0,0.2);border-radius:16px;text-shadow: 0 0 5px rgba(0,0,0,0.5);}
#events {transition: all .25s;position:absolute;left:32px;height:740px;width:870px;top:32px;}
#eventsbox span {float:right;background:#300;border-radius:6px;font-size:0.9em;padding:0.3em 0.5em;}
#eventsbox span[data-location~="Reception"] {background:#613583;}
#eventsbox span[data-location~="Restaurant"] {background:#26a269;}
#eventsbox span[data-location~="Disco"] {background:#e5a50a;}
#eventsbox span[data-location~="Dealers"] {background:#1a5fb4;}
#eventsbox span[data-location~="Tent"] {background:#a51d2d;}
#eventsbox span[data-location~="Stage"] {background:#63452c;}
#eventsbox > div {padding:1em 1em 0.5em 1em;font-size:1.5em;margin-bottom:1em;border-bottom:5px solid;border-radius:8px 8px 0 0;border-image: linear-gradient(to right, darkblue, darkorchid 50%, rgba(0,0,0,0) 50.1%) 1;transition:all 0.5s;}
#eventsbox {height:690px;overflow:auto;overflow:auto;scrollbar-width: none;-webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 5%, #000 95%, transparent);}
.eventmeter {box-sizing:border-box;display:inline-block;background:#090;height:4px;width:100%;margin-left:-1em;}
/* Side column and tv */
#tv {position:absolute;background:#000;right:32px;width:320px;height:180px;top:32px;border-radius:16px;}
#column {transition: all .25s;text-align:center;position:absolute;right:32px;width:320px;height:460px;top:230px;padding:1em;}
#column p {font-size:1.3em;}
h1,h2,h3 {font-family: 'Trade Winds';}
h1 {text-align:center;font-size:2em;}
hr {border: 1px solid white;opacity:0.2;width:320px;max-width:60%;margin:0.5em auto;}
@keyframes shake {
0% { transform: translate(1px, 1px); }
10% { transform: translate(-1px, -2px); }
20% { transform: translate(-3px, 0px); }
30% { transform: translate(3px, 2px); }
40% { transform: translate(1px, -1px); }
50% { transform: translate(-1px, 2px); }
60% { transform: translate(-3px, 1px); }
70% { transform: translate(3px, 1px); }
80% { transform: translate(-1px, -1px); }
90% { transform: translate(1px, 2px); }
100% { transform: translate(1px, -2px); }
}
.yell {line-height:1.1em;font-size:2.5em;animation: shake 0.5s;animation-iteration-count: infinite; }

246
res/boopbox.js Normal file
View File

@ -0,0 +1,246 @@
const box = document.getElementById("msgbox");
const msgbox = document.getElementById("msgcontent");
const wolf = document.getElementById("wolf");
const tiger = document.getElementById("tiger");
const touch = document.getElementById("touch");
const bye_elements = document.getElementsByClassName('bye');
const welcome_back_elements = document.getElementsByClassName('welcome-back');
const apiUrl = "/manage/api/events.json";
let currentTime = new Date();
const searchParams = new URL(window.location.href).searchParams;
const boopboxId = searchParams.get('id');
if(!boopboxId) {
alert("Boopbox id is not set!");
}
window.onload = function() {
updateEvents();
loop();
clock();
//fastForward();
}
let cachedData = null;
function updateEvents() {
fetch(apiUrl)
.then(response => response.json())
.then(data => {
cachedData = data;
updateDivs(data);
})
.catch(error => {
if (cachedData) {
updateDivs(cachedData);
} else {
console.error('Request failed and no cached data available:', error);
}
});
}
function updateDivs(data) {
const eventsContainer = document.getElementById("eventsbox");
const tenMinutesFromNow = new Date(currentTime.getTime() + 10 * 60000);
const tenMinutesAgo = new Date(currentTime.getTime() - 10 * 60000);
let visibleEvents = 0;
data.forEach(event => {
const eventStart = new Date(event.start);
const eventEnd = new Date(event.end);
const eventPercent = Math.max(0, Math.min(100, ((currentTime - eventStart) / (eventEnd - eventStart)) * 100));
if (eventStart <= tenMinutesFromNow && eventEnd >= tenMinutesAgo) {
visibleEvents++;
let eventDiv = document.getElementById(`event-${event.id}`);
if (!eventDiv) {
eventDiv = document.createElement('div');
eventDiv.id = `event-${event.id}`;
eventsContainer.appendChild(eventDiv);
}
eventDiv.innerHTML = `
<span data-location="${event.location}">${event.location}</span>
<h2>${event.title}</h2>
<p>${event.about}</p>
<p style="text-align:right;">${Math.ceil(Math.max(0, (eventEnd - currentTime) / 60000))} minutes left</p>
`;
eventDiv.style.borderImage = `linear-gradient(to right, darkblue, darkorchid ${eventPercent}%, rgba(0,0,0,0) ${eventPercent+0.1}%) 1`;
} else {
const eventDiv = document.getElementById(`event-${event.id}`);
if (eventDiv) {
eventDiv.remove();
}
}
});
if(visibleEvents == 0) {
document.getElementById("nothing-happening").style.display = 'block';
} else {
document.getElementById("nothing-happening").style.display = 'none';
}
}
function clock() {
setInterval(() => {
currentTime = new Date();
let ts = currentTime.toString();
ts = ts.replace("GMT+0200 (Central European Summer Time)", "");
ts = ts.replace("2023", "<br />");
document.getElementById("clock").innerHTML = ts;
}, 1000);
}
function fastForward() {
currentTime = new Date("2023-05-29T18:00Z");
setInterval(() => {
updateDivs(cachedData);
currentTime.setMinutes(currentTime.getMinutes()+1);
}, 100);
}
async function loop() {
while (true) {
try {
document.getElementById("nfcstat").removeAttribute('disabled');
const response = await fetch("/boop/getqueue/"+boopboxId);
const json = await response.json();
document.getElementById("nfcstat").setAttribute('disabled', 'true');
for(i = 0; i < json.length; i++) {
console.log(json[i]);
document.getElementById('debug').innerHTML = JSON.stringify(json[i]);
try {
await window[json[i]["_"]](json[i]);
} catch (e) {
document.getElementById('debug').innerHTML = e;
}
}
} catch (e) {
await new Promise(r => setTimeout(r, 2000));
console.error(e);
}
}
}
/*window.oncontextmenu = function(event) {
event.preventDefault();
event.stopPropagation();
return false;
};*/
async function talk(dict) {
for (const c of welcome_back_elements) {
c.classList.add('hidden');
}
character = document.getElementById(dict.who);
character.removeAttribute('disabled');
box.removeAttribute('disabled');
for(j = 0;j < dict.msg.length; j++) {
to = 20;
msgbox.innerHTML += dict.msg[j];
if(dict.msg[j] == '.') {to = 400;}
if(dict.msg[j] == ',') {to = 200;}
await new Promise(r => setTimeout(r, to));
}
await new Promise(r => setTimeout(r, 1000));
}
async function bye(dict) {
msgbox.innerHTML = '';
for (const c of bye_elements) {
c.setAttribute('disabled', 'true');
}
for (const c of welcome_back_elements) {
c.classList.remove('hidden');
}
}
async function play(dict) {
const audio = new Audio(dict.src);
await audio.play();
}
async function wait(dict) {
return new Promise((resolve) => {
setTimeout(resolve, dict.time * 1000);
});
}
async function orchestrate(thing) {
for(var i = 0; i < msg.length; i++) {
task = msg[i]
// It's a command
if(task.length == 3) {
switch(task) {
case '!w-':
wolf.setAttribute('disabled', 'true');
break;
case '!t-':
tiger.setAttribute('disabled', 'true');
break;
case '!w+':
wolf.removeAttribute('disabled');
break;
case '!t+':
tiger.removeAttribute('disabled');
break;
case '!s+':
msgbox.classList.add('yell');
break;
case '!s-':
msgbox.classList.remove('yell');
break;
default:
alert('Invalid command received.' + task);
}
continue;
}
touch.setAttribute("disabled", "true");
// It's a text message
if (task.startsWith('!w:') || task.startsWith('!t:')) {
if(task[1] == 'w' && wolf.getAttribute('disabled') == 'true') {
wolf.removeAttribute('disabled');
}
if(task[1] == 't' && tiger.getAttribute('disabled') == 'true') {
tiger.removeAttribute('disabled');
}
msgbox.style.color = (task[1] == 'w')?'#FAEBD7':'#ADD8E6';
txt = task.substr(3);
await new Promise(r => setTimeout(r, 500));
//touch.removeAttribute("disabled");
await new Promise(r => setTimeout(r, 1000));
msgbox.innerHTML = '';
console.log(task);
}
}
box.setAttribute('disabled', 'true');
}

BIN
res/botbg2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
res/error_openbox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
res/font/ticketing.otf Normal file

Binary file not shown.

1
res/icons/keypad.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" ?><svg height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><title/><rect height="96" rx="8" ry="8" width="96" x="80" y="16"/><rect height="96" rx="8" ry="8" width="96" x="208" y="16"/><rect height="96" rx="8" ry="8" width="96" x="336" y="16"/><rect height="96" rx="8" ry="8" width="96" x="80" y="144"/><rect height="96" rx="8" ry="8" width="96" x="208" y="144"/><rect height="96" rx="8" ry="8" width="96" x="336" y="144"/><rect height="96" rx="8" ry="8" width="96" x="80" y="272"/><rect height="96" rx="8" ry="8" width="96" x="208" y="272"/><rect height="96" rx="8" ry="8" width="96" x="208" y="400"/><rect height="96" rx="8" ry="8" width="96" x="336" y="272"/></svg>

After

Width:  |  Height:  |  Size: 717 B

View File

@ -1,46 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" ?><svg height="24" version="1.1" width="24" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><g transform="translate(0 -1028.4)"><path d="m5 1037.4c-1.1046 0-2 0.9-2 2v1 6 1c0 1.1 0.8954 2 2 2h2 10 2c1.105 0 2-0.9 2-2v-7-1c0-1.1-0.895-2-2-2h-2-12z" fill="#f1c40f"/><path d="m5 1040.4c-1.1046 0-2 0.9-2 2v1 6 1c0 1.1 0.8954 2 2 2h2 10 2c1.105 0 2-0.9 2-2v-7-1c0-1.1-0.895-2-2-2h-2-12z" fill="#f39c12"/><path d="m12 1029.4c-3.866 0-7 3.1-7 7h3c0-2.2 1.7909-4 4-4 2.209 0 4 1.8 4 4h3c0-3.9-3.134-7-7-7z" fill="#bdc3c7"/><path d="m5 14v1h14v-1h-14zm0 2v1h14v-1h-14zm0 2v1h14v-1h-14zm0 2v1h14v-1h-14z" fill="#e67e22" transform="translate(0 1028.4)"/><path d="m5 1037.4v1c0 0.5 0.6716 1 1.5 1s1.5-0.5 1.5-1v-1c0 0.5-0.6716 1-1.5 1s-1.5-0.5-1.5-1z" fill="#7f8c8d"/><path d="m16 1037.4v1c0 0.5 0.672 1 1.5 1s1.5-0.5 1.5-1v-1c0 0.5-0.672 1-1.5 1s-1.5-0.5-1.5-1z" fill="#7f8c8d"/><path d="m12 2.4375c-0.351 0-0.699 0.0338-1.031 0.0937-0.247 0.0446-0.487 0.1129-0.719 0.1876-0.217 0.0698-0.4229 0.1561-0.625 0.25-0.106 0.0492-0.2109 0.1008-0.3125 0.1562-0.1319 0.0719-0.2513 0.1682-0.375 0.25-0.1158 0.0765-0.2356 0.1342-0.3437 0.2188-0.1407 0.1097-0.2799 0.2211-0.4063 0.3437-0.0369 0.0362-0.0581 0.0878-0.0937 0.125-0.1684 0.1744-0.3313 0.3356-0.4688 0.5313-0.1402 0.1993-0.2704 0.4065-0.375 0.625-0.004 0.0084 0.004 0.0227 0 0.0312-0.1111 0.2359-0.2159 0.4959-0.2812 0.75-0.253 0.5844-0.9424 1-1.75 1-0.06 0-0.0832 0.0018-0.125 0-0.0105-0.0004-0.0168 0.0014-0.0313 0l-0.0625 1v1 1h3v-1-1c0-0.2761 0.0405-0.5521 0.0938-0.8125 0.3729-1.8227 1.9732-3.1875 3.9062-3.1875 2.209 0 4 1.7909 4 4v1 1h3v-1-1l-0.062-1c-0.015 0.0014-0.021-0.0004-0.032 0h-0.125c-0.807 0-1.497-0.4156-1.75-1-0.065-0.2541-0.17-0.5141-0.281-0.75-0.004-0.0085 0.004-0.0228 0-0.0312-0.105-0.2185-0.235-0.4257-0.375-0.625-0.138-0.1957-0.3-0.3569-0.469-0.5313-0.036-0.038-0.055-0.0881-0.094-0.125-0.126-0.1226-0.265-0.234-0.406-0.3437-0.104-0.0817-0.232-0.1448-0.344-0.2188-0.122-0.0809-0.244-0.1788-0.374-0.25-0.101-0.055-0.208-0.1074-0.313-0.1562-0.202-0.0939-0.408-0.1802-0.625-0.25-0.232-0.0747-0.472-0.143-0.719-0.1876-0.332-0.0599-0.68-0.0937-1.031-0.0937z" fill="#95a5a6" transform="translate(0 1028.4)"/></g></svg>
<svg
id="Icons"
viewBox="0 0 24 24"
version="1.1"
sodipodi:docname="lock.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview11"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="35.416667"
inkscape:cx="9.2611765"
inkscape:cy="12"
inkscape:window-width="1920"
inkscape:window-height="1035"
inkscape:window-x="0"
inkscape:window-y="21"
inkscape:window-maximized="1"
inkscape:current-layer="Icons" />
<defs
id="defs4">
<style
id="style2">.cls-1{fill:#232323;}</style>
</defs>
<path
class="cls-1"
d="M12,0A6,6,0,0,0,6,6V7H8V6a4,4,0,0,1,8,0V7h2V6A6,6,0,0,0,12,0Z"
id="path6"
style="fill:#000000" />
<path
class="cls-1"
d="M18,9H6a3,3,0,0,0-3,3v8a3,3,0,0,0,3,3H18a3,3,0,0,0,3-3V12A3,3,0,0,0,18,9Zm-5,7.73V19a1,1,0,0,1-2,0V16.73a2,2,0,1,1,2,0Z"
id="path8"
style="fill:#000000" />
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

1
res/icons/nfc.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" ?><svg viewBox="0 0 288 288" xmlns="http://www.w3.org/2000/svg"><path d="M228 12c27 0 48 22 48 48v168c0 26-22 48-48 48h-72c-45 0-66-12-66-126V77l70 70c11 11 28-6 16-18L87 40c-9-9-21-2-21 8v102c0 52 4 90 18 115 3 6 4 11-4 11H60c-27 0-48-22-48-48V60c0-26 21-48 48-48h72c45 0 66 12 66 126v73l-69-69c-12-12-29 5-17 17l89 89c9 9 21 2 21-8V138c0-52-3-88-18-115-4-8-3-11 4-11h20z"/></svg>

After

Width:  |  Height:  |  Size: 401 B

1
res/icons/refresh.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" ?><svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 3v2a5 5 0 0 0-3.54 8.54l-1.41 1.41A7 7 0 0 1 10 3zm4.95 2.05A7 7 0 0 1 10 17v-2a5 5 0 0 0 3.54-8.54l1.41-1.41zM10 20l-4-4 4-4v8zm0-12V0l4 4-4 4z"/></svg>

After

Width:  |  Height:  |  Size: 248 B

1
res/icons/touch.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="512px" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Gesture_x2C__hand_x2C__hand_touch_x2C__touch"><path d="M446.937,360.598l-21.123-39.657c-5.226-9.797-17.397-13.51-27.202-8.288l-1.763,0.937 c-3.169-25.073,8.091-55.672-1.983-73.96l-46.898-85.132c-1.521-2.755-3.658-5.033-6.157-6.851 c-8.699-8.423-23.044-10.28-35.29-3.531c-2.048,1.122-3.876,2.449-5.561,3.891c-8.659-8.756-23.26-10.759-35.709-3.906 c-6.406,3.534-11.045,8.868-13.775,14.81c-0.35,0.07-0.7,0.145-1.057,0.219l-1.389-2.509c-7.308-13.269-25.165-17.448-39.89-9.332 c-9.729,5.359-15.534,14.733-16.34,24.303c-0.169,0.036-0.346,0.076-0.521,0.115l-26.598-49.215 c4.681-8.321,7.35-17.869,7.35-28.017c0-32.165-26.766-58.333-59.667-58.333S53.697,62.309,53.697,94.474 c0,18.958,9.477,36.8,25.35,47.726c9.94,6.842,21.615,10.496,33.795,10.598c8.919,16.505,17.32,32.05,25.882,47.891 c0.22,0.47,0.422,0.945,0.676,1.406c1.646,2.993,3.126,5.679,4.459,8.094c6.074,11.243,12.293,22.746,18.898,34.969l0.192-0.104 c4.724,8.598,13.152,23.919,28.596,51.953c0,0-57.87,14.669-82.033,27.975c-52.687,29.033-26.438,63.83-15.233,64.689 c95.239-39.039,116.045-31.486,156.024-0.686c0.512,0.389,1.176,1.066,1.956,1.96c-7.942,5.814-10.672,16.666-5.912,25.601 l21.129,39.655c5.224,9.808,17.396,13.515,27.195,8.293l143.977-76.697C448.443,382.579,452.161,370.4,446.937,360.598z M89.253,127.373c-10.993-7.566-17.556-19.865-17.556-32.899c0-22.24,18.692-40.333,41.667-40.333s41.667,18.093,41.667,40.333 c0,2.42-0.234,4.788-0.658,7.092l-5.033-9.313c-9.719-17.975-29.581-26.063-44.378-18.076 c-14.805,7.982-18.927,29.022-9.213,46.988c2.25,4.163,4.445,8.226,6.604,12.221C97.686,132.154,93.264,130.134,89.253,127.373z M416.674,368.771c-7.784,4.296-17.579,1.469-21.877-6.319c-4.295-7.783-1.465-17.58,6.32-21.876 c7.785-4.292,17.578-1.468,21.876,6.318C427.29,354.682,424.458,364.474,416.674,368.771z"/></g><g id="Layer_1"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 MiB

BIN
res/orig/TIGER.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

BIN
res/orig/wolf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
res/snd/01.mp3 Normal file

Binary file not shown.

BIN
res/snd/02.mp3 Normal file

Binary file not shown.

BIN
res/snd/03.mp3 Normal file

Binary file not shown.

BIN
res/snd/04.mp3 Normal file

Binary file not shown.

BIN
res/snd/05.mp3 Normal file

Binary file not shown.

BIN
res/snd/06.mp3 Normal file

Binary file not shown.

BIN
res/snd/07.mp3 Normal file

Binary file not shown.

BIN
res/snd/08.mp3 Normal file

Binary file not shown.

BIN
res/snd/09.mp3 Normal file

Binary file not shown.

BIN
res/snd/10.mp3 Normal file

Binary file not shown.

BIN
res/snd/11.mp3 Normal file

Binary file not shown.

BIN
res/snd/12.mp3 Normal file

Binary file not shown.

BIN
res/snd/13.mp3 Normal file

Binary file not shown.

BIN
res/snd/14.mp3 Normal file

Binary file not shown.

BIN
res/snd/15.mp3 Normal file

Binary file not shown.

BIN
res/snd/16.mp3 Normal file

Binary file not shown.

BIN
res/snd/17.mp3 Normal file

Binary file not shown.

BIN
res/snd/18.mp3 Normal file

Binary file not shown.

BIN
res/snd/19.mp3 Normal file

Binary file not shown.

BIN
res/snd/20.mp3 Normal file

Binary file not shown.

BIN
res/snd/21.mp3 Normal file

Binary file not shown.

BIN
res/snd/22.mp3 Normal file

Binary file not shown.

BIN
res/snd/23.mp3 Normal file

Binary file not shown.

BIN
res/snd/24.mp3 Normal file

Binary file not shown.

BIN
res/snd/25.mp3 Normal file

Binary file not shown.

BIN
res/snd/error.wav Normal file

Binary file not shown.

BIN
res/snd/ratelimit.wav Normal file

Binary file not shown.

BIN
res/tiger.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

BIN
res/tr.webm Normal file

Binary file not shown.

BIN
res/tv.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

BIN
res/video/coin-intro.webm Normal file

Binary file not shown.

BIN
res/video/coin-loop.webm Normal file

Binary file not shown.

BIN
res/video/coin.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
res/wolf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

BIN
res/wood.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

85
tpl/boopbox.html Normal file
View File

@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="/res/boopbox.css" />
</head>
<body>
<div id="commands">
<a href="javascript:document.documentElement.requestFullscreen();">Fs</a>
<a href="javascript:document.getElementById('wolf').setAttribute('disabled', 'true');">Wolf hide</a>
<a href="javascript:document.getElementById('wolf').removeAttribute('disabled');">Wolf show</a>
<a href="javascript:document.getElementById('tiger').setAttribute('disabled', 'true');">Tiger hide</a>
<a href="javascript:document.getElementById('tiger').removeAttribute('disabled');">Tiger show</a>
<a href="javascript:document.getElementById('msgbox').removeAttribute('disabled');">Chatbox show</a>
<a href="javascript:document.getElementById('msgbox').setAttribute('disabled', 'true');">Chatbox hide</a>
<span id="debug">Debug command</span>
</div>
<!--<div id="error">
<p>An unrecoverable error has happened. Please call foxo.</p>
</div>-->
<!--<div id="border"></div>-->
<div id="main">
<img class="char bye" id="wolf" src="/res/wolf.png" disabled="true" />
<img class="char bye" id="tiger" src="/res/tiger.png" disabled="true" />
<div id="msgbox" class="bye" disabled><span id="msgcontent"></span><span id="touch" disabled>(touch the screen to continue <img src="/res/touch.svg" />)</span></div>
<div id="events" class="welcome-back">
<div id="eventsbox">
<h1 style="margin-top:7em;display:none;" id="nothing-happening">It looks like nothing is happening now~<br />_-¯¯-¯_<br />See you later :)</h1>
</div>
<h1>See more events with our app!</h1>
</div>
<video id="tv" autoplay muted playsinline controls="false" poster="/res/tv.jpg" class="welcome-back">
<!-- <source id="video-source" src="http://192.168.32.189:8000/tron.webm" type="video/mp4"> -->
</video>
<script>
var video = document.getElementById('tv');
var source = document.getElementById('video-source');
video.controls = false; // Ensure controls are hidden
video.addEventListener('error', function() {
video.style.display = 'none';
});
function playVideo() {
var cachebuster = Date.now(); // Generate a unique timestamp
var videoUrl = 'http://192.168.32.189:8000/tron.webm?cachebuster=' + cachebuster;
source.src = videoUrl;
video.load();
video.play().catch(function() {
fallbackImage.style.display = 'block'; // Show fallback image on playback failure
});
}
playVideo();
</script>
<div id="column" class="welcome-back">
<h1>Useful Info</h1>
<h2>CALL SECURITY</h2>
<p>Dial (+39) 375 732 4734</p>
<hr />
<h2>Reception Open</h2>
<p>Every day 8:00~19:00</p>
<hr />
<h2>NFC issues?</h2>
<p>Search for Foxo (Badge ID 3) or write to @unfoxo on Telegram</p>
<hr />
<h2 id="clock"></h2>
</div>
<div id="nfcstat" class="welcome-back" disabled>Dealer's Den <img src="/res/icons/nfc.svg" /></div>
</div>
<script type="text/javascript" src="/res/boopbox.js"></script>
</body>
</html>