summaryrefslogtreecommitdiff
path: root/index.html
diff options
context:
space:
mode:
Diffstat (limited to 'index.html')
-rw-r--r--index.html1236
1 files changed, 618 insertions, 618 deletions
diff --git a/index.html b/index.html
index fb29374..df57955 100644
--- a/index.html
+++ b/index.html
@@ -1,619 +1,619 @@
-<!DOCTYPE html>
-<!--suppress HtmlFormInputWithoutLabel, JSUndeclaredVariable -->
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <title>Bits</title>
- <script>
- isNodeJS = typeof require === "function";
- </script>
- <style>
- :root {
- --perc-color: black;
- }
- * {
- user-select: none;
- overflow-x: hidden !important;
- }
- body {
- color: white;
- font-family: sans-serif;
- }
- .ln:hover {
- text-decoration: underline;
- }
- .ln:active {
- opacity: .75;
- }
- .transaction {
- transition: background-color 100ms;
- }
- .transaction:hover {
- background-color: rgba(0, 0, 0, .1);
- }
-
- @keyframes shake {
- 0% {
- transform: scale(1);
- }
- 100% {
- transform: scale(1.5);
- }
- }
- </style>
- <link rel="stylesheet" href="./assets/black.css" disabled id="mobile-css">
- <script src="./chart.js"></script>
- <script src="./trendline.js"></script>
-</head>
-<body style="position:fixed;inset:0;">
- <div id="loader" style="background:#222;transition:opacity 500ms;z-index:9999;position:fixed;inset:0;display:flex;align-items: center;justify-content: center;">
- <img alt="" src="assets/logo.svg" style="width:96px;">
- <script>
- window.onload = async () => {
- if (!isNodeJS) {
- document.getElementById("mobile-css").removeAttribute("disabled");
- }
-
- setTimeout(async () => {
- loginStatus = JSON.parse(await (await window.fetch("https://money-v1.equestria.dev/Authentication/Test/")).text()).status;
- if (loginStatus === 1) {
- if (isNodeJS) {
- console.info("Starting authentication procedure (Electron)");
- const { ipcRenderer } = require('electron');
- ipcRenderer.send("login");
- } else {
- console.info("Starting authentication procedure (Mobile)");
- location.href = "https://money-v1.equestria.dev/Authentication/Mobile/"
- }
- } else {
- console.info("Authenticated successfully");
-
- await refresh();
- }
- }, 1000)
- }
-
- async function refresh() {
- document.getElementById("transactions").innerHTML = "";
- document.getElementById("username").innerText = JSON.parse(await (await window.fetch("https://money-v1.equestria.dev/Authentication/Username/")).text()).name;
- document.getElementById("user-profile").src = "https://account.minteck.org/hub/api/rest/avatar/" + JSON.parse(await (await window.fetch("https://money-v1.equestria.dev/Authentication/Username/")).text()).id + "?dpr=2&size=48";
-
- window.transactions = JSON.parse(await (await window.fetch("https://money-v1.equestria.dev/Application/TransactionsList/")).text());
- window.goal = JSON.parse(await (await window.fetch("https://money-v1.equestria.dev/Application/GetGoal/")).text());
-
- for (let transaction of transactions) {
- demo = document.getElementById("demo-transaction");
- demo.id = "";
-
- if (transaction.type === "pay") {
- word = "used";
- color = "#b31500";
- } else {
- word = "added";
- color = "#0065b3";
- }
-
- if (transaction.amount['original'] === "eur") {
- baseCurrency = transaction.amount['eur'].toFixed(2) + "€";
- convertedCurrency = "£" + transaction.amount['gbp'].toFixed(2);
- } else {
- baseCurrency = "£" + transaction.amount['gbp'].toFixed(2);
- convertedCurrency = transaction.amount['eur'].toFixed(2) + "€";
- }
-
- document.getElementById("transactions").innerHTML += demo.outerHTML
- .replace("%user%", transaction.author.name)
- .replace("%picture%", "\" src=\"" + transaction.author.avatar + "\"")
- .replace("%time%", transaction.date.relative)
- .replace("%transactionId%", transaction.date.absolute)
- .replace("%description%", transaction.description.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;"))
- .replace("%type%", word)
- .replace("%amount_bc%", baseCurrency)
- .replace("%amount_cc%", convertedCurrency)
- .replace("%type%", word)
- .replace("var(--perc-color)", color)
-
- demo.id = "demo-transaction";
- }
-
- try {
- totalPaidEUR = transactions.filter(i => i.type === "pay").map(i => { return i.amount['eur']; }).reduce((a, b) => { return a+b; });
- } catch (e) {
- totalPaidEUR = 0;
- }
-
- try {
- totalPaidGBP = transactions.filter(i => i.type === "pay").map(i => { return i.amount['gbp']; }).reduce((a, b) => { return a+b; });
- } catch (e) {
- totalPaidGBP = 0;
- }
-
- try {
- totalGainedEUR = transactions.filter(i => i.type !== "pay").map(i => { return i.amount['eur']; }).reduce((a, b) => { return a+b; });
- } catch (e) {
- totalGainedEUR = 0;
- }
-
- try {
- totalGainedGBP = transactions.filter(i => i.type !== "pay").map(i => { return i.amount['gbp']; }).reduce((a, b) => { return a+b; });
- } catch (e) {
- totalGainedGBP = 0;
- }
-
- totalEUR = totalGainedEUR - totalPaidEUR;
- totalGBP = totalGainedGBP - totalPaidGBP;
-
- document.getElementById("balance-eur").innerText = totalEUR.toFixed(2);
- document.getElementById("balance-gbp").innerText = totalGBP.toFixed(2);
-
- document.getElementById("goal-name").innerText = goal.name;
- document.getElementById("goal-amount-eur").innerText = goal.amount['eur'].toFixed(2);
- document.getElementById("goal-amount-gbp").innerText = goal.amount['gbp'].toFixed(2);
-
- if (goal.amount['eur'] === 0 || goal.amount['gbp'] === 0) {
- document.getElementById("goal-amount-percentage").innerText = "N/A";
- document.getElementById("goal-bar-fill").style.width = "0%";
- } else {
- percentage = (totalEUR / goal.amount['eur']) * 100;
- document.getElementById("goal-amount-percentage").innerText = percentage.toFixed(2);
- document.getElementById("goal-bar-fill").style.width = percentage.toFixed(5) + "%";
- }
-
- document.getElementById("app").style.display = "";
- document.getElementById("loader").style.opacity = "0";
- document.getElementById("loader").style.pointerEvents = "none";
-
- for (let item of Array.from(document.getElementsByClassName("transaction"))) {
- item.onclick = () => {
- deleteTransaction(item.getAttribute("data-transaction-id"));
- }
- }
-
- graph.data.labels = transactions.map((i) => { return new Date(i.date.absolute).toString().split(":")[0] + ":" + new Date(i.date.absolute).toString().split(":")[1]; }).reverse().filter((_, i) => i > 1);
-
- let last = 0;
- let balances = [];
-
- transactions.map((i) => { if (i.type === "pay") { return -(i.amount.eur); } else { return i.amount.eur; } }).reverse().map((i) => {
- last = last + i;
- balances.push(last);
- });
- graph.data.datasets[0].data = balances.filter((_, i) => i > 1);
-
- trendData = balances.filter((_, i) => i > 1).map((i, _) => { return { x: _, y: i } });
- trend = trendline(trendData, 'x', 'y');
-
- let lastTrend = trend.yStart;
- let balancesTrend = [];
-
- transactions.filter((_, i) => i > 1).map(() => {
- lastTrend = lastTrend + trend.slope;
- balancesTrend.push(lastTrend);
- });
-
- graph.data.datasets[1].data = balancesTrend;
-
- document.getElementById("graph-insights-color").style.backgroundColor = "black";
- document.getElementById("graph-insights-text").style.color = "black";
- document.getElementById("graph-insights-text").innerText = "No insights available. Please try again later.";
-
- let avgSlope = Math.round(trend.slope);
- if (avgSlope < -3) {
- document.getElementById("graph-insights-color").style.backgroundColor = "red";
- document.getElementById("graph-insights-text").style.color = "red";
- document.getElementById("graph-insights-text").innerText = "Money is going down faster than it should, you must immediately reduce your expenses.";
- } else if (avgSlope < 0) {
- document.getElementById("graph-insights-color").style.backgroundColor = "orange";
- document.getElementById("graph-insights-text").style.color = "orange";
- document.getElementById("graph-insights-text").innerText = "You are not saving money, consider reducing your expenses.";
- } else if (avgSlope === 0) {
- document.getElementById("graph-insights-color").style.backgroundColor = "green";
- document.getElementById("graph-insights-text").style.color = "green";
- document.getElementById("graph-insights-text").innerText = "Your balance is stable, consider saving more money.";
- } else if (avgSlope > 0) {
- document.getElementById("graph-insights-color").style.backgroundColor = "sky";
- document.getElementById("graph-insights-text").style.color = "sky";
- document.getElementById("graph-insights-text").innerText = "You are effectively saving money.";
- }
-
- if (avgSlope < 0) {
- it = 0; b = totalEUR; while (b > 0) { it++; b = b + trend.slope; }
- let avgDelay = Math.round(averageDelta(transactions.map((i) => { return new Date(i.date.absolute).getTime() }).reverse()));
- let timeUntilEmpty = avgDelay * it;
- let date = new Date(new Date().getTime() + timeUntilEmpty).toString().split(":")[0];
- document.getElementById("graph-insights-text").innerText += " (reaching zero on " + date.substring(0, date.length - 3) + ")";
- } else {
- it = 0; b = totalEUR; while (b < goal.amount.eur) { it++; b = b + trend.slope; }
- let avgDelay = Math.round(averageDelta(transactions.map((i) => { return new Date(i.date.absolute).getTime() }).reverse()));
- let timeUntilGoal = avgDelay * it;
- let date = new Date(new Date().getTime() + timeUntilGoal).toString().split(":")[0];
- document.getElementById("graph-insights-text").innerText += " (reaching goal on " + date.substring(0, date.length - 3) + ")";
- }
- }
-
- async function createTransaction() {
- document.getElementById('create-action').disabled = true;
- document.getElementById('create-currency').disabled = true;
- document.getElementById('create-description').disabled = true;
- document.getElementById('create-amount').disabled = true;
- document.getElementById('create-button-create').disabled = true;
- document.getElementById('create-button-cancel').disabled = true;
-
- if (isNodeJS) {
- await (await window.fetch("https://money-v1.equestria.dev/Application/AddTransaction/?Currency=" + document.getElementById('create-currency').value + "&Amount=" + document.getElementById('create-amount').value + "&Operation=" + document.getElementById('create-action').value + "&Description=" + Buffer.from(document.getElementById('create-description').value).toString("base64url"))).text();
- } else {
- await (await window.fetch("https://money-v1.equestria.dev/Application/AddTransaction/?Currency=" + document.getElementById('create-currency').value + "&Amount=" + document.getElementById('create-amount').value + "&Operation=" + document.getElementById('create-action').value + "&Description=" + btoa(document.getElementById('create-description').value).replaceAll("+", "-").replaceAll("/", "_"))).text();
- }
-
- await refresh();
-
- document.getElementById('create-action').disabled = false;
- document.getElementById('create-currency').disabled = false;
- document.getElementById('create-description').disabled = false;
- document.getElementById('create-amount').disabled = false;
- document.getElementById('create-button-create').disabled = false;
- document.getElementById('create-button-cancel').disabled = false;
- document.getElementById('create-modal').style.display = 'none';
- document.getElementById('create-action').value = '+';
- document.getElementById('create-currency').value = '£';
- document.getElementById('create-description').value = '';
- document.getElementById('create-amount').value = '';
- }
-
- function showConfirm(text) {
- document.getElementById("confirm-modal").style.display = isNodeJS ? "flex" : "";
- document.getElementById("confirm-text").innerText = text;
- document.getElementById('confirm-button-cancel').disabled = false;
- document.getElementById('confirm-button-ok').disabled = false;
- }
-
- confirmAction = () => {
- console.log("Confirmed!");
- document.getElementById('confirm-modal').style.display = 'none';
- }
-
- async function deleteTransaction(id) {
- let transaction = transactions.filter(i => i.date.absolute === id)[0];
- console.log(transaction);
- showConfirm("This will remove the transaction made by " + transaction.author.name + " " + transaction.date.relative + ".\nIt will be removed from the list and the global balance will be recalculated without this transaction.");
- confirmAction = async () => {
- if (isNodeJS) {
- await (await window.fetch("https://money-v1.equestria.dev/Application/RemoveTransaction/?Transaction=" + Buffer.from(id).toString("base64url"))).text();
- } else {
- await (await window.fetch("https://money-v1.equestria.dev/Application/RemoveTransaction/?Transaction=" + btoa(id).replaceAll("+", "-").replaceAll("/", "_"))).text();
- }
- await refresh();
- document.getElementById('confirm-modal').style.display = 'none';
- }
- }
- </script>
- </div>
- <script>
- if (!isNodeJS) {
- document.getElementById("loader").style.backgroundColor = "#000000";
- }
- </script>
- <div id="app" style="display:none;position:fixed;z-index:3;">
- <div id="header" style="z-index:5;background: #111;position: fixed;top: 0;padding: 8px 30px;left: 0;right: 0;height: 32px;">
- <img alt="Bits" src="assets/logo.svg" style="width:32px;vertical-align: middle;">
- <span style="vertical-align: middle;" id="menu-desktop">
- <a class="ln" style="cursor:pointer;margin-left:20px;" onclick="document.getElementById('create-modal').style.display = isNodeJS ? 'flex' : '';">New transaction</a><a class="ln" id="tab-0" style="cursor:pointer;margin-left:20px;" onclick="document.getElementById('list').style.display = 'none';document.getElementById('tab-0').style.display = 'none';document.getElementById('tab-1').style.display = '';document.getElementById('graph').style.display = '';">Statistics</a><a class="ln" id="tab-1" style="display:none;cursor:pointer;margin-left:20px;" onclick="document.getElementById('list').style.display = '';document.getElementById('tab-0').style.display = '';document.getElementById('tab-1').style.display = 'none';document.getElementById('graph').style.display = 'none';">Transactions</a><a class="ln" onclick="document.getElementById('about-modal').style.display = isNodeJS ? 'flex' : '';" style="cursor:pointer;margin-left:20px;">About</a>
- </span>
- <span style="vertical-align: middle;display:none;" id="menu-mobile">
- <a class="ln" style="cursor:pointer;margin-left:20px;" onclick="document.getElementById('create-modal').style.display = isNodeJS ? 'flex' : '';">Add</a><a class="ln" id="tab-0m" style="cursor:pointer;margin-left:20px;" onclick="document.getElementById('list').style.display = 'none';document.getElementById('tab-0m').style.display = 'none';document.getElementById('tab-1m').style.display = '';document.getElementById('graph').style.display = '';">Stats</a><a class="ln" id="tab-1m" style="display:none;cursor:pointer;margin-left:20px;" onclick="document.getElementById('list').style.display = '';document.getElementById('tab-0m').style.display = '';document.getElementById('tab-1m').style.display = 'none';document.getElementById('graph').style.display = 'none';">List</a><a class="ln" onclick="document.getElementById('about-modal').style.display = isNodeJS ? 'flex' : '';" style="cursor:pointer;margin-left:20px;">About</a>
- </span>
- <span style="float: right;margin-right: 10px;margin-top: 7px;" id="total-data">
- <span id="balance" style="opacity: .5;">
- <span id="balance-eur">%total_eur%</span>€, £<span id="balance-gbp">%total_gbp%</span>
- </span><span id="username-desktop" style="opacity:.5;"> · <span id="username">%user%</span></span><span id="username-mobile" style="display:none;vertical-align: middle;"><img id="user-profile" alt="" style="border-radius: 999px;width: 32px;height: 32px;vertical-align: middle;position:relative;top:-6px;display:inline-block;margin-left:7px;" src=""></span>
- </span>
- </div>
- <script>
- if (!isNodeJS) {
- document.getElementById("menu-desktop").style.display = "none";
- document.getElementById("menu-mobile").style.display = "";
- document.getElementById("username-desktop").style.display = "none";
- document.getElementById("username-mobile").style.display = "";
- document.getElementById("header").style.padding = "8px";
- document.getElementById("header").style.backgroundColor = "#000000";
- document.getElementById("balance").style.position = "relative";
- document.getElementById("balance").style.top = "-4px";
- }
- </script>
- <div id="view" style="z-index:5;color:black;background:rgb(247, 249, 250);position:fixed;top:48px;left:0;right:0;bottom:64px;">
- <div id="list" style="position: fixed;top: 48px;left: 0;bottom: 64px;right:0;border-right: 1px solid rgba(0, 0, 0, .25);">
- <div id="demo-zone" style="display:none;">
- <div class="transaction" data-transaction-id="%transactionId%" id="demo-transaction" style="padding:10px;border-bottom: 1px solid rgba(0, 0, 0, .25);display:grid;grid-template-columns: 48px 1fr;grid-column-gap: 15px;">
- <div class="transaction-user" style="display:flex;align-items: center;justify-content: center;">
- <img alt="%picture%" style="border-radius: 999px;width: 48px;height: 48px;">
- </div>
- <div class="transaction-details">
- <b>%user%</b> %type% <span style="color:var(--perc-color);"><b>%amount_bc%</b> <i>(%amount_cc%)</i></span><br>
- %time%<br>
- <div style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;width: 100%;">%description%</div>
- </div>
- </div>
- </div>
- <div id="transactions" style="overflow: scroll;height: 100%;"></div>
- </div>
-
- <div id="graph" style="display:none;width:100%;height:100%;">
- <p style="
- font-size: 14px;
- margin: 14px;
-">
- <span id="graph-insights-color" style="display: inline-block;width: 12px;height: 12px;border-radius: 999px;opacity: .75;background: black;vertical-align: middle;margin-right: 5px;"></span>
- <span id="graph-insights-text" style="
- display: inline-block;
- vertical-align: middle;
-">No insights available. Please try again later.</span>
- </p>
- <canvas id="graph-display" style="width:100%;height:100%;"></canvas>
- <script>
- const ctx = document.getElementById('graph-display').getContext('2d');
- const graph = new Chart(ctx, {
- type: 'line',
- data: {
- labels: [],
- datasets: [{
- label: 'Balance',
- data: [],
- borderColor: 'rgba(153, 102, 255, 1)',
- fill: false,
- },
- {
- label: 'Trendline',
- data: [],
- borderColor: 'rgba(153, 102, 255, .5)',
- borderDash: [5],
- pointRadius: 0,
- fill: false,
- }]
- },
- options: {
- scales: {
- y: {
- beginAtZero: true
- }
- },
- scaleLabel: "<%=value%>%",
- legend: {
- display: false
- },
- tooltips: {
- callbacks: {
- label: function(tooltipItem, data) {
- return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index] + '€';
- }
- }
- }
- }
- });
- </script>
- </div>
- </div>
-
- <div id="create-modal" style="
- display: none;
- position: fixed;
- z-index: 999;
- inset: 0;
- background: rgba(0, 0, 0, .75);
- backdrop-filter: blur(10px);
- align-items: center;
- justify-content: center;
- color: black;
-"><div id="create-modal-inner" style="
- background: whitesmoke;
- width: 500px;
- padding: 10px;
- border-radius: 5px;
- box-shadow: 0 0 20px rgba(0, 0, 0, .5);
-">
- <h3 style="
- margin: 0;
- margin-bottom: 20px;
- margin-left: -10px;
- margin-right: -10px;
- text-align: center;
- margin-top: -10px;
- padding: 10px;
- background: rgba(0, 0, 0, .1);
-">New transaction</h3>
- Amount: <select id="create-action"><option>+</option><option>-</option></select>&nbsp;
- <input id="create-amount" type="number" max="99999">&nbsp;
- <select id="create-currency"><option>£</option><option>€</option></select><br><br>Description: <input maxlength="100" id="create-description" type="text" style="
- display: block;
- margin-top: 5px;
- width: calc(100% - 10px);
-">
- <div style="
- display: block;
- margin-top: 20px;
- margin-left: auto;
- margin-right: auto;
-width:max-content;">
- <button id="create-button-create" onclick="createTransaction();">Create</button>&nbsp;<button id="create-button-cancel" onclick="document.getElementById('create-modal').style.display = 'none';document.getElementById('create-action').value='+';document.getElementById('create-currency').value='£';document.getElementById('create-description').value='';document.getElementById('create-amount').value='';">Cancel</button>
- </div>
- </div></div>
-
- <div id="confirm-modal" style="
- display: none;
- position: fixed;
- z-index: 999;
- inset: 0;
- background: rgba(0, 0, 0, .75);
- backdrop-filter: blur(10px);
- align-items: center;
- justify-content: center;
- color: black;
-"><div id="confirm-modal-inner" style="
- background: whitesmoke;
- width: 500px;
- padding: 10px;
- border-radius: 5px;
- box-shadow: 0 0 20px rgba(0, 0, 0, .5);
-">
- <h3 style="
- margin: 0;
- margin-bottom: 20px;
- margin-left: -10px;
- margin-right: -10px;
- text-align: center;
- margin-top: -10px;
- padding: 10px;
- background: rgba(0, 0, 0, .1);
-">Confirm action</h3>
- <p id="confirm-text">Are you sure you want to do this?</p>
- <div style="
- display: block;
- margin-top: 20px;
- margin-left: auto;
- margin-right: auto;
-width:max-content;">
- <button id="confirm-button-ok" onclick="document.getElementById('confirm-button-cancel').disabled = true;document.getElementById('confirm-button-ok').disabled = true;confirmAction();">OK</button>&nbsp;<button id="confirm-button-cancel" onclick="document.getElementById('confirm-modal').style.display = 'none';">Cancel</button>
- </div>
- </div></div>
-
-
- <div id="about-modal" style="
- display: none;
- position: fixed;
- z-index: 999;
- inset: 0;
- background: rgba(0, 0, 0, .75);
- backdrop-filter: blur(10px);
- align-items: center;
- justify-content: center;
- color: black;
-"><div id="about-modal-inner" style="
- background: whitesmoke;
- width: 500px;
- padding: 10px;
- border-radius: 5px;
- box-shadow: 0 0 20px rgba(0, 0, 0, .5);
-">
- <h3 style="
- margin: 0;
- margin-bottom: 20px;
- margin-left: -10px;
- margin-right: -10px;
- text-align: center;
- margin-top: -10px;
- padding: 10px;
- background: rgba(0, 0, 0, .1);
-">About Bits</h3>
- <div id="about-text">
- <div style="font-weight: bold;text-align:center;margin-bottom:5px;">Bits</div>
- <b>Server:</b> money-v1.equestria.dev<br>
- <b>Platform:</b> <span id="about-platform"></span><script>document.getElementById("about-platform").innerText = isNodeJS ? "Desktop (Electron, NodeJS)" : "Android (Chrome, Android WebView)";</script><br>
- <b>User Agent:</b> <span id="about-useragent"></span><script>document.getElementById("about-useragent").innerText = navigator.userAgent;</script></div>
- <div style="
- display: block;
- margin-top: 20px;
- margin-left: auto;
- margin-right: auto;
-width:max-content;">
- <button id="about-button-cancel" onclick="document.getElementById('about-modal').style.display = 'none';">Close</button>
- </div>
- </div></div>
-
- <div id="goal" style="
- position: fixed;
- background: rgb(64, 64, 64);
- bottom: 0;
- left: 0;
- right: 0;
- padding: 10px;
- z-index: 2;
-"><b id="goal-name">%goal%</b> <span style="
- float: right;
-"><span id="goal-amount-eur">%goal_eur%</span>€, £<span id="goal-amount-gbp">%goal_gbp%</span> · <span id="goal-amount-percentage">%goal_percentage%</span>% completed</span><span id="goal-bar" style="
- display: block;
- height: 16px;
- margin-top: 10px;
- border-radius: 999px;
- background: rgba(0, 0, 0, .25);
-"><span id="goal-bar-fill" style="
- height: 16px;
- background: linear-gradient(90deg, rgba(183,153,201,1) 0%, rgba(87,58,152,1) 25%, rgba(204,129,148,1) 50%, rgba(235,186,115,1) 75%, rgba(169,83,144,1) 100%);
- width: 0;
- display: block;
- border-radius: 999px;
-"></span></span></div>
- </div>
-<script>
- if (!isNodeJS) {
- document.getElementById("create-modal-inner").style.borderRadius = "0";
- document.getElementById("create-modal-inner").style.borderBottomLeftRadius = "5px";
- document.getElementById("create-modal-inner").style.borderBottomRightRadius = "5px";
- document.getElementById("create-modal-inner").style.width = "calc(100% - 30px)";
- document.getElementById("confirm-modal-inner").style.borderRadius = "0";
- document.getElementById("confirm-modal-inner").style.width = "calc(100% - 30px)";
- document.getElementById("confirm-modal-inner").style.borderBottomLeftRadius = "5px";
- document.getElementById("confirm-modal-inner").style.borderBottomRightRadius = "5px";
- }
-
- f13presses = 0;
- document.onkeydown = (event) => {
- if (event.keyCode === 124) {
- if (require('os').platform() === "win32") {
- alert("haha you're using an inferior OS! Doing F13 on here is too easy so you might have expected " +
- "something but you're not going to get it.")
- } else {
- f13presses++;
-
- switch (f13presses) {
- case 1:
- Array.from(document.querySelectorAll("*")).forEach((item) => {
- item.style.fontFamily = "'Noto Sans Symbols', 'Wingdings', 'Webdings', monospace";
- });
- break;
-
- case 2:
- Array.from(document.querySelectorAll("*")).forEach((item) => {
- item.style.background = "red";
- });
- break;
-
- case 3:
- Array.from(document.querySelectorAll("*")).forEach((item) => {
- item.style.color = "blue";
- });
- break;
-
- case 4:
- Array.from(document.querySelectorAll("img")).forEach((item) => {
- item.src = "";
- item.style.color = "yellow";
- });
- break;
-
- case 5:
- Array.from(document.querySelectorAll("*")).forEach((item) => {
- item.style.transition = "transform 200ms";
- item.style.animationName = "shake";
- item.style.animationDuration = "200ms";
- item.style.animationIterationCount = "infinite";
- item.style.animationDirection = "alternate-reverse";
- });
- break;
-
- case 6:
- Array.from(document.querySelectorAll("*")).forEach((item) => {
- item.style.display = "none";
- });
- break;
-
- default:
- Array.from(document.querySelectorAll("*")).forEach((item) => {
- window.close();
- });
- break;
- }
- }
- }
- }
-</script>
-</body>
+<!DOCTYPE html>
+<!--suppress HtmlFormInputWithoutLabel, JSUndeclaredVariable -->
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Bits</title>
+ <script>
+ isNodeJS = typeof require === "function";
+ </script>
+ <style>
+ :root {
+ --perc-color: black;
+ }
+ * {
+ user-select: none;
+ overflow-x: hidden !important;
+ }
+ body {
+ color: white;
+ font-family: sans-serif;
+ }
+ .ln:hover {
+ text-decoration: underline;
+ }
+ .ln:active {
+ opacity: .75;
+ }
+ .transaction {
+ transition: background-color 100ms;
+ }
+ .transaction:hover {
+ background-color: rgba(0, 0, 0, .1);
+ }
+
+ @keyframes shake {
+ 0% {
+ transform: scale(1);
+ }
+ 100% {
+ transform: scale(1.5);
+ }
+ }
+ </style>
+ <link rel="stylesheet" href="./assets/black.css" disabled id="mobile-css">
+ <script src="./chart.js"></script>
+ <script src="./trendline.js"></script>
+</head>
+<body style="position:fixed;inset:0;">
+ <div id="loader" style="background:#222;transition:opacity 500ms;z-index:9999;position:fixed;inset:0;display:flex;align-items: center;justify-content: center;">
+ <img alt="" src="assets/logo.svg" style="width:96px;">
+ <script>
+ window.onload = async () => {
+ if (!isNodeJS) {
+ document.getElementById("mobile-css").removeAttribute("disabled");
+ }
+
+ setTimeout(async () => {
+ loginStatus = JSON.parse(await (await window.fetch("https://money-v1.equestria.dev/Authentication/Test/")).text()).status;
+ if (loginStatus === 1) {
+ if (isNodeJS) {
+ console.info("Starting authentication procedure (Electron)");
+ const { ipcRenderer } = require('electron');
+ ipcRenderer.send("login");
+ } else {
+ console.info("Starting authentication procedure (Mobile)");
+ location.href = "https://money-v1.equestria.dev/Authentication/Mobile/"
+ }
+ } else {
+ console.info("Authenticated successfully");
+
+ await refresh();
+ }
+ }, 1000)
+ }
+
+ async function refresh() {
+ document.getElementById("transactions").innerHTML = "";
+ document.getElementById("username").innerText = JSON.parse(await (await window.fetch("https://money-v1.equestria.dev/Authentication/Username/")).text()).name;
+ document.getElementById("user-profile").src = "https://account.minteck.org/hub/api/rest/avatar/" + JSON.parse(await (await window.fetch("https://money-v1.equestria.dev/Authentication/Username/")).text()).id + "?dpr=2&size=48";
+
+ window.transactions = JSON.parse(await (await window.fetch("https://money-v1.equestria.dev/Application/TransactionsList/")).text());
+ window.goal = JSON.parse(await (await window.fetch("https://money-v1.equestria.dev/Application/GetGoal/")).text());
+
+ for (let transaction of transactions) {
+ demo = document.getElementById("demo-transaction");
+ demo.id = "";
+
+ if (transaction.type === "pay") {
+ word = "used";
+ color = "#b31500";
+ } else {
+ word = "added";
+ color = "#0065b3";
+ }
+
+ if (transaction.amount['original'] === "eur") {
+ baseCurrency = transaction.amount['eur'].toFixed(2) + "€";
+ convertedCurrency = "£" + transaction.amount['gbp'].toFixed(2);
+ } else {
+ baseCurrency = "£" + transaction.amount['gbp'].toFixed(2);
+ convertedCurrency = transaction.amount['eur'].toFixed(2) + "€";
+ }
+
+ document.getElementById("transactions").innerHTML += demo.outerHTML
+ .replace("%user%", transaction.author.name)
+ .replace("%picture%", "\" src=\"" + transaction.author.avatar + "\"")
+ .replace("%time%", transaction.date.relative)
+ .replace("%transactionId%", transaction.date.absolute)
+ .replace("%description%", transaction.description.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;"))
+ .replace("%type%", word)
+ .replace("%amount_bc%", baseCurrency)
+ .replace("%amount_cc%", convertedCurrency)
+ .replace("%type%", word)
+ .replace("var(--perc-color)", color)
+
+ demo.id = "demo-transaction";
+ }
+
+ try {
+ totalPaidEUR = transactions.filter(i => i.type === "pay").map(i => { return i.amount['eur']; }).reduce((a, b) => { return a+b; });
+ } catch (e) {
+ totalPaidEUR = 0;
+ }
+
+ try {
+ totalPaidGBP = transactions.filter(i => i.type === "pay").map(i => { return i.amount['gbp']; }).reduce((a, b) => { return a+b; });
+ } catch (e) {
+ totalPaidGBP = 0;
+ }
+
+ try {
+ totalGainedEUR = transactions.filter(i => i.type !== "pay").map(i => { return i.amount['eur']; }).reduce((a, b) => { return a+b; });
+ } catch (e) {
+ totalGainedEUR = 0;
+ }
+
+ try {
+ totalGainedGBP = transactions.filter(i => i.type !== "pay").map(i => { return i.amount['gbp']; }).reduce((a, b) => { return a+b; });
+ } catch (e) {
+ totalGainedGBP = 0;
+ }
+
+ totalEUR = totalGainedEUR - totalPaidEUR;
+ totalGBP = totalGainedGBP - totalPaidGBP;
+
+ document.getElementById("balance-eur").innerText = totalEUR.toFixed(2);
+ document.getElementById("balance-gbp").innerText = totalGBP.toFixed(2);
+
+ document.getElementById("goal-name").innerText = goal.name;
+ document.getElementById("goal-amount-eur").innerText = goal.amount['eur'].toFixed(2);
+ document.getElementById("goal-amount-gbp").innerText = goal.amount['gbp'].toFixed(2);
+
+ if (goal.amount['eur'] === 0 || goal.amount['gbp'] === 0) {
+ document.getElementById("goal-amount-percentage").innerText = "N/A";
+ document.getElementById("goal-bar-fill").style.width = "0%";
+ } else {
+ percentage = (totalEUR / goal.amount['eur']) * 100;
+ document.getElementById("goal-amount-percentage").innerText = percentage.toFixed(2);
+ document.getElementById("goal-bar-fill").style.width = percentage.toFixed(5) + "%";
+ }
+
+ document.getElementById("app").style.display = "";
+ document.getElementById("loader").style.opacity = "0";
+ document.getElementById("loader").style.pointerEvents = "none";
+
+ for (let item of Array.from(document.getElementsByClassName("transaction"))) {
+ item.onclick = () => {
+ deleteTransaction(item.getAttribute("data-transaction-id"));
+ }
+ }
+
+ graph.data.labels = transactions.map((i) => { return new Date(i.date.absolute).toString().split(":")[0] + ":" + new Date(i.date.absolute).toString().split(":")[1]; }).reverse().filter((_, i) => i > 1);
+
+ let last = 0;
+ let balances = [];
+
+ transactions.map((i) => { if (i.type === "pay") { return -(i.amount.eur); } else { return i.amount.eur; } }).reverse().map((i) => {
+ last = last + i;
+ balances.push(last);
+ });
+ graph.data.datasets[0].data = balances.filter((_, i) => i > 1);
+
+ trendData = balances.filter((_, i) => i > 1).map((i, _) => { return { x: _, y: i } });
+ trend = trendline(trendData, 'x', 'y');
+
+ let lastTrend = trend.yStart;
+ let balancesTrend = [];
+
+ transactions.filter((_, i) => i > 1).map(() => {
+ lastTrend = lastTrend + trend.slope;
+ balancesTrend.push(lastTrend);
+ });
+
+ graph.data.datasets[1].data = balancesTrend;
+
+ document.getElementById("graph-insights-color").style.backgroundColor = "black";
+ document.getElementById("graph-insights-text").style.color = "black";
+ document.getElementById("graph-insights-text").innerText = "No insights available. Please try again later.";
+
+ let avgSlope = Math.round(trend.slope);
+ if (avgSlope < -3) {
+ document.getElementById("graph-insights-color").style.backgroundColor = "red";
+ document.getElementById("graph-insights-text").style.color = "red";
+ document.getElementById("graph-insights-text").innerText = "Money is going down faster than it should, you must immediately reduce your expenses.";
+ } else if (avgSlope < 0) {
+ document.getElementById("graph-insights-color").style.backgroundColor = "orange";
+ document.getElementById("graph-insights-text").style.color = "orange";
+ document.getElementById("graph-insights-text").innerText = "You are not saving money, consider reducing your expenses.";
+ } else if (avgSlope === 0) {
+ document.getElementById("graph-insights-color").style.backgroundColor = "green";
+ document.getElementById("graph-insights-text").style.color = "green";
+ document.getElementById("graph-insights-text").innerText = "Your balance is stable, consider saving more money.";
+ } else if (avgSlope > 0) {
+ document.getElementById("graph-insights-color").style.backgroundColor = "sky";
+ document.getElementById("graph-insights-text").style.color = "sky";
+ document.getElementById("graph-insights-text").innerText = "You are effectively saving money.";
+ }
+
+ if (avgSlope < 0) {
+ it = 0; b = totalEUR; while (b > 0) { it++; b = b + trend.slope; }
+ let avgDelay = Math.round(averageDelta(transactions.map((i) => { return new Date(i.date.absolute).getTime() }).reverse()));
+ let timeUntilEmpty = avgDelay * it;
+ let date = new Date(new Date().getTime() + timeUntilEmpty).toString().split(":")[0];
+ document.getElementById("graph-insights-text").innerText += " (reaching zero on " + date.substring(0, date.length - 3) + ")";
+ } else {
+ it = 0; b = totalEUR; while (b < goal.amount.eur) { it++; b = b + trend.slope; }
+ let avgDelay = Math.round(averageDelta(transactions.map((i) => { return new Date(i.date.absolute).getTime() }).reverse()));
+ let timeUntilGoal = avgDelay * it;
+ let date = new Date(new Date().getTime() + timeUntilGoal).toString().split(":")[0];
+ document.getElementById("graph-insights-text").innerText += " (reaching goal on " + date.substring(0, date.length - 3) + ")";
+ }
+ }
+
+ async function createTransaction() {
+ document.getElementById('create-action').disabled = true;
+ document.getElementById('create-currency').disabled = true;
+ document.getElementById('create-description').disabled = true;
+ document.getElementById('create-amount').disabled = true;
+ document.getElementById('create-button-create').disabled = true;
+ document.getElementById('create-button-cancel').disabled = true;
+
+ if (isNodeJS) {
+ await (await window.fetch("https://money-v1.equestria.dev/Application/AddTransaction/?Currency=" + document.getElementById('create-currency').value + "&Amount=" + document.getElementById('create-amount').value + "&Operation=" + document.getElementById('create-action').value + "&Description=" + Buffer.from(document.getElementById('create-description').value).toString("base64url"))).text();
+ } else {
+ await (await window.fetch("https://money-v1.equestria.dev/Application/AddTransaction/?Currency=" + document.getElementById('create-currency').value + "&Amount=" + document.getElementById('create-amount').value + "&Operation=" + document.getElementById('create-action').value + "&Description=" + btoa(document.getElementById('create-description').value).replaceAll("+", "-").replaceAll("/", "_"))).text();
+ }
+
+ await refresh();
+
+ document.getElementById('create-action').disabled = false;
+ document.getElementById('create-currency').disabled = false;
+ document.getElementById('create-description').disabled = false;
+ document.getElementById('create-amount').disabled = false;
+ document.getElementById('create-button-create').disabled = false;
+ document.getElementById('create-button-cancel').disabled = false;
+ document.getElementById('create-modal').style.display = 'none';
+ document.getElementById('create-action').value = '+';
+ document.getElementById('create-currency').value = '£';
+ document.getElementById('create-description').value = '';
+ document.getElementById('create-amount').value = '';
+ }
+
+ function showConfirm(text) {
+ document.getElementById("confirm-modal").style.display = isNodeJS ? "flex" : "";
+ document.getElementById("confirm-text").innerText = text;
+ document.getElementById('confirm-button-cancel').disabled = false;
+ document.getElementById('confirm-button-ok').disabled = false;
+ }
+
+ confirmAction = () => {
+ console.log("Confirmed!");
+ document.getElementById('confirm-modal').style.display = 'none';
+ }
+
+ async function deleteTransaction(id) {
+ let transaction = transactions.filter(i => i.date.absolute === id)[0];
+ console.log(transaction);
+ showConfirm("This will remove the transaction made by " + transaction.author.name + " " + transaction.date.relative + ".\nIt will be removed from the list and the global balance will be recalculated without this transaction.");
+ confirmAction = async () => {
+ if (isNodeJS) {
+ await (await window.fetch("https://money-v1.equestria.dev/Application/RemoveTransaction/?Transaction=" + Buffer.from(id).toString("base64url"))).text();
+ } else {
+ await (await window.fetch("https://money-v1.equestria.dev/Application/RemoveTransaction/?Transaction=" + btoa(id).replaceAll("+", "-").replaceAll("/", "_"))).text();
+ }
+ await refresh();
+ document.getElementById('confirm-modal').style.display = 'none';
+ }
+ }
+ </script>
+ </div>
+ <script>
+ if (!isNodeJS) {
+ document.getElementById("loader").style.backgroundColor = "#000000";
+ }
+ </script>
+ <div id="app" style="display:none;position:fixed;z-index:3;">
+ <div id="header" style="z-index:5;background: #111;position: fixed;top: 0;padding: 8px 30px;left: 0;right: 0;height: 32px;">
+ <img alt="Bits" src="assets/logo.svg" style="width:32px;vertical-align: middle;">
+ <span style="vertical-align: middle;" id="menu-desktop">
+ <a class="ln" style="cursor:pointer;margin-left:20px;" onclick="document.getElementById('create-modal').style.display = isNodeJS ? 'flex' : '';">New transaction</a><a class="ln" id="tab-0" style="cursor:pointer;margin-left:20px;" onclick="document.getElementById('list').style.display = 'none';document.getElementById('tab-0').style.display = 'none';document.getElementById('tab-1').style.display = '';document.getElementById('graph').style.display = '';">Statistics</a><a class="ln" id="tab-1" style="display:none;cursor:pointer;margin-left:20px;" onclick="document.getElementById('list').style.display = '';document.getElementById('tab-0').style.display = '';document.getElementById('tab-1').style.display = 'none';document.getElementById('graph').style.display = 'none';">Transactions</a><a class="ln" onclick="document.getElementById('about-modal').style.display = isNodeJS ? 'flex' : '';" style="cursor:pointer;margin-left:20px;">About</a>
+ </span>
+ <span style="vertical-align: middle;display:none;" id="menu-mobile">
+ <a class="ln" style="cursor:pointer;margin-left:20px;" onclick="document.getElementById('create-modal').style.display = isNodeJS ? 'flex' : '';">Add</a><a class="ln" id="tab-0m" style="cursor:pointer;margin-left:20px;" onclick="document.getElementById('list').style.display = 'none';document.getElementById('tab-0m').style.display = 'none';document.getElementById('tab-1m').style.display = '';document.getElementById('graph').style.display = '';">Stats</a><a class="ln" id="tab-1m" style="display:none;cursor:pointer;margin-left:20px;" onclick="document.getElementById('list').style.display = '';document.getElementById('tab-0m').style.display = '';document.getElementById('tab-1m').style.display = 'none';document.getElementById('graph').style.display = 'none';">List</a><a class="ln" onclick="document.getElementById('about-modal').style.display = isNodeJS ? 'flex' : '';" style="cursor:pointer;margin-left:20px;">About</a>
+ </span>
+ <span style="float: right;margin-right: 10px;margin-top: 7px;" id="total-data">
+ <span id="balance" style="opacity: .5;">
+ <span id="balance-eur">%total_eur%</span>€, £<span id="balance-gbp">%total_gbp%</span>
+ </span><span id="username-desktop" style="opacity:.5;"> · <span id="username">%user%</span></span><span id="username-mobile" style="display:none;vertical-align: middle;"><img id="user-profile" alt="" style="border-radius: 999px;width: 32px;height: 32px;vertical-align: middle;position:relative;top:-6px;display:inline-block;margin-left:7px;" src=""></span>
+ </span>
+ </div>
+ <script>
+ if (!isNodeJS) {
+ document.getElementById("menu-desktop").style.display = "none";
+ document.getElementById("menu-mobile").style.display = "";
+ document.getElementById("username-desktop").style.display = "none";
+ document.getElementById("username-mobile").style.display = "";
+ document.getElementById("header").style.padding = "8px";
+ document.getElementById("header").style.backgroundColor = "#000000";
+ document.getElementById("balance").style.position = "relative";
+ document.getElementById("balance").style.top = "-4px";
+ }
+ </script>
+ <div id="view" style="z-index:5;color:black;background:rgb(247, 249, 250);position:fixed;top:48px;left:0;right:0;bottom:64px;">
+ <div id="list" style="position: fixed;top: 48px;left: 0;bottom: 64px;right:0;border-right: 1px solid rgba(0, 0, 0, .25);">
+ <div id="demo-zone" style="display:none;">
+ <div class="transaction" data-transaction-id="%transactionId%" id="demo-transaction" style="padding:10px;border-bottom: 1px solid rgba(0, 0, 0, .25);display:grid;grid-template-columns: 48px 1fr;grid-column-gap: 15px;">
+ <div class="transaction-user" style="display:flex;align-items: center;justify-content: center;">
+ <img alt="%picture%" style="border-radius: 999px;width: 48px;height: 48px;">
+ </div>
+ <div class="transaction-details">
+ <b>%user%</b> %type% <span style="color:var(--perc-color);"><b>%amount_bc%</b> <i>(%amount_cc%)</i></span><br>
+ %time%<br>
+ <div style="text-overflow: ellipsis;white-space: nowrap;overflow: hidden;width: 100%;">%description%</div>
+ </div>
+ </div>
+ </div>
+ <div id="transactions" style="overflow: scroll;height: 100%;"></div>
+ </div>
+
+ <div id="graph" style="display:none;width:100%;height:100%;">
+ <p style="
+ font-size: 14px;
+ margin: 14px;
+">
+ <span id="graph-insights-color" style="display: inline-block;width: 12px;height: 12px;border-radius: 999px;opacity: .75;background: black;vertical-align: middle;margin-right: 5px;"></span>
+ <span id="graph-insights-text" style="
+ display: inline-block;
+ vertical-align: middle;
+">No insights available. Please try again later.</span>
+ </p>
+ <canvas id="graph-display" style="width:100%;height:100%;"></canvas>
+ <script>
+ const ctx = document.getElementById('graph-display').getContext('2d');
+ const graph = new Chart(ctx, {
+ type: 'line',
+ data: {
+ labels: [],
+ datasets: [{
+ label: 'Balance',
+ data: [],
+ borderColor: 'rgba(153, 102, 255, 1)',
+ fill: false,
+ },
+ {
+ label: 'Trendline',
+ data: [],
+ borderColor: 'rgba(153, 102, 255, .5)',
+ borderDash: [5],
+ pointRadius: 0,
+ fill: false,
+ }]
+ },
+ options: {
+ scales: {
+ y: {
+ beginAtZero: true
+ }
+ },
+ scaleLabel: "<%=value%>%",
+ legend: {
+ display: false
+ },
+ tooltips: {
+ callbacks: {
+ label: function(tooltipItem, data) {
+ return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index] + '€';
+ }
+ }
+ }
+ }
+ });
+ </script>
+ </div>
+ </div>
+
+ <div id="create-modal" style="
+ display: none;
+ position: fixed;
+ z-index: 999;
+ inset: 0;
+ background: rgba(0, 0, 0, .75);
+ backdrop-filter: blur(10px);
+ align-items: center;
+ justify-content: center;
+ color: black;
+"><div id="create-modal-inner" style="
+ background: whitesmoke;
+ width: 500px;
+ padding: 10px;
+ border-radius: 5px;
+ box-shadow: 0 0 20px rgba(0, 0, 0, .5);
+">
+ <h3 style="
+ margin: 0;
+ margin-bottom: 20px;
+ margin-left: -10px;
+ margin-right: -10px;
+ text-align: center;
+ margin-top: -10px;
+ padding: 10px;
+ background: rgba(0, 0, 0, .1);
+">New transaction</h3>
+ Amount: <select id="create-action"><option>+</option><option>-</option></select>&nbsp;
+ <input id="create-amount" type="number" max="99999">&nbsp;
+ <select id="create-currency"><option>£</option><option>€</option></select><br><br>Description: <input maxlength="100" id="create-description" type="text" style="
+ display: block;
+ margin-top: 5px;
+ width: calc(100% - 10px);
+">
+ <div style="
+ display: block;
+ margin-top: 20px;
+ margin-left: auto;
+ margin-right: auto;
+width:max-content;">
+ <button id="create-button-create" onclick="createTransaction();">Create</button>&nbsp;<button id="create-button-cancel" onclick="document.getElementById('create-modal').style.display = 'none';document.getElementById('create-action').value='+';document.getElementById('create-currency').value='£';document.getElementById('create-description').value='';document.getElementById('create-amount').value='';">Cancel</button>
+ </div>
+ </div></div>
+
+ <div id="confirm-modal" style="
+ display: none;
+ position: fixed;
+ z-index: 999;
+ inset: 0;
+ background: rgba(0, 0, 0, .75);
+ backdrop-filter: blur(10px);
+ align-items: center;
+ justify-content: center;
+ color: black;
+"><div id="confirm-modal-inner" style="
+ background: whitesmoke;
+ width: 500px;
+ padding: 10px;
+ border-radius: 5px;
+ box-shadow: 0 0 20px rgba(0, 0, 0, .5);
+">
+ <h3 style="
+ margin: 0;
+ margin-bottom: 20px;
+ margin-left: -10px;
+ margin-right: -10px;
+ text-align: center;
+ margin-top: -10px;
+ padding: 10px;
+ background: rgba(0, 0, 0, .1);
+">Confirm action</h3>
+ <p id="confirm-text">Are you sure you want to do this?</p>
+ <div style="
+ display: block;
+ margin-top: 20px;
+ margin-left: auto;
+ margin-right: auto;
+width:max-content;">
+ <button id="confirm-button-ok" onclick="document.getElementById('confirm-button-cancel').disabled = true;document.getElementById('confirm-button-ok').disabled = true;confirmAction();">OK</button>&nbsp;<button id="confirm-button-cancel" onclick="document.getElementById('confirm-modal').style.display = 'none';">Cancel</button>
+ </div>
+ </div></div>
+
+
+ <div id="about-modal" style="
+ display: none;
+ position: fixed;
+ z-index: 999;
+ inset: 0;
+ background: rgba(0, 0, 0, .75);
+ backdrop-filter: blur(10px);
+ align-items: center;
+ justify-content: center;
+ color: black;
+"><div id="about-modal-inner" style="
+ background: whitesmoke;
+ width: 500px;
+ padding: 10px;
+ border-radius: 5px;
+ box-shadow: 0 0 20px rgba(0, 0, 0, .5);
+">
+ <h3 style="
+ margin: 0;
+ margin-bottom: 20px;
+ margin-left: -10px;
+ margin-right: -10px;
+ text-align: center;
+ margin-top: -10px;
+ padding: 10px;
+ background: rgba(0, 0, 0, .1);
+">About Bits</h3>
+ <div id="about-text">
+ <div style="font-weight: bold;text-align:center;margin-bottom:5px;">Bits</div>
+ <b>Server:</b> money-v1.equestria.dev<br>
+ <b>Platform:</b> <span id="about-platform"></span><script>document.getElementById("about-platform").innerText = isNodeJS ? "Desktop (Electron, NodeJS)" : "Android (Chrome, Android WebView)";</script><br>
+ <b>User Agent:</b> <span id="about-useragent"></span><script>document.getElementById("about-useragent").innerText = navigator.userAgent;</script></div>
+ <div style="
+ display: block;
+ margin-top: 20px;
+ margin-left: auto;
+ margin-right: auto;
+width:max-content;">
+ <button id="about-button-cancel" onclick="document.getElementById('about-modal').style.display = 'none';">Close</button>
+ </div>
+ </div></div>
+
+ <div id="goal" style="
+ position: fixed;
+ background: rgb(64, 64, 64);
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: 10px;
+ z-index: 2;
+"><b id="goal-name">%goal%</b> <span style="
+ float: right;
+"><span id="goal-amount-eur">%goal_eur%</span>€, £<span id="goal-amount-gbp">%goal_gbp%</span> · <span id="goal-amount-percentage">%goal_percentage%</span>% completed</span><span id="goal-bar" style="
+ display: block;
+ height: 16px;
+ margin-top: 10px;
+ border-radius: 999px;
+ background: rgba(0, 0, 0, .25);
+"><span id="goal-bar-fill" style="
+ height: 16px;
+ background: linear-gradient(90deg, rgba(183,153,201,1) 0%, rgba(87,58,152,1) 25%, rgba(204,129,148,1) 50%, rgba(235,186,115,1) 75%, rgba(169,83,144,1) 100%);
+ width: 0;
+ display: block;
+ border-radius: 999px;
+"></span></span></div>
+ </div>
+<script>
+ if (!isNodeJS) {
+ document.getElementById("create-modal-inner").style.borderRadius = "0";
+ document.getElementById("create-modal-inner").style.borderBottomLeftRadius = "5px";
+ document.getElementById("create-modal-inner").style.borderBottomRightRadius = "5px";
+ document.getElementById("create-modal-inner").style.width = "calc(100% - 30px)";
+ document.getElementById("confirm-modal-inner").style.borderRadius = "0";
+ document.getElementById("confirm-modal-inner").style.width = "calc(100% - 30px)";
+ document.getElementById("confirm-modal-inner").style.borderBottomLeftRadius = "5px";
+ document.getElementById("confirm-modal-inner").style.borderBottomRightRadius = "5px";
+ }
+
+ f13presses = 0;
+ document.onkeydown = (event) => {
+ if (event.keyCode === 124) {
+ if (require('os').platform() === "win32") {
+ alert("haha you're using an inferior OS! Doing F13 on here is too easy so you might have expected " +
+ "something but you're not going to get it.")
+ } else {
+ f13presses++;
+
+ switch (f13presses) {
+ case 1:
+ Array.from(document.querySelectorAll("*")).forEach((item) => {
+ item.style.fontFamily = "'Noto Sans Symbols', 'Wingdings', 'Webdings', monospace";
+ });
+ break;
+
+ case 2:
+ Array.from(document.querySelectorAll("*")).forEach((item) => {
+ item.style.background = "red";
+ });
+ break;
+
+ case 3:
+ Array.from(document.querySelectorAll("*")).forEach((item) => {
+ item.style.color = "blue";
+ });
+ break;
+
+ case 4:
+ Array.from(document.querySelectorAll("img")).forEach((item) => {
+ item.src = "";
+ item.style.color = "yellow";
+ });
+ break;
+
+ case 5:
+ Array.from(document.querySelectorAll("*")).forEach((item) => {
+ item.style.transition = "transform 200ms";
+ item.style.animationName = "shake";
+ item.style.animationDuration = "200ms";
+ item.style.animationIterationCount = "infinite";
+ item.style.animationDirection = "alternate-reverse";
+ });
+ break;
+
+ case 6:
+ Array.from(document.querySelectorAll("*")).forEach((item) => {
+ item.style.display = "none";
+ });
+ break;
+
+ default:
+ Array.from(document.querySelectorAll("*")).forEach((item) => {
+ window.close();
+ });
+ break;
+ }
+ }
+ }
+ }
+</script>
+</body>
</html> \ No newline at end of file