Bump to v1.1.0, enable network and docker detectors
- Version bump reflecting new features (sounds, tap reaction, docker detector) - Rename title to Kao - Enable network and docker detectors by default Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,7 @@
|
||||
},
|
||||
{
|
||||
"name": "network",
|
||||
"enabled": false,
|
||||
"enabled": true,
|
||||
"script": "detectors/network.py",
|
||||
"env": {
|
||||
"CHECK_INTERVAL": "60",
|
||||
@@ -55,7 +55,7 @@
|
||||
},
|
||||
{
|
||||
"name": "docker",
|
||||
"enabled": false,
|
||||
"enabled": true,
|
||||
"script": "detectors/docker.py",
|
||||
"env": {
|
||||
"CHECK_INTERVAL": "60",
|
||||
|
||||
671
index.html
671
index.html
@@ -1,371 +1,398 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Sentry-Emote</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<title>Kao</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #000000;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: monospace;
|
||||
overflow: hidden;
|
||||
}
|
||||
body {
|
||||
background: #000000;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: monospace;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#emote {
|
||||
font-size: 18vw;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
#emote {
|
||||
font-size: 18vw;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
#message {
|
||||
font-size: 4vw;
|
||||
margin-top: 2vh;
|
||||
opacity: 0.7;
|
||||
text-align: center;
|
||||
}
|
||||
#message {
|
||||
font-size: 4vw;
|
||||
margin-top: 2vh;
|
||||
opacity: 0.7;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Breathing animation - slow pulse */
|
||||
.breathing {
|
||||
animation: breathe 3s ease-in-out infinite;
|
||||
}
|
||||
/* Breathing animation - slow pulse */
|
||||
.breathing {
|
||||
animation: breathe 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes breathe {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
@keyframes breathe {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
/* Shaking animation - rapid jitter for Critical */
|
||||
.shaking {
|
||||
animation: shake 0.15s linear infinite;
|
||||
}
|
||||
/* Shaking animation - rapid jitter for Critical */
|
||||
.shaking {
|
||||
animation: shake 0.15s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-5px); }
|
||||
75% { transform: translateX(5px); }
|
||||
}
|
||||
@keyframes shake {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Popping animation - scale up for Notifications */
|
||||
.popping {
|
||||
animation: pop 1s ease-in-out infinite;
|
||||
}
|
||||
/* Popping animation - scale up for Notifications */
|
||||
.popping {
|
||||
animation: pop 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pop {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
}
|
||||
@keyframes pop {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
}
|
||||
|
||||
/* Celebrating animation - bounce and wiggle */
|
||||
.celebrating {
|
||||
animation: celebrate 0.5s ease-in-out infinite;
|
||||
}
|
||||
/* Celebrating animation - bounce and wiggle */
|
||||
.celebrating {
|
||||
animation: celebrate 0.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes celebrate {
|
||||
0%, 100% {
|
||||
transform: translateY(0) rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: translateY(-10px) rotate(-5deg);
|
||||
}
|
||||
75% {
|
||||
transform: translateY(-10px) rotate(5deg);
|
||||
}
|
||||
}
|
||||
@keyframes celebrate {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0) rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: translateY(-10px) rotate(-5deg);
|
||||
}
|
||||
75% {
|
||||
transform: translateY(-10px) rotate(5deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Floating animation - gentle drift */
|
||||
.floating {
|
||||
animation: float 4s ease-in-out infinite;
|
||||
}
|
||||
/* Floating animation - gentle drift */
|
||||
.floating {
|
||||
animation: float 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Bouncing animation - playful hop */
|
||||
.bouncing {
|
||||
animation: bounce 1s ease-in-out infinite;
|
||||
}
|
||||
/* Bouncing animation - playful hop */
|
||||
.bouncing {
|
||||
animation: bounce 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
}
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Swaying animation - curious side-to-side */
|
||||
.swaying {
|
||||
animation: sway 3s ease-in-out infinite;
|
||||
}
|
||||
/* Swaying animation - curious side-to-side */
|
||||
.swaying {
|
||||
animation: sway 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes sway {
|
||||
0%, 100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(-3deg);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(3deg);
|
||||
}
|
||||
}
|
||||
@keyframes sway {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(-3deg);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(3deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Blink animation - quick fade for winks */
|
||||
.blink {
|
||||
animation: blink 0.3s ease-in-out 1;
|
||||
}
|
||||
/* Blink animation - quick fade for winks */
|
||||
.blink {
|
||||
animation: blink 0.3s ease-in-out 1;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
@keyframes blink {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
/* Searching animation - looking around for connection */
|
||||
.searching {
|
||||
animation: search 2s ease-in-out infinite;
|
||||
}
|
||||
/* Searching animation - looking around for connection */
|
||||
.searching {
|
||||
animation: search 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes search {
|
||||
0%, 100% {
|
||||
transform: translateX(0);
|
||||
opacity: 0.6;
|
||||
}
|
||||
25% {
|
||||
transform: translateX(-10px);
|
||||
opacity: 0.8;
|
||||
}
|
||||
75% {
|
||||
transform: translateX(10px);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
@keyframes search {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
opacity: 0.6;
|
||||
}
|
||||
25% {
|
||||
transform: translateX(-10px);
|
||||
opacity: 0.8;
|
||||
}
|
||||
75% {
|
||||
transform: translateX(10px);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sleeping animation - very slow, subtle breathing */
|
||||
.sleeping {
|
||||
animation: sleep 6s ease-in-out infinite;
|
||||
}
|
||||
/* Sleeping animation - very slow, subtle breathing */
|
||||
.sleeping {
|
||||
animation: sleep 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes sleep {
|
||||
0%, 100% {
|
||||
opacity: 0.4;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.2;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="emote" class="breathing">( ^_^)</div>
|
||||
<div id="message">Loading...</div>
|
||||
@keyframes sleep {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.4;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.2;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="emote" class="breathing">( ^_^)</div>
|
||||
<div id="message">Loading...</div>
|
||||
|
||||
<script>
|
||||
const emoteEl = document.getElementById('emote');
|
||||
const messageEl = document.getElementById('message');
|
||||
const POLL_INTERVAL = 2000;
|
||||
const VERSION = 'v1.0.0';
|
||||
<script>
|
||||
const emoteEl = document.getElementById("emote");
|
||||
const messageEl = document.getElementById("message");
|
||||
const POLL_INTERVAL = 2000;
|
||||
const VERSION = "v1.1.0";
|
||||
|
||||
// Sound system
|
||||
let audioCtx = null;
|
||||
let soundEnabled = new URLSearchParams(window.location.search).get('sound') === 'on';
|
||||
let lastState = null;
|
||||
let lastData = null;
|
||||
let isReacting = false;
|
||||
// Sound system
|
||||
let audioCtx = null;
|
||||
let soundEnabled =
|
||||
new URLSearchParams(window.location.search).get("sound") === "on";
|
||||
let lastState = null;
|
||||
let lastData = null;
|
||||
let isReacting = false;
|
||||
|
||||
function initAudio() {
|
||||
if (!audioCtx) {
|
||||
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
}
|
||||
if (audioCtx.state === 'suspended') {
|
||||
audioCtx.resume();
|
||||
}
|
||||
}
|
||||
function initAudio() {
|
||||
if (!audioCtx) {
|
||||
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
}
|
||||
if (audioCtx.state === "suspended") {
|
||||
audioCtx.resume();
|
||||
}
|
||||
}
|
||||
|
||||
function playTone(frequency, duration, type = 'sine', volume = 0.1) {
|
||||
if (!soundEnabled || !audioCtx) return;
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
osc.type = type;
|
||||
osc.frequency.value = frequency;
|
||||
gain.gain.value = volume;
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration);
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + duration);
|
||||
}
|
||||
function playTone(frequency, duration, type = "sine", volume = 0.1) {
|
||||
if (!soundEnabled || !audioCtx) return;
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
osc.type = type;
|
||||
osc.frequency.value = frequency;
|
||||
gain.gain.value = volume;
|
||||
gain.gain.exponentialRampToValueAtTime(
|
||||
0.001,
|
||||
audioCtx.currentTime + duration,
|
||||
);
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + duration);
|
||||
}
|
||||
|
||||
function playWarningSound() {
|
||||
// Soft double-beep
|
||||
playTone(440, 0.15);
|
||||
setTimeout(() => playTone(440, 0.15), 180);
|
||||
}
|
||||
function playWarningSound() {
|
||||
// Soft double-beep
|
||||
playTone(440, 0.15);
|
||||
setTimeout(() => playTone(440, 0.15), 180);
|
||||
}
|
||||
|
||||
function playCriticalSound() {
|
||||
// Urgent descending tone
|
||||
playTone(600, 0.2);
|
||||
setTimeout(() => playTone(400, 0.3), 220);
|
||||
}
|
||||
function playCriticalSound() {
|
||||
// Urgent descending tone
|
||||
playTone(600, 0.2);
|
||||
setTimeout(() => playTone(400, 0.3), 220);
|
||||
}
|
||||
|
||||
function playNotifySound() {
|
||||
// Gentle ping
|
||||
playTone(880, 0.1, 'sine', 0.08);
|
||||
}
|
||||
function playNotifySound() {
|
||||
// Gentle ping
|
||||
playTone(880, 0.1, "sine", 0.08);
|
||||
}
|
||||
|
||||
function playRecoverySound() {
|
||||
// Happy ascending chirp
|
||||
playTone(523, 0.1);
|
||||
setTimeout(() => playTone(659, 0.1), 100);
|
||||
setTimeout(() => playTone(784, 0.15), 200);
|
||||
}
|
||||
function playRecoverySound() {
|
||||
// Happy ascending chirp
|
||||
playTone(523, 0.1);
|
||||
setTimeout(() => playTone(659, 0.1), 100);
|
||||
setTimeout(() => playTone(784, 0.15), 200);
|
||||
}
|
||||
|
||||
function playReactSound() {
|
||||
// Cute surprised chirp
|
||||
playTone(600, 0.08, 'sine', 0.06);
|
||||
}
|
||||
function playReactSound() {
|
||||
// Cute surprised chirp
|
||||
playTone(600, 0.08, "sine", 0.06);
|
||||
}
|
||||
|
||||
function handleStateChange(newState, newEmote) {
|
||||
if (!lastState) {
|
||||
lastState = newState;
|
||||
return;
|
||||
}
|
||||
function handleStateChange(newState, newEmote) {
|
||||
if (!lastState) {
|
||||
lastState = newState;
|
||||
return;
|
||||
}
|
||||
|
||||
// State transitions that trigger sounds
|
||||
if (newState !== lastState) {
|
||||
if (newState === 'critical') {
|
||||
playCriticalSound();
|
||||
} else if (newState === 'warning') {
|
||||
playWarningSound();
|
||||
} else if (newState === 'notify') {
|
||||
playNotifySound();
|
||||
} else if (newState === 'optimal' && lastState !== 'optimal' && lastState !== 'sleeping') {
|
||||
// Recovery - also check for celebration emote
|
||||
playRecoverySound();
|
||||
}
|
||||
lastState = newState;
|
||||
}
|
||||
}
|
||||
// State transitions that trigger sounds
|
||||
if (newState !== lastState) {
|
||||
if (newState === "critical") {
|
||||
playCriticalSound();
|
||||
} else if (newState === "warning") {
|
||||
playWarningSound();
|
||||
} else if (newState === "notify") {
|
||||
playNotifySound();
|
||||
} else if (
|
||||
newState === "optimal" &&
|
||||
lastState !== "optimal" &&
|
||||
lastState !== "sleeping"
|
||||
) {
|
||||
// Recovery - also check for celebration emote
|
||||
playRecoverySound();
|
||||
}
|
||||
lastState = newState;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle tap - enable sound and show reaction
|
||||
document.body.addEventListener('click', () => {
|
||||
// Enable sound on first tap (browser autoplay policy)
|
||||
if (!soundEnabled) {
|
||||
soundEnabled = true;
|
||||
initAudio();
|
||||
}
|
||||
// Handle tap - enable sound and show reaction
|
||||
document.body.addEventListener("click", () => {
|
||||
// Enable sound on first tap (browser autoplay policy)
|
||||
if (!soundEnabled) {
|
||||
soundEnabled = true;
|
||||
initAudio();
|
||||
}
|
||||
|
||||
// Show surprised reaction and version
|
||||
if (!isReacting) {
|
||||
isReacting = true;
|
||||
const prevEmote = emoteEl.textContent;
|
||||
const prevColor = emoteEl.style.color;
|
||||
const prevClass = emoteEl.className;
|
||||
const prevMsg = messageEl.textContent;
|
||||
// Show surprised reaction and version
|
||||
if (!isReacting) {
|
||||
isReacting = true;
|
||||
const prevEmote = emoteEl.textContent;
|
||||
const prevColor = emoteEl.style.color;
|
||||
const prevClass = emoteEl.className;
|
||||
const prevMsg = messageEl.textContent;
|
||||
|
||||
// Surprised face!
|
||||
emoteEl.textContent = '( °o°)';
|
||||
emoteEl.className = 'popping';
|
||||
messageEl.textContent = `Kao ${VERSION}`;
|
||||
playReactSound();
|
||||
// Surprised face!
|
||||
emoteEl.textContent = "( °o°)";
|
||||
emoteEl.className = "popping";
|
||||
messageEl.textContent = `Kao ${VERSION}`;
|
||||
playReactSound();
|
||||
|
||||
// Return to normal after 1.5s
|
||||
setTimeout(() => {
|
||||
if (lastData) {
|
||||
updateDisplay(lastData);
|
||||
} else {
|
||||
emoteEl.textContent = prevEmote;
|
||||
emoteEl.style.color = prevColor;
|
||||
emoteEl.className = prevClass;
|
||||
messageEl.textContent = prevMsg;
|
||||
}
|
||||
isReacting = false;
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
// Return to normal after 1.5s
|
||||
setTimeout(() => {
|
||||
if (lastData) {
|
||||
updateDisplay(lastData);
|
||||
} else {
|
||||
emoteEl.textContent = prevEmote;
|
||||
emoteEl.style.color = prevColor;
|
||||
emoteEl.className = prevClass;
|
||||
messageEl.textContent = prevMsg;
|
||||
}
|
||||
isReacting = false;
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
|
||||
// Also init if ?sound=on
|
||||
if (soundEnabled) {
|
||||
document.addEventListener('DOMContentLoaded', initAudio);
|
||||
}
|
||||
// Also init if ?sound=on
|
||||
if (soundEnabled) {
|
||||
document.addEventListener("DOMContentLoaded", initAudio);
|
||||
}
|
||||
|
||||
async function fetchStatus() {
|
||||
try {
|
||||
const response = await fetch('/status');
|
||||
if (!response.ok) throw new Error('Failed to fetch');
|
||||
const data = await response.json();
|
||||
updateDisplay(data);
|
||||
} catch (err) {
|
||||
// Connection lost state
|
||||
emoteEl.textContent = '( ?.?)';
|
||||
emoteEl.style.color = '#888888';
|
||||
emoteEl.className = 'searching';
|
||||
messageEl.style.color = '#888888';
|
||||
messageEl.textContent = '';
|
||||
}
|
||||
}
|
||||
async function fetchStatus() {
|
||||
try {
|
||||
const response = await fetch("/status");
|
||||
if (!response.ok) throw new Error("Failed to fetch");
|
||||
const data = await response.json();
|
||||
updateDisplay(data);
|
||||
} catch (err) {
|
||||
// Connection lost state
|
||||
emoteEl.textContent = "( ?.?)";
|
||||
emoteEl.style.color = "#888888";
|
||||
emoteEl.className = "searching";
|
||||
messageEl.style.color = "#888888";
|
||||
messageEl.textContent = "";
|
||||
}
|
||||
}
|
||||
|
||||
function updateDisplay(data) {
|
||||
lastData = data;
|
||||
function updateDisplay(data) {
|
||||
lastData = data;
|
||||
|
||||
// Don't update display while showing reaction
|
||||
if (isReacting) return;
|
||||
// Don't update display while showing reaction
|
||||
if (isReacting) return;
|
||||
|
||||
// Check for state changes and play sounds
|
||||
handleStateChange(data.current_state, data.active_emote);
|
||||
// Check for state changes and play sounds
|
||||
handleStateChange(data.current_state, data.active_emote);
|
||||
|
||||
emoteEl.textContent = data.active_emote;
|
||||
emoteEl.style.color = data.color;
|
||||
messageEl.style.color = data.color;
|
||||
emoteEl.textContent = data.active_emote;
|
||||
emoteEl.style.color = data.color;
|
||||
messageEl.style.color = data.color;
|
||||
|
||||
// Only show message when there's something to report
|
||||
const topEvent = data.active_events && data.active_events[0];
|
||||
messageEl.textContent = (topEvent && topEvent.message) || '';
|
||||
// Only show message when there's something to report
|
||||
const topEvent = data.active_events && data.active_events[0];
|
||||
messageEl.textContent = (topEvent && topEvent.message) || "";
|
||||
|
||||
// Update animation class
|
||||
emoteEl.className = '';
|
||||
if (data.animation) {
|
||||
emoteEl.classList.add(data.animation);
|
||||
}
|
||||
}
|
||||
// Update animation class
|
||||
emoteEl.className = "";
|
||||
if (data.animation) {
|
||||
emoteEl.classList.add(data.animation);
|
||||
}
|
||||
}
|
||||
|
||||
// Initial fetch and start polling
|
||||
fetchStatus();
|
||||
setInterval(fetchStatus, POLL_INTERVAL);
|
||||
</script>
|
||||
</body>
|
||||
// Initial fetch and start polling
|
||||
fetchStatus();
|
||||
setInterval(fetchStatus, POLL_INTERVAL);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user