Initial Upload
Simple bare-bones book progress bar with a test animation running. TODO: - Remove IMGUR hotlink - Update book art - Proper input value testing - Get websocket connected and tested
@@ -4,16 +4,20 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Silkscreen:wght@400;700&family=VT323&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Courier+Prime:ital,wght@0,400;0,700;1,400;1,700&family=Cutive+Mono&display=swap" rel="stylesheet">
|
||||||
<link href='https://fonts.googleapis.com/css?family=Inter' rel='stylesheet'>
|
|
||||||
<link rel="stylesheet" href="timer.css" />
|
<link rel="stylesheet" href="timer.css" />
|
||||||
<script src="timer.js"></script>
|
<script src="timer.js"></script>
|
||||||
<title>Lilac Timer</title>
|
<title>Lilac BookBar</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<div class='widgetContainer'>
|
<div class='widgetContainer'>
|
||||||
<div class="layoutContainer">
|
<div id='progressBar' class="layoutContainer">
|
||||||
|
<div id='barBack' class='barFlashHelper'></div>
|
||||||
|
<div id='barFront' class='barFlashHelper'>
|
||||||
|
<div id='paperOverlay'>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 221 KiB |
|
After Width: | Height: | Size: 233 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 246 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 263 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 270 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 279 KiB |
|
After Width: | Height: | Size: 281 KiB |
|
After Width: | Height: | Size: 284 KiB |
|
After Width: | Height: | Size: 289 KiB |
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 298 KiB |
|
After Width: | Height: | Size: 300 KiB |
|
After Width: | Height: | Size: 304 KiB |
|
After Width: | Height: | Size: 307 KiB |
|
After Width: | Height: | Size: 312 KiB |
|
After Width: | Height: | Size: 314 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 320 KiB |
|
After Width: | Height: | Size: 324 KiB |
|
After Width: | Height: | Size: 327 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 336 KiB |
|
After Width: | Height: | Size: 340 KiB |
|
After Width: | Height: | Size: 344 KiB |
|
After Width: | Height: | Size: 348 KiB |
|
After Width: | Height: | Size: 351 KiB |
|
After Width: | Height: | Size: 355 KiB |
|
After Width: | Height: | Size: 358 KiB |
|
After Width: | Height: | Size: 361 KiB |
|
After Width: | Height: | Size: 364 KiB |
|
After Width: | Height: | Size: 367 KiB |
|
After Width: | Height: | Size: 370 KiB |
|
After Width: | Height: | Size: 372 KiB |
|
After Width: | Height: | Size: 376 KiB |
|
After Width: | Height: | Size: 378 KiB |
|
After Width: | Height: | Size: 380 KiB |
|
After Width: | Height: | Size: 383 KiB |
|
After Width: | Height: | Size: 385 KiB |
|
After Width: | Height: | Size: 364 KiB |
|
After Width: | Height: | Size: 757 KiB |
|
After Width: | Height: | Size: 782 KiB |
@@ -12,17 +12,19 @@ html, body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: "Courier Prime", monospace;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
font-family: "Silkscreen", sans-serif;
|
font-family: "Courier Prime", monospace;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
color: #EA4045;
|
color: #0f0908;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,14 +38,14 @@ h1 {
|
|||||||
h2 {
|
h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
color: #404145;
|
color: #0f0908;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
color: #404145;
|
color: #0f0908;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,314 +55,60 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
*** Timer Widget ***
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.contentContainer {
|
|
||||||
height: fit-content;
|
|
||||||
width: fit-content;
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainHeart {
|
|
||||||
max-height: 100vh;
|
|
||||||
max-width: 72vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
#SecondHeart {
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.infoWrap {
|
|
||||||
container-type: size;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
position: absolute;
|
|
||||||
left: 0px;
|
|
||||||
top: 0px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 56% 34% 1fr;
|
|
||||||
grid-template-rows: 11% 1fr 29%;
|
|
||||||
place-items: center;
|
|
||||||
text-wrap: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.infoGroup {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
grid-column-start: 2;
|
|
||||||
grid-column-end: 2;
|
|
||||||
grid-row-start: 2;
|
|
||||||
grid-row-end: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.infoBox {
|
|
||||||
height: auto;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 4cqh;
|
|
||||||
}
|
|
||||||
|
|
||||||
#InfoBox1 {
|
|
||||||
transform: translate(-2%, 0%);
|
|
||||||
margin-top: -50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#InfoBox2 {
|
|
||||||
/* -- !!DO NOT REMOVE!! --
|
|
||||||
Removing this transform kills the goal label and I do not know why
|
|
||||||
*/
|
|
||||||
transform: translate(0%, 0%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#InfoBox3 {
|
|
||||||
width: 142%;
|
|
||||||
position: relative;
|
|
||||||
transform: translate(65%, 10%) scale(1, 1);
|
|
||||||
border-radius: 3cqh;
|
|
||||||
border: .4cqh solid;
|
|
||||||
border-color: #00A0F3;
|
|
||||||
background-color: white;
|
|
||||||
padding: .4ch 0 .4ch 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#Timer {
|
|
||||||
/*
|
|
||||||
margin-top: 10%;
|
|
||||||
margin-bottom: 2%;
|
|
||||||
*/
|
|
||||||
transform: translate(0%, 0%) scale(.95, 1.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#TimerInfo {
|
|
||||||
transform: translate(0%, -60%);
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#GoalLabel {
|
|
||||||
color: #404145;
|
|
||||||
}
|
|
||||||
|
|
||||||
#GoalName {
|
|
||||||
width: fit-content;
|
|
||||||
position: relative;
|
|
||||||
color: #32B993;
|
|
||||||
text-wrap: nowrap;
|
|
||||||
display: inline-block;
|
|
||||||
padding-right: 4%;
|
|
||||||
transform: translate(.5ch, 0%);
|
|
||||||
/* --- animation-duration = JS VAR --- */
|
|
||||||
}
|
|
||||||
|
|
||||||
#GoalNameDupe {
|
|
||||||
width: fit-content;
|
|
||||||
position: relative;
|
|
||||||
color: #32B993;
|
|
||||||
text-wrap: nowrap;
|
|
||||||
padding-right: 4%;
|
|
||||||
transform: translate(.5ch, 0%);
|
|
||||||
/* --- animation-duration = JS VAR --- */
|
|
||||||
}
|
|
||||||
|
|
||||||
#GoalProgressWrap {
|
|
||||||
width: fit-content;
|
|
||||||
padding-left: .8cqh;
|
|
||||||
padding-right: .8cqh;
|
|
||||||
border-radius: 3cqh;
|
|
||||||
border: .4cqh solid;
|
|
||||||
border-color: #404145;
|
|
||||||
margin: .4ch auto 0 auto;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#GoalProgressBar {
|
|
||||||
width: 110%;
|
|
||||||
background-color: #FC98B3;
|
|
||||||
transform: translate(-5%, 0%) scale(1, 1.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#GoalProgress {
|
|
||||||
transform: translate(0%, -8%) scale(1);
|
|
||||||
text-align: center;
|
|
||||||
margin-top: -19%;
|
|
||||||
color: #404145;
|
|
||||||
}
|
|
||||||
|
|
||||||
#TopUsers {
|
|
||||||
margin-bottom: 2%;
|
|
||||||
font-weight: normal;
|
|
||||||
color: #404145;
|
|
||||||
}
|
|
||||||
|
|
||||||
.giftUserWrap {
|
|
||||||
transform: translate(9%, -8%) scale(1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.giftUserRow {
|
|
||||||
width: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 2ch 14ch 5ch;
|
|
||||||
grid-template-rows: 1fr;
|
|
||||||
text-wrap: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.giftUserNum {
|
|
||||||
border-right: 1.5px solid #00A0F3;
|
|
||||||
border-image: linear-gradient(to top,
|
|
||||||
transparent 0%,
|
|
||||||
transparent 5%,
|
|
||||||
#00A0F3 35%,
|
|
||||||
#00A0F3 65%,
|
|
||||||
transparent 95%) 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.giftUserScore {
|
|
||||||
border-left: 1.5px solid #00A0F3;
|
|
||||||
border-image: linear-gradient(to top,
|
|
||||||
transparent 0%,
|
|
||||||
transparent 5%,
|
|
||||||
#00A0F3 40%,
|
|
||||||
#00A0F3 60%,
|
|
||||||
transparent 95%) 1;
|
|
||||||
text-align: right;
|
|
||||||
padding-right: .25ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.giftUserName {
|
|
||||||
width: fit-content;
|
|
||||||
margin-left: 0%;
|
|
||||||
position: relative;
|
|
||||||
color: #32B993;
|
|
||||||
text-align: left;
|
|
||||||
display: inline-block;
|
|
||||||
padding-right: 4%;
|
|
||||||
transform: translate(.5ch, 0%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.giftUserNameDupe {
|
|
||||||
width: fit-content;
|
|
||||||
margin-left: 0%;
|
|
||||||
position: relative;
|
|
||||||
color: #32B993;
|
|
||||||
text-align: left;
|
|
||||||
padding-right: 4%;
|
|
||||||
transform: translate(.5ch, 0%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#GiftUserName1 {
|
|
||||||
/* --- animation-duration = JS VAR --- */
|
|
||||||
}
|
|
||||||
|
|
||||||
#GiftUserName2 {
|
|
||||||
/* --- animation-duration = JS VAR --- */
|
|
||||||
}
|
|
||||||
|
|
||||||
#GiftUserName3 {
|
|
||||||
/* --- animation-duration = JS VAR --- */
|
|
||||||
}
|
|
||||||
|
|
||||||
#GiftUserNameDupe1 {
|
|
||||||
/* --- animation-duration = JS VAR --- */
|
|
||||||
}
|
|
||||||
|
|
||||||
#GiftUserNameDupe2 {
|
|
||||||
/* --- animation-duration = JS VAR --- */
|
|
||||||
}
|
|
||||||
|
|
||||||
#GiftUserNameDupe3 {
|
|
||||||
/* --- animation-duration = JS VAR --- */
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollWrap {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightFadeWrap {
|
|
||||||
/*
|
|
||||||
mask-image: linear-gradient(to right, black 75%, transparent 90%);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
#RightFadeWrapInfoBox2 {
|
|
||||||
width: 90%;
|
|
||||||
border-left: 1.5px solid #00A0F3;
|
|
||||||
border-right: 1.5px solid #00A0F3;
|
|
||||||
border-image: linear-gradient(to top,
|
|
||||||
transparent 0%,
|
|
||||||
transparent 5%,
|
|
||||||
#00A0F3 40%,
|
|
||||||
#00A0F3 60%,
|
|
||||||
transparent 95%) 1;
|
|
||||||
transform: translate(1ch, 0%) scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fadeLeft {
|
|
||||||
mask-image: linear-gradient(to right,
|
|
||||||
transparent 0%,
|
|
||||||
black 1ch,
|
|
||||||
black calc(100% - 2ch),
|
|
||||||
transparent 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ScrollWrapGoal {
|
|
||||||
/*
|
|
||||||
width: 90%;
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollWrapName {
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollWrapW {
|
|
||||||
width: fit-content;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ScrollWrapGoalW {
|
|
||||||
}
|
|
||||||
|
|
||||||
.hide {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollAnim {
|
|
||||||
animation: myScroll linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes myScroll {
|
|
||||||
0% {left: 0%; top: 0%;}
|
|
||||||
42% {left: 0%; top: 0%;}
|
|
||||||
100% {left: -54%; top: 0%;}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*** Progres Bar Widget ***
|
*** Progres Bar Widget ***
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
.widgetContainer {
|
.widgetContainer {
|
||||||
max-height: 16vh;
|
aspect-ratio: 195/32;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
|
max-height: 100vh;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: lightblue;
|
|
||||||
container-type: inline-size;
|
container-type: inline-size;
|
||||||
container-name: sidebar;
|
container-name: sidebar;
|
||||||
}
|
}
|
||||||
.layoutContainer {
|
.layoutContainer {
|
||||||
height: clamp(64px, calc(.164*100vw), calc(.164*100vw));
|
width: 100%;
|
||||||
width: clamp(36.563px, 100vw, 100vw);
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: grey;
|
background-color: transparent;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
background-image: url("src/Books/BookBar_v5_f0000.png");
|
||||||
|
}
|
||||||
|
.barFlashHelper {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
background-image: url("src/Books/BookBar_v5_f0000.png");
|
||||||
|
}
|
||||||
|
#barBack {
|
||||||
|
}
|
||||||
|
#barFront {
|
||||||
|
}
|
||||||
|
#paperOverlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
opacity: 1;
|
||||||
|
background-image: url("src/Paper_v4.png");
|
||||||
|
mask-image: url("https://i.imgur.com/lseOCYA.png");
|
||||||
|
mask-mode: luminance;
|
||||||
|
mask-size: 100%;
|
||||||
|
mix-blend-mode: soft-light;
|
||||||
}
|
}
|
||||||
@@ -1,354 +1,51 @@
|
|||||||
window.addEventListener("DOMContentLoaded", () => {
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
const websocket = new ReconnectingWebSocket("wss://lilac.thewisewolf.dev/tracker/timer/ws");
|
//Create progress bar
|
||||||
communicate(websocket);
|
const book_bar = new ProgressBar();
|
||||||
|
//Pat Lilac
|
||||||
|
const websocket = new ReconnectingWebSocket("wss://lilac.thewisewolf.dev/campaigns/partnerplus2026/ws");
|
||||||
|
communicate(websocket, book_bar);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
class Timer {
|
class ProgressBar {
|
||||||
constructor(renderer, timer_data) {
|
|
||||||
this.renderer = renderer;
|
|
||||||
this.destroying = false;
|
|
||||||
this.ended = false;
|
|
||||||
|
|
||||||
this.update_from_data(timer_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
update_from_data(timer_data) {
|
|
||||||
this.end_at = new Date(timer_data.end_at);
|
|
||||||
this.running = timer_data.is_running;
|
|
||||||
this.name = timer_data.name;
|
|
||||||
this.score_table = timer_data.score_table;
|
|
||||||
|
|
||||||
this.total_contribution = timer_data.total_contribution;
|
|
||||||
this.total_subpoints = timer_data.total_subpoints;
|
|
||||||
this.total_bits = timer_data.total_bits;
|
|
||||||
|
|
||||||
this.goals_met = timer_data.goals_met;
|
|
||||||
this.goals_total = timer_data.goals_total;
|
|
||||||
this.last_goal = timer_data.last_goal;
|
|
||||||
this.next_goal = timer_data.next_goal;
|
|
||||||
this.goals = timer_data.goals;
|
|
||||||
|
|
||||||
this.last_contribution = timer_data.last_contribution;
|
|
||||||
this.leaderboard = timer_data.leaderboard;
|
|
||||||
}
|
|
||||||
|
|
||||||
check_ended() {
|
|
||||||
if (!this.ended && this.end_at < new Date()) {
|
|
||||||
this.ended = true;
|
|
||||||
this.renderer.finale();
|
|
||||||
}
|
|
||||||
return this.ended;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_ended() {
|
|
||||||
if (!this.ended && this.end_at < new Date()) {
|
|
||||||
this.ended = true;
|
|
||||||
this.renderer.finale();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render_time() {
|
|
||||||
if (this.check_ended()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.renderer.render_time(
|
|
||||||
Math.floor((this.end_at - new Date()) / 1000)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.ended) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Render goal
|
|
||||||
if (this.next_goal != null) {
|
|
||||||
this.renderer.render_current_goal(
|
|
||||||
this.next_goal.name,
|
|
||||||
this.total_contribution,
|
|
||||||
this.next_goal.required,
|
|
||||||
);
|
|
||||||
this.renderer.animate();
|
|
||||||
} else if (this.last_goal != null) {
|
|
||||||
this.renderer.render_current_goal(
|
|
||||||
this.last_goal.name,
|
|
||||||
this.total_contribution,
|
|
||||||
this.last_goal.required,
|
|
||||||
);
|
|
||||||
this.renderer.animate();
|
|
||||||
} else {
|
|
||||||
this.renderer.clear_current_goal();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render leaderboard
|
|
||||||
this.renderer.render_users(this.leaderboard);
|
|
||||||
this.renderer.animate();
|
|
||||||
|
|
||||||
// Render timer
|
|
||||||
this.render_time();
|
|
||||||
|
|
||||||
this.set_ended();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursive call run per second to update the rendered timer
|
|
||||||
*/
|
|
||||||
tick() {
|
|
||||||
if (!this.destroying) {
|
|
||||||
this.render_time();
|
|
||||||
|
|
||||||
if (this.running) {
|
|
||||||
setTimeout(this.tick.bind(this), 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TimerRenderer {
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// HH:MM or MM:SS
|
this.current_progress = 0;
|
||||||
this.timer = document.getElementById("Timer");
|
this.max_progress = 45;
|
||||||
|
this.target_element = document.getElementById("progressBar");
|
||||||
// Current Goal:
|
this.bar_back = document.getElementById("barBack");
|
||||||
this.goal_label = document.getElementById("GoalLabel")
|
this.bar_front = document.getElementById("barFront");
|
||||||
|
|
||||||
// Name of the goal to display
|
|
||||||
this.goal_name = document.getElementById("GoalName")
|
|
||||||
this.goal_namedupe = document.getElementById("GoalNameDupe")
|
|
||||||
|
|
||||||
// 1524/3000 Points
|
|
||||||
this.goal_progress = document.getElementById("GoalProgress")
|
|
||||||
this.goal_bar = document.getElementById("GoalProgressBar")
|
|
||||||
|
|
||||||
// Top 3 Gifters
|
|
||||||
this.topusers_label = document.getElementById("TopUsers")
|
|
||||||
|
|
||||||
// Leaderboard items
|
|
||||||
// 1. (or points?) (1 as in 1st place, static)
|
|
||||||
this.topusers_user1_num = document.getElementById("GiftUserNum1")
|
|
||||||
// Name of user 1
|
|
||||||
this.topusers_user1_name = document.getElementById("GiftUserName1")
|
|
||||||
this.topusers_userdupe1_name = document.getElementById("GiftUserNameDupe1")
|
|
||||||
this.topusers_user1_score = document.getElementById("GiftUserScore1")
|
|
||||||
this.topusers_user2_num = document.getElementById("GiftUserNum2")
|
|
||||||
this.topusers_user2_name = document.getElementById("GiftUserName2")
|
|
||||||
this.topusers_userdupe2_name = document.getElementById("GiftUserNameDupe2")
|
|
||||||
this.topusers_user2_score = document.getElementById("GiftUserScore2")
|
|
||||||
this.topusers_user3_num = document.getElementById("GiftUserNum3")
|
|
||||||
this.topusers_user3_name = document.getElementById("GiftUserName3")
|
|
||||||
this.topusers_userdupe3_name = document.getElementById("GiftUserNameDupe3")
|
|
||||||
this.topusers_user3_score = document.getElementById("GiftUserScore3")
|
|
||||||
|
|
||||||
// -- Animation --
|
|
||||||
// Left-fade wrappers
|
|
||||||
this.scrollwraps = document.getElementsByClassName("scrollWrap")
|
|
||||||
// Parent wrappers
|
|
||||||
this.scrollboxes = [document.getElementById("GiftUserRow1"),
|
|
||||||
document.getElementById("GiftUserRow2"),
|
|
||||||
document.getElementById("GiftUserRow3"),
|
|
||||||
document.getElementById("ScrollWrapGoal")]
|
|
||||||
// Leaderboard names
|
|
||||||
this.userboxes = [this.topusers_user1_name, this.topusers_user2_name, this.topusers_user3_name]
|
|
||||||
this.userdupeboxes = [this.topusers_userdupe1_name, this.topusers_userdupe2_name, this.topusers_userdupe3_name]
|
|
||||||
|
|
||||||
// Thank you!
|
|
||||||
this.thankyou = "Thank you! The subathon has now ended, thank you so much for contributions and viewership to celebrate!"
|
|
||||||
}
|
}
|
||||||
|
//SET NUMBER OF COLORED BOOKS
|
||||||
// -- Toggles animation if text overflows container and adjusts speed for uniform movement --
|
setProgress(val) {
|
||||||
animate() {
|
if (Number.isInteger(val) && (this.target_element != null) && (this.bar_back != null) && (this.bar_front != null)) {
|
||||||
|
let val_string = val.toString();
|
||||||
for (let i = 0; i < this.scrollboxes.length; i++) {
|
if (val_string.length < 2) {
|
||||||
// Check whether the itteration is a username or goal
|
val_string = "0" + val_string;
|
||||||
if (this.scrollboxes[i].id == 'ScrollWrapGoal') {
|
|
||||||
|
|
||||||
// Check if element text overflows
|
|
||||||
if (this.goal_name.getBoundingClientRect().width > (this.scrollboxes[i].getBoundingClientRect().width)) {
|
|
||||||
// Toggle animation
|
|
||||||
this.goal_name.classList.add("scrollAnim");
|
|
||||||
this.goal_namedupe.classList.add("scrollAnim");
|
|
||||||
this.goal_namedupe.style.display = 'inline-block';
|
|
||||||
this.scrollwraps[i].classList.add("fadeLeft");
|
|
||||||
// Set animation speed
|
|
||||||
this.goal_name.style.animationDuration = `${this.goal_name.getBoundingClientRect().width / 40}s`;
|
|
||||||
this.goal_namedupe.style.animationDuration = `${this.goal_name.getBoundingClientRect().width / 40}s`;
|
|
||||||
} else {
|
|
||||||
// Toggle animation
|
|
||||||
this.goal_name.classList.remove("scrollAnim");
|
|
||||||
this.goal_namedupe.classList.remove("scrollAnim");
|
|
||||||
this.goal_namedupe.style.display = 'none';
|
|
||||||
this.scrollwraps[i].classList.remove("fadeLeft");
|
|
||||||
}
|
}
|
||||||
} else {
|
this.bar_back.style.backgroundImage = "url(src/Books/BookBar_v5_f00" + val_string + ".png)";
|
||||||
|
setTimeout(this.preLoad.bind(this), 150, val_string, val);
|
||||||
// Check if element text overflows
|
|
||||||
if (this.userboxes[i].getBoundingClientRect().width > (this.scrollboxes[i].getBoundingClientRect().width * .65)) {
|
|
||||||
// Toggle animation
|
|
||||||
this.userboxes[i].classList.add("scrollAnim");
|
|
||||||
this.userdupeboxes[i].classList.add("scrollAnim");
|
|
||||||
this.userdupeboxes[i].style.display = 'inline-block';
|
|
||||||
this.scrollwraps[i].classList.add("fadeLeft");
|
|
||||||
// Set animation speed
|
|
||||||
this.userboxes[i].style.animationDuration = `${this.userboxes[i].getBoundingClientRect().width / 40}s`;
|
|
||||||
this.userdupeboxes[i].style.animationDuration = `${this.userboxes[i].getBoundingClientRect().width / 40}s`;
|
|
||||||
} else {
|
|
||||||
// Toggle animation
|
|
||||||
this.userboxes[i].classList.remove("scrollAnim");
|
|
||||||
this.userdupeboxes[i].classList.remove("scrollAnim");
|
|
||||||
this.userdupeboxes[i].style.display = 'none';
|
|
||||||
this.scrollwraps[i].classList.remove("fadeLeft");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
preLoad(val_string, new_val) {
|
||||||
|
this.bar_front.style.backgroundImage = "url(src/Books/BookBar_v5_f00" + val_string + ".png)";
|
||||||
|
if ((new_val >= 0) && (new_val <= this.max_progress)) {
|
||||||
|
this.current_progress = new_val;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.warn("Progress update is out of range.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//TEST ANIMATION
|
||||||
|
|
||||||
/**
|
simProgress() {
|
||||||
* Bring the display into a standard initial state.
|
if (this.current_progress < this.max_progress) {
|
||||||
*/
|
this.setProgress(this.current_progress + 1);
|
||||||
reset() {
|
setTimeout(this.simProgress.bind(this), 500);
|
||||||
this.timer.textContent = "88:88";
|
|
||||||
this.goal_label.textContent = "Next Goal:";
|
|
||||||
this.goal_name.textContent = "Goal name for the subathon";
|
|
||||||
this.goal_namedupe.textContent = "Goal name for the subathon";
|
|
||||||
this.goal_progress.textContent = "1234/1234";
|
|
||||||
|
|
||||||
this.topusers_label.textContent = "Leaderboard:";
|
|
||||||
this.topusers_user1_num.textContent = "1";
|
|
||||||
this.topusers_user1_name.textContent = "Username1024";
|
|
||||||
this.topusers_userdupe1_name.textContent = "Username1024";
|
|
||||||
this.topusers_user1_score.textContent = "0000";
|
|
||||||
this.topusers_user2_num.textContent = "2";
|
|
||||||
this.topusers_user2_name.textContent = "User";
|
|
||||||
this.topusers_userdupe2_name.textContent = "User";
|
|
||||||
this.topusers_user2_score.textContent = "0000";
|
|
||||||
this.topusers_user3_num.textContent = "3";
|
|
||||||
this.topusers_user3_name.textContent = "Username_Very_Super_Long12";
|
|
||||||
this.topusers_userdupe3_name.textContent = "Username_Very_Super_Long12";
|
|
||||||
this.topusers_user3_score.textContent = "0000";
|
|
||||||
}
|
}
|
||||||
|
if (this.current_progress == this.max_progress) {
|
||||||
// End subathon, change timer color, and show thank you message
|
setTimeout(() => this.setProgress(0), 5000);
|
||||||
finale() {
|
setTimeout(this.simProgress.bind(this), 5500);
|
||||||
this.timer.textContent = "00:00";
|
|
||||||
this.timer.style.color = '#82C8B6';
|
|
||||||
this.goal_label.textContent = "Completed!";
|
|
||||||
this.goal_name.textContent = this.thankyou;
|
|
||||||
this.goal_namedupe.textContent = this.thankyou;
|
|
||||||
this.animate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the timer from a time remaining.
|
|
||||||
*
|
|
||||||
* @param {integer} seconds_remaining - The number of seconds remaining on the timer.
|
|
||||||
*/
|
|
||||||
render_time(seconds_remaining) {
|
|
||||||
var timestr;
|
|
||||||
// Render a time remaining given in seconds
|
|
||||||
if (seconds_remaining <= 0) {
|
|
||||||
timestr = "00:00";
|
|
||||||
} else {
|
|
||||||
var seconds = seconds_remaining % 60;
|
|
||||||
seconds_remaining = seconds_remaining - seconds;
|
|
||||||
var minutes = Math.floor(seconds_remaining / 60);
|
|
||||||
var hours = Math.floor(minutes / 60);
|
|
||||||
minutes = minutes - 60 * hours;
|
|
||||||
|
|
||||||
// Change rendering mode based on hours or minutes left
|
|
||||||
if (hours > 0) {
|
|
||||||
timestr = String(hours).padStart(2, '0') + ':' + String(minutes).padStart(2, '0');
|
|
||||||
} else {
|
|
||||||
timestr = String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.timer.textContent = timestr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the current goal.
|
|
||||||
*
|
|
||||||
* @param {string} name - The name of the current goal.
|
|
||||||
* @param {number} current - The number of points we currently have.
|
|
||||||
* @param {number} required - The number of points to achieve the goal.
|
|
||||||
*/
|
|
||||||
render_current_goal(name, current, required) {
|
|
||||||
// TODO: Some kind of end condition, ability to clear a goal etc
|
|
||||||
// E.g. if subathon doesn't have a goal at all
|
|
||||||
// Or after we accomplish all goals
|
|
||||||
|
|
||||||
this.goal_name.textContent = name;
|
|
||||||
this.goal_namedupe.textContent = name;
|
|
||||||
//this.goal_name.style.animationDuration = `${this.goal_name.getBoundingClientRect().width / 50}s`;
|
|
||||||
|
|
||||||
// Bitwise OR done to convert floats to integers if needed
|
|
||||||
this.goal_progress.textContent = String(current | 0) + '/' + String(required | 0)
|
|
||||||
|
|
||||||
// Update the progress bar
|
|
||||||
if (current / required > .02) {
|
|
||||||
this.goal_bar.style.maskImage = `linear-gradient(to right, #FC98B3 0%, #FC98B3 ${((current / required) - .02) * 100}%, transparent ${(current / required) * 100}%)`;
|
|
||||||
} else {
|
|
||||||
this.goal_bar.style.maskImage = `linear-gradient(to right, #FC98B3 0%, transparent .01%)`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the current goal field.
|
|
||||||
*/
|
|
||||||
clear_current_goal() {
|
|
||||||
this.goal_name.textContent = "";
|
|
||||||
//this.goal_namedupe.textContent = "";
|
|
||||||
this.goal_progress.textContent = "";
|
|
||||||
this.goal_label.textContent = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the user leaderboard.
|
|
||||||
*
|
|
||||||
* @param {Object[]} users - The user leaderboard in descending (points, name) order.
|
|
||||||
* @param {integer} users[].amount - The number of points this users has contrib.
|
|
||||||
* @param {string} users[].user_name - The name of this user.
|
|
||||||
*/
|
|
||||||
render_users(users) {
|
|
||||||
|
|
||||||
if (users.length >= 1) {
|
|
||||||
this.topusers_user1_num.textContent = "1";
|
|
||||||
this.topusers_user1_name.textContent = users[0].user_name;
|
|
||||||
this.topusers_userdupe1_name.textContent = users[0].user_name;
|
|
||||||
this.topusers_user1_score.textContent = Math.round(users[0].amount);
|
|
||||||
//this.topusers_user1_name.style.animationDuration = `${this.topusers_user1_name.getBoundingClientRect().width / 50}s`;
|
|
||||||
} else {
|
|
||||||
this.topusers_user1_num.textContent = "1";
|
|
||||||
this.topusers_user1_name.textContent = "";
|
|
||||||
this.topusers_userdupe1_name.textContent = "";
|
|
||||||
this.topusers_user1_score.textContent = "0000";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (users.length >= 2) {
|
|
||||||
this.topusers_user2_num.textContent = "2";
|
|
||||||
this.topusers_user2_name.textContent = users[1].user_name;
|
|
||||||
this.topusers_userdupe2_name.textContent = users[1].user_name;
|
|
||||||
this.topusers_user2_score.textContent = Math.round(users[1].amount);
|
|
||||||
//this.topusers_user2_name.style.animationDuration = `${this.topusers_user2_name.getBoundingClientRect().width / 50}s`;
|
|
||||||
} else {
|
|
||||||
this.topusers_user2_num.textContent = "2";
|
|
||||||
this.topusers_user2_name.textContent = "";
|
|
||||||
this.topusers_userdupe2_name.textContent = "";
|
|
||||||
this.topusers_user2_score.textContent = "0000";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (users.length >= 3) {
|
|
||||||
this.topusers_user3_num.textContent = "3";
|
|
||||||
this.topusers_user3_name.textContent = users[2].user_name;
|
|
||||||
this.topusers_userdupe3_name.textContent = users[2].user_name;
|
|
||||||
this.topusers_user3_score.textContent = Math.round(users[2].amount);
|
|
||||||
//this.topusers_user3_name.style.animationDuration = `${this.topusers_user3_name.getBoundingClientRect().width / 50}s`;
|
|
||||||
} else {
|
|
||||||
this.topusers_user3_num.textContent = "3";
|
|
||||||
this.topusers_user3_name.textContent = "";
|
|
||||||
this.topusers_userdupe3_name.textContent = "";
|
|
||||||
this.topusers_user3_score.textContent = "0000";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -371,7 +68,7 @@ class ReconnectingWebSocket {
|
|||||||
this.ws = new WebSocket(this.url);
|
this.ws = new WebSocket(this.url);
|
||||||
|
|
||||||
this.ws.onopen = () => {
|
this.ws.onopen = () => {
|
||||||
console.log('WebSocket connected');
|
console.log('WebSocket connecting');
|
||||||
this.attempt = 0;
|
this.attempt = 0;
|
||||||
if (this.onopen != null) {
|
if (this.onopen != null) {
|
||||||
this.onopen();
|
this.onopen();
|
||||||
@@ -389,8 +86,9 @@ class ReconnectingWebSocket {
|
|||||||
if (this.closing) {
|
if (this.closing) {
|
||||||
console.log('Closing Websocket.')
|
console.log('Closing Websocket.')
|
||||||
} {
|
} {
|
||||||
console.warn('WebSocket closed. Reconnecting...');
|
console.warn('WebSocket closed. Reconnecting... (disabled for now)');
|
||||||
this.reconnect();
|
//this.reconnect();
|
||||||
|
//TODO turn that back on ^
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -438,13 +136,11 @@ class ReconnectingWebSocket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function communicate(websocket, book_bar) {
|
||||||
|
|
||||||
function communicate(websocket) {
|
|
||||||
console.log("Communicating");
|
console.log("Communicating");
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
var renderer = new TimerRenderer();
|
// INITIALIZE CLASS HERE
|
||||||
let timer;
|
book_bar.simProgress();
|
||||||
|
|
||||||
websocket.onopen = () => {
|
websocket.onopen = () => {
|
||||||
websocket.send(
|
websocket.send(
|
||||||
@@ -456,11 +152,10 @@ function communicate(websocket) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
renderer.reset();
|
|
||||||
renderer.animate();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
websocket.onmessage = ({ data }) => {
|
websocket.onmessage = ({ data }) => {
|
||||||
|
console.log("messaged");
|
||||||
const event = JSON.parse(data);
|
const event = JSON.parse(data);
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "DO":
|
case "DO":
|
||||||
@@ -469,26 +164,18 @@ function communicate(websocket) {
|
|||||||
// Call the specified method
|
// Call the specified method
|
||||||
switch (event.method) {
|
switch (event.method) {
|
||||||
case "setTimer":
|
case "setTimer":
|
||||||
if (timer == null) {
|
//do something with the new data
|
||||||
timer = new Timer(renderer, args);
|
console.log("set timer: " + event);
|
||||||
} else {
|
book_bar.setProgress(event.reward_progress);
|
||||||
timer.update_from_data(args);
|
|
||||||
}
|
|
||||||
timer.render();
|
|
||||||
timer.tick();
|
|
||||||
break;
|
break;
|
||||||
case "noTimer":
|
case "noTimer":
|
||||||
if (timer != null) {
|
//do something default
|
||||||
timer.destroying = true;
|
console.log("no timer: " + event);
|
||||||
timer = null;
|
break;
|
||||||
}
|
|
||||||
renderer.reset();
|
|
||||||
renderer.animate();
|
|
||||||
case "endTimer":
|
case "endTimer":
|
||||||
if (timer != null) {
|
//do something for the final time
|
||||||
timer.destroying = true;
|
console.log("end timer: " + event);
|
||||||
timer = null;
|
break
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported method requested: ${event.method}.`)
|
throw new Error(`Unsupported method requested: ${event.method}.`)
|
||||||
}
|
}
|
||||||
|
|||||||