summaryrefslogtreecommitdiff
path: root/includes/components
diff options
context:
space:
mode:
Diffstat (limited to 'includes/components')
-rw-r--r--includes/components/details.inc166
-rw-r--r--includes/components/emergency.inc194
-rw-r--r--includes/components/footer.inc42
-rw-r--r--includes/components/fullbanner.inc28
-rw-r--r--includes/components/header.inc220
-rw-r--r--includes/components/mobilenav.inc88
-rw-r--r--includes/components/navigation.inc424
-rw-r--r--includes/components/pane.inc129
-rw-r--r--includes/components/planner.inc1046
-rw-r--r--includes/components/search.inc534
-rw-r--r--includes/components/sysbanner.inc96
-rw-r--r--includes/components/wakeup.inc114
12 files changed, 3081 insertions, 0 deletions
diff --git a/includes/components/details.inc b/includes/components/details.inc
new file mode 100644
index 0000000..b7d0ebe
--- /dev/null
+++ b/includes/components/details.inc
@@ -0,0 +1,166 @@
+<?php global $isLoggedIn; global $metadata; global $memberData; global $isLowerLoggedIn; global $systemID; global $app; global $lang; global $pages; $pronouns = getMemberPronouns($memberData['pronouns']); ?>
+<div id="member-details" class="<?= (($isLoggedIn || $isLowerLoggedIn) && $systemID !== $app["other"]["id"]) ? 'member-details-loggedIn' : 'member-details-loggedIn2' ?>" style="<?php if (!$isLoggedIn && !$isLowerLoggedIn): ?>grid-template-columns: repeat(4, 1fr);<?php endif; ?> background-color: <?= isset($memberData["color"]) ? '#' . $memberData["color"] . "33" : "transparent" ?>; margin-left: -20px; margin-right: -20px;">
+ <div>
+ <b><?= $lang["details"]["food"] ?></b><span class="member-small-separator"><br></span>
+ <?= match ($metadata["food"]) {
+ 0 => "<span data-bs-toggle='tooltip' title='" . ucfirst(str_replace("'", "&apos;", $pronouns["possessive_det"])) . " species indicates that " . str_replace("'", "&apos;", $pronouns["subjective"]) . ' ' . ($pronouns["third"] ? "doesn&apos;t" : "don&apos;t") . " need to eat food, therefore " . str_replace("'", "&apos;", $pronouns["subjective"]) . " can eat all foods.'>" . $lang["details"]["food_states"][0] . "</span>",
+ 1 => "<span data-bs-toggle='tooltip' title='" . ucfirst(str_replace("'", "&apos;", $pronouns["subjective"])) . " can&apos;t eat fish or meat.'>" . $lang["details"]["food_states"][1] . "</span>",
+ 2 => "<span data-bs-toggle='tooltip' title='" . ucfirst(str_replace("'", "&apos;", $pronouns["subjective"])) . " can&apos;t eat meat, but can eat fish.'>" . $lang["details"]["food_states"][2] . "</span>",
+ 3 => "<span data-bs-toggle='tooltip' title='" . ucfirst(str_replace("'", "&apos;", $pronouns["subjective"])) . " can eat all foods, meat and fish included.'>" . $lang["details"]["food_states"][3] . "</span>",
+ } ?>
+ </div>
+ <div>
+ <b><?= $lang["details"]["memory"] ?></b><span class="member-small-separator"><br></span>
+ <?= match ($metadata["shared_memory"]) {
+ 0 => "<span data-bs-toggle='tooltip' title='" . str_replace("'", "&apos;", ucfirst($pronouns["subjective"])) . ($pronouns["third"] ? " doesn&apos;t directly share" : " don&apos;t directly share") . " memory with " . str_replace("'", "&apos;", $pronouns["possessive_det"]) . " headmates, but is able to (slowly) query it if that is needed.'>" . $lang["details"]["memory_states"][0] . "</span>",
+ 1 => "<span data-bs-toggle='tooltip' title='" . str_replace("'", "&apos;", ucfirst($pronouns["subjective"])) . ($pronouns["third"] ? " doesn&apos;t directly shares" : " don&apos;t directly share") . " all memories with " . str_replace("'", "&apos;", $pronouns["possessive_det"]) . " headmates, but may be able to share certain memories and/or share memories at will.'>" . $lang["details"]["memory_states"][1] . "</span>",
+ 2 => "<span data-bs-toggle='tooltip' title='" . str_replace("'", "&apos;", ucfirst($pronouns["subjective"])) . ($pronouns["third"] ? " shares" : " share") . " all memories with " . str_replace("'", "&apos;", $pronouns["possessive_det"]) . " headmates.'>" . $lang["details"]["memory_states"][2] . "</span>",
+ } ?>
+ </div>
+ <?php if ($isLoggedIn || $isLowerLoggedIn): ?>
+ <div>
+ <b>Age:</b><span class="member-small-separator"><br></span>
+ <?php if (!isset($metadata["birth"]["year"]) || $metadata["birth"]["age"] > 0): ?>
+ <?php if ($metadata["birth"]["age"] === -1): $age = abs(log(0)); // => INF ?>
+ <?= "<span data-bs-toggle='tooltip' title='" . str_replace("'", "&apos;", ucfirst($pronouns["subjective"])) . " never age" . str_replace("'", "&apos;", $pronouns["third"] ? "s" : "") . " and " . str_replace("'", "&apos;", $pronouns["third"] ? "doesn't" : "don't") . " have a defined age, making " . str_replace("'", "&apos;", $pronouns["object"]) . " pretty much eternal.'>Eternal</span>" ?>
+ <?php elseif ($metadata["birth"]["age"] <= 0): ?>
+ -
+ <?php else: $age = $metadata["birth"]["age"]; if (is_numeric($metadata["birth"]["age"])): ?>
+ <?= "<span data-bs-toggle='tooltip' title='" . str_replace("'", "&apos;", ucfirst($pronouns["subjective"])) . str_replace("'", "&apos;", $pronouns["third"] ? " doesn't" : " don't") . " age like the body does, making " . str_replace("'", "&apos;", $pronouns["object"]) . " stay " . $metadata["birth"]["age"] . " years old for " . str_replace("'", "&apos;", $pronouns["possessive_det"]) . " entire life.'>" . $metadata["birth"]["age"] . "* years old</span>" ?>
+ <?php else: ?>
+ <?= "<span data-bs-toggle='tooltip' title='" . str_replace("'", "&apos;", ucfirst($pronouns["object"])) . " age " . str_replace("'", "&apos;", $pronouns["third"] ? " changes" : " change") . " frequently between " . explode("-", $metadata["birth"]["age"])[0] . " and " . explode("-", $metadata["birth"]["age"])[1] . " years old, and " . str_replace("'", "&apos;", $pronouns["subjective"]) . " " . str_replace("'", "&apos;", $pronouns["third"] ? " doesn't" : " don't") . " have a single defined age.'>Sliding " . explode("-", $metadata["birth"]["age"])[0] . " to " . explode("-", $metadata["birth"]["age"])[1] . "</span>" ?>
+ <?php endif; endif; ?>
+ <?php else: ?>
+ <?php if ($metadata["birth"]["year"] <= 1900): ?>
+ -
+ <?php else: ?>
+ <?php
+
+ $birthdate = $metadata["birth"]["year"] . date('-m-d');
+ if (isset($metadata["birth"]["date"]) && trim($metadata["birth"]["date"]) !== "" && $metadata["birth"]["date"] !== "01-01") {
+ $birthdate = $metadata["birth"]["year"] . "-" . $metadata["birth"]["date"];
+ }
+
+ $age = (int)date('Y') - $metadata["birth"]["year"] + (strtotime(date('Y') . "-" . $metadata["birth"]["date"]) <= time() ? 0 : -1);
+ $age2 = floor((time() - strtotime($birthdate)) / 31557600);
+ $birthday = "as time passes";
+
+ if (isset($metadata["birth"]["date"]) && trim($metadata["birth"]["date"]) !== "" && $metadata["birth"]["date"] !== "01-01") {
+ $birthday = "every " . date('F jS', strtotime(date('Y') . "-" . $metadata["birth"]["date"]));
+ }
+
+ ?>
+ <?= "<span data-bs-toggle='tooltip' title='$age2 with the old algorithm | " . str_replace("'", "&apos;", ucfirst($pronouns["subjective"])) . " age" . str_replace("'", "&apos;", $pronouns["third"] ? "s" : "") . " like the body does, making " . str_replace("'", "&apos;", $pronouns["object"]) . " stay " . $age . " years old for now, having this age change $birthday.'>" . $age . " years old</span>" ?>
+ <?php endif; ?>
+ <?php endif; ?>
+ </div>
+ <?php endif; ?>
+ <?php if ($systemID !== $app["other"]["id"]): ?>
+ <div>
+ <b><?= $lang["details"]["code"] ?></b><span class="member-small-separator"><br></span>
+ <?php if (isset($metadata["code"]) && trim($metadata["code"]) !== ""): ?>
+ <code style="color: white;"><?= strtoupper(substr($metadata["code"], 0, 3)) ?></code>
+ <?php else: ?>
+ -
+ <?php endif; ?>
+ </div>
+ <div>
+ <b>Primary interest:</b><span class="member-small-separator"><br></span>
+ <?php if (isset($metadata["interest"]) && trim($metadata["interest"]) !== ""): ?>
+ <?= trim($metadata["interest"]) ?>
+ <?php else: ?>
+ -
+ <?php endif; ?>
+ </div>
+ <?php endif; ?>
+<?php if ($systemID !== $app["other"]["id"]): ?>
+</div>
+<?php endif; ?>
+<?php if ($isLoggedIn || $isLowerLoggedIn): ?>
+<?php if ($systemID !== $app["other"]["id"]): ?>
+<div id="member-details-2" style="background-color: <?= isset($memberData["color"]) ? '#' . $memberData["color"] . "33" : "transparent" ?>; margin-left: -20px; margin-right: -20px;<?php if ($isLowerLoggedIn): ?>grid-template-columns: repeat(3, 1fr);<?php endif; ?>">
+<?php endif; ?>
+ <?php if (((isset($age) && $age >= 16 && $metadata["little"] === 0) || (!isset($age) && $metadata["little"] === 0)) || $metadata["sexual_features"]): ?>
+ <?php if ($isLoggedIn && $systemID !== $app["other"]["id"]): ?>
+ <div>
+ <span style="vertical-align: middle;">
+ <b>Sexual consent:</b><span class="member-small-separator"><br></span>
+ <?php if ($metadata["sexually_active"]): ?>
+ <span title="Consent is assumed when having sex with <?= $pronouns["object"] ?>, however <?= $pronouns["subjective"] ?> may ask to not have sex at the moment and such request must be honored." data-bs-toggle="tooltip">Preemptive</span>
+ <?php else: ?>
+ <span title="Consent is absolutely required every time when having sex with <?= $pronouns["object"] ?>." data-bs-toggle="tooltip">Required</span>
+ <?php endif; ?>
+ </span>
+ <?php if (!((isset($age) && $age >= 16 && $metadata["little"] === 0) || (!isset($age) && $metadata["little"] === 0))): ?>
+ <img src="/assets/icons/warning.svg" style="width: 16px;" title="Although this pony is able to engage in sexual relationships, they are not allowed to have sex with ponies who are over 16 years old." data-bs-toggle="tooltip">
+ <?php endif; ?>
+ </div>
+ <?php endif; ?>
+ <div>
+ <b><span class="member-detail-desktop">Sexual alignment</span><span class="member-detail-mobile">Sex. algn.</span>:</b><span class="member-small-separator"><br></span>
+ <?php switch ($metadata["alignment"]["sexual"]) {
+ case "aroace":
+ echo "Asexual";
+ break;
+ case "hetero":
+ echo "Straight";
+ break;
+ case "homo":
+ echo $pronouns["subjective"] === "she" ? "Lesbian" : ($pronouns["subjective"] === "he" ? "Gay" : "Homosexual");
+ break;
+ case "bi":
+ echo "Bisexual";
+ break;
+ case "pan":
+ echo "Pansexual";
+ break;
+ case "poly":
+ echo "Polysexual";
+ break;
+ } ?><?php if ($metadata["polyamorous"]["sexual"]): ?> (poly)<?php endif; ?>
+ </div>
+ <?php else: ?>
+ <div>
+ <style>
+ #member-details-2 {
+ grid-template-columns: <?= $isLowerLoggedIn ? "1fr 1fr 1fr" : "2fr 1fr 1fr" ?>;
+ }
+ </style>
+ <div style="display: flex; align-items: center; justify-content: center; height: 100%;opacity:.5;">This member is too young to be in a sexual relationship.</div>
+ </div>
+ <?php endif; ?>
+ <div>
+ <b><span class="member-detail-desktop">Romantic alignment</span><span class="member-detail-mobile">Rom. algn.</span>:</b><span class="member-small-separator"><br></span>
+ <?php switch ($metadata["alignment"]["romantic"]) {
+ case "aroace":
+ echo "Aromantic";
+ break;
+ case "hetero":
+ echo "Heteroromantic";
+ break;
+ case "homo":
+ echo $pronouns["subjective"] === "she" ? "Lesbiromantic" : ($pronouns["subjective"] === "he" ? "Gay" : "Homoromantic");
+ break;
+ case "bi":
+ echo "Biromantic";
+ break;
+ case "pan":
+ echo "Panromantic";
+ break;
+ case "poly":
+ echo "Polyromantic";
+ break;
+ } ?><?php if ($metadata["polyamorous"]["romantic"]): ?> (poly)<?php endif; ?>
+ </div>
+ <?php if ($systemID !== $app["other"]["id"]): ?>
+ <div>
+ <b>Birthday:</b><span class="member-small-separator"><br></span>
+ <?php if (isset($metadata["birth"]["date"]) && trim($metadata["birth"]["date"]) !== "" && $metadata["birth"]["date"] !== "01-01"): ?>
+ <?= date('F jS', strtotime(date('Y') . "-" . $metadata["birth"]["date"])) ?>
+ <?php else: ?>
+ -
+ <?php endif; ?>
+ </div>
+ <?php endif; ?>
+</div>
+<?php endif; ?>
diff --git a/includes/components/emergency.inc b/includes/components/emergency.inc
new file mode 100644
index 0000000..5490985
--- /dev/null
+++ b/includes/components/emergency.inc
@@ -0,0 +1,194 @@
+<h2>Emergency alert
+ <details style="display: inline-block;font-size:12px;">
+ <summary class="text-muted" style="opacity:.5;"></summary>
+ <label><input id="test-mode" type="checkbox"> Test Mode</label> · <label><input id="fake-requests" type="checkbox"> Fake Requests</label>
+ </details>
+</h2>
+
+<span data-bs-toggle="modal" data-bs-target="#turn-on" id="btn-on" style="background: #7f0000;font-size: 48px;padding: 10px 50px;border-radius: 10px;width: max-content;display: block;margin-left: auto;margin-right: auto;cursor: pointer;">Turn <b>ON</b></span>
+<span data-bs-toggle="modal" data-bs-target="#turn-off" id="btn-off" style="display:none;background: #007f0b;font-size: 48px;padding: 10px 50px;border-radius: 10px;width: max-content;margin-left: auto;margin-right: auto;cursor: pointer;">Turn <b>OFF</b></span>
+<p style="text-align:center;margin-top:10px;">Sending next notification <b><span id="next-notification">never</span></b></p>
+
+<style>
+ .modal-header {
+ border-bottom: 1px solid #353738;
+ }
+
+ .modal-content {
+ border: 1px solid rgba(255, 255, 255, .2);
+ background-color: #111;
+ }
+
+ .btn-close {
+ filter: invert(1);
+ }
+</style>
+
+<div class="modal fade" id="turn-on">
+ <div class="modal-dialog">
+ <div class="modal-content">
+
+ <div class="modal-header">
+ <h4 class="modal-title">This is to be treated seriously.</h4>
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+
+ <div class="modal-body">
+ <div class="alert alert-danger">
+ <b>WARNING:</b> This alert should be used IF AND ONLY IF you are in a situation that requires immediate help from a loved one. If your life is at immediate risk, please do not use this emergency system and call your local emergency services.
+ </div>
+ <div class="alert alert-danger">
+ Keep this page open until you receive help. If you close the page, alert notifications will not be sent.
+ </div>
+
+ <button onclick="enableAlert();" data-bs-dismiss="modal" class="btn btn-danger" style="font-size:20px;font-weight:bold;display:block;width:100%;">I understand the risk, enable the alert now.</button>
+
+ <hr>
+
+ <div class="alert alert-danger">
+ Once enabled, disable the alert system ONLY once you are safe. <b>Your mental health is more important than sleep.</b>
+ </div>
+
+ <div class="alert alert-warning">
+ This emergency alert system is designed to make sure a loved one can get in touch with you as soon as possible. Therefore, it will emit sudden alerts, and may surprise somecreature if e.g. they are sleeping. Keep that in mind.
+ </div>
+
+ <p>
+ <b>Disclaimer:</b> This emergency alert system MUST NOT be used in life-threatening situations. Although it has been extensively tested in multiple conditions, it may stop working correctly or stop working at all at any time and without warning. If your life is at immediate risk, call your local emergency services.
+ </p>
+ <p>
+ © <?= date('Y') ?> Equestria.dev
+ </p>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div class="modal fade" id="turn-off">
+ <div class="modal-dialog">
+ <div class="modal-content">
+
+ <div class="modal-header">
+ <h4 class="modal-title">Make sure you are safe.</h4>
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+
+ <div class="modal-body">
+ <div class="alert alert-danger">
+ Make sure you are safe, and you got in touch with a loved one before disabling this alert system. It can be re-enabled later at any time. Remember, <b>your mental health is more important than sleep.</b>
+ </div>
+
+ <p class="text-muted">
+ Scroll until the bottom of the page to disable the alert.
+ </p>
+
+ <p>
+ <b>Read this if you want to disable the alert before receiving help:</b> Remember you are loved, cared for. Ponies rely on you, need you, love you. We never want to lose any of you, so please, keep the alert enabled until you are safe. If you are unsure, always keep the alert enabled.
+ </p>
+ <p>
+ <b>Disclaimer:</b> This emergency alert system MUST NOT be used in life-threatening situations. Although it has been extensively tested in multiple conditions, it may stop working correctly or stop working at all at any time and without warning. If your life is at immediate risk, call your local emergency services.
+ </p>
+ <p>
+ This service makes use of the ntfy platform, provided by a third party. Do note that their privacy policy applies when delivering the notifications; although they should not contain personal information in any way.
+ </p>
+ <p>
+ © <?= date('Y') ?> Equestria.dev
+ </p>
+
+ <button id="disable-button" onclick="disableAlert();" data-bs-dismiss="modal" class="btn btn-success disabled" style="display:block;width:100%;">I am safe, disable the alert now. <span id="disable-timer">(X)</span></button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script>
+ window.alertInterval = null;
+ window.alertIntervalCounter = 15;
+ window.alertDisablerCounter = 9;
+ window.alertDisablerEnabled = false;
+ window.alertDisablerCounterInterval = null;
+
+ setInterval(() => {
+ if (document.getElementById("turn-off").offsetWidth > 0 || document.getElementById("turn-off").offsetHeight > 0) {
+ if (!window.alertDisablerEnabled) {
+ window.alertDisablerEnabled = true;
+ window.alertDisablerCounter = 9;
+
+ document.getElementById("disable-timer").innerText = "(" + window.alertDisablerCounter + ")";
+ document.getElementById("disable-button").classList.add("disabled");
+
+ window.alertDisablerCounterInterval = setInterval(() => {
+ window.alertDisablerCounter--;
+
+ if (window.alertDisablerCounter > 0) {
+ document.getElementById("disable-timer").innerText = "(" + window.alertDisablerCounter + ")";
+ document.getElementById("disable-button").classList.add("disabled");
+ } else {
+ document.getElementById("disable-timer").innerText = "";
+ document.getElementById("disable-button").classList.remove("disabled");
+ }
+ }, 1000);
+ }
+ } else {
+ window.alertDisablerEnabled = false;
+ try { clearInterval(window.alertDisablerCounterInterval) } catch (e) {}
+ window.alertDisablerCounter = 9;
+ }
+ });
+
+ function sendNotification() {
+ window.alertIntervalCounter = -1;
+
+ if (document.getElementById("test-mode").checked) {
+ document.getElementById("next-notification").innerText = "now";
+ if (document.getElementById("fake-requests").checked) {
+ window.alertIntervalCounter = 15;
+ document.getElementById("next-notification").innerText = "15 seconds";
+ } else {
+ window.fetch("/api/emergency").then(() => {
+ window.alertIntervalCounter = 15;
+ document.getElementById("next-notification").innerText = "15 seconds";
+ })
+ }
+ } else {
+ document.getElementById("next-notification").innerText = "now";
+ if (document.getElementById("fake-requests").checked) {
+ window.alertIntervalCounter = 15;
+ document.getElementById("next-notification").innerText = "15 seconds";
+ } else {
+ window.fetch("/api/emergency-real").then(() => {
+ window.alertIntervalCounter = 15;
+ document.getElementById("next-notification").innerText = "15 seconds";
+ })
+ }
+ }
+ }
+
+ function enableAlert() {
+ sendNotification();
+ document.getElementById("btn-on").style.display = "none";
+ document.getElementById("btn-off").style.display = "block";
+ document.getElementById("test-mode").disabled = true;
+ document.getElementById("fake-requests").disabled = true;
+
+ window.alertInterval = setInterval(() => {
+ window.alertIntervalCounter--;
+
+ if (window.alertIntervalCounter === 0) {
+ sendNotification();
+ } else if (window.alertIntervalCounter > -1) {
+ document.getElementById("next-notification").innerText = window.alertIntervalCounter + " second" + (window.alertIntervalCounter > 1 ? "s" : "");
+ }
+ }, 1000);
+ }
+
+ function disableAlert() {
+ clearInterval(window.alertInterval);
+ window.alertIntervalCounter = 15;
+ document.getElementById("next-notification").innerText = "never";
+ document.getElementById("btn-on").style.display = "block";
+ document.getElementById("btn-off").style.display = "none";
+ document.getElementById("test-mode").disabled = false;
+ document.getElementById("fake-requests").disabled = false;
+ }
+</script> \ No newline at end of file
diff --git a/includes/components/footer.inc b/includes/components/footer.inc
new file mode 100644
index 0000000..2cef58f
--- /dev/null
+++ b/includes/components/footer.inc
@@ -0,0 +1,42 @@
+<?php
+
+global $pageFile;
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc";
+
+?>
+
+<div id="footer-pre"></div>
+<div id="footer">
+ <hr>
+ <div class="container text-muted">
+ <?php
+
+ $refresh = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/refresh.json"), true);
+ $version = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/version.json"), true);
+
+ global $lang; global $pages;
+
+ ?>
+ © <?= date("Y") ?> <a href="https://equestria.horse" target="_blank" class="text-muted"><?= $lang["footer"]["copyright"] ?></a> · build <?= $version["build"] ?>.<?= $version["revision"] ?><br>
+ <a class="debug-outer text-muted" style="text-decoration: none;"><?= $lang["footer"]["update"] ?> <?= trim(timeAgo($refresh["timestamp"], $lang["_french"])) ?><span class="debug-hidden">&nbsp;(<?php if (time() - $refresh["timestamp"] > 360): ?><?= $lang["footer"]["no_update"] ?><?= $lang["footer"]["separator"] ?>; <?php endif; ?><?= date('D j M, G:i:s T', (int)$refresh["timestamp"]) ?><?= $lang["footer"]["separator"] ?>; <?= $lang["footer"]["took"] ?> <?= round($refresh["duration"]) ?> sec)</span></a>
+ <br><br><br><br><br>
+ </div>
+</div>
+
+<script>
+ let tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
+ let tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
+ return new bootstrap.Tooltip(tooltipTriggerEl)
+ });
+
+ Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]')).forEach((item) => {
+ if (!item.classList.contains("tooltip-nohelp")) {
+ item.style.cursor = "help";
+ }
+ })
+</script>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/components/search.inc"; ?>
+
+</body>
+</html> \ No newline at end of file
diff --git a/includes/components/fullbanner.inc b/includes/components/fullbanner.inc
new file mode 100644
index 0000000..ddd59df
--- /dev/null
+++ b/includes/components/fullbanner.inc
@@ -0,0 +1,28 @@
+<?php global $memberData; global $memberID; global $systemID; global $metadata; global $travelling; global $systemCommonName; global $member; global $lang; global $pages; ?>
+<script>
+ window.currentMemberData = JSON.parse(atob(`<?= base64_encode(json_encode(getMemberBannerData($memberID, $systemID))) ?>`));
+</script>
+<?php if ($memberData["name"] !== "unknown" && $memberData["name"] !== "fusion"): ?>
+ <div id="member-banner-container">
+ <div id="member-banner"></div>
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/components/details.inc"; ?>
+ </div>
+<?php else: ?>
+ <div id="member-banner"></div>
+<?php endif; ?>
+
+<script src="/assets/logo/banner.js"></script><script>refreshBanner(false, <?= $lang["_french"] ? "true" : "false" ?>)</script>
+
+<?php if ($travelling[$memberID]['travelling'] && !$travelling[$memberID]['equestria']): ?>
+ <br>
+ <div class="alert alert-primary" style="margin-bottom: 0 !important;">
+ <?= getMiniName($memberData["display_name"] ?? $memberData["name"]) ?> <?= $lang["fullbanner"]["visit"][0] ?> <?= $systemID === "ynmuc" ? "Raindrops System" : "Cloudburst System" ?><?= str_replace("%1", getMemberPronouns($memberData["pronouns"])["subjective"], str_replace("%2", getMemberPronouns($memberData["pronouns"])["third"] ? "is" : "are", $lang["fullbanner"]["visit"][1])) ?><?= $systemCommonName ?>.
+ </div>
+ <br>
+<?php elseif ($travelling[$memberID]['travelling'] && $travelling[$memberID]['equestria']): ?>
+ <br>
+ <div class="alert alert-primary" style="margin-bottom: 0 !important;">
+ <?= getMiniName($memberData["display_name"] ?? $memberData["name"]) ?> is on a trip to Equestria. <?= str_replace("%1", getMemberPronouns($memberData["pronouns"])["subjective"], str_replace("%2", getMemberPronouns($memberData["pronouns"])["third"] ? "is" : "are", "Therefore %1 %2 currently not in the ")) ?><?= $systemCommonName ?>.
+ </div>
+ <br>
+<?php endif; ?> \ No newline at end of file
diff --git a/includes/components/header.inc b/includes/components/header.inc
new file mode 100644
index 0000000..7c4cfc8
--- /dev/null
+++ b/includes/components/header.inc
@@ -0,0 +1,220 @@
+<?php global $title; global $pages;
+
+$useNewUI = !isset($_GET['old']);
+$readOnly = false;
+file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/.test", "hello");
+
+if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/.test")) {
+ unlink($_SERVER['DOCUMENT_ROOT'] . "/includes/data/.test");
+} else {
+ $readOnly = true;
+}
+
+$isNormallyLoggedIn = false;
+
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/session.inc"; global $isLoggedIn; global $isUserLoggedIn; global $isLowerLoggedIn;
+if ($readOnly && $isLoggedIn || $readOnly && $isUserLoggedIn) {
+ $isLoggedIn = false;
+ $isUserLoggedIn = false;
+ $isNormallyLoggedIn = true;
+}
+
+$pages = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/pages.json"), true);
+
+if (!function_exists("error")) {
+ function error($errno, $errstr, $file, $line) {
+ echo('
+ <!-- -->">
+ <div class="alert alert-danger" style="text-align: left;">
+ <b>Error ' . $errno . ':</b> ' . $errstr . ' [' . $file . ':' . $line . ']
+ </div>');
+ }
+}
+
+if (isset($_GET['errors'])) {
+ ini_set('display_errors', '1');
+ ini_set('display_startup_errors', '1');
+ error_reporting(E_ALL);
+ set_error_handler("error", E_ALL);
+}
+
+global $_MemberName;
+global $_MemberPage;
+global $_SystemName;
+global $_SystemPage;
+global $toplevel;
+
+$pages = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/pages.json"), true);
+$page = $pages[$toplevel] ?? [
+ "rail" => false
+];
+
+require_once $_SERVER["DOCUMENT_ROOT"] . "/includes/util/travelling.inc"; global $travelling;
+require_once $_SERVER["DOCUMENT_ROOT"] . "/includes/util/score.inc";
+require_once $_SERVER["DOCUMENT_ROOT"] . "/includes/util/pronouns.inc";
+require_once $_SERVER["DOCUMENT_ROOT"] . "/includes/util/bitset.inc";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/banner.inc";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/rainbow.inc";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc";
+
+?>
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link href="/assets/logo/custom.css" rel="stylesheet">
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
+ <title><?= $title && $title !== "-" ? $title . " · " : "" ?>Cold Haze</title>
+ <link rel="shortcut icon" href="/assets/logo/newlogo<?= $isLoggedIn || $isLowerLoggedIn ? "3" : "" ?>.png" type="image/png">
+</head>
+<body<?php if ($page["rail"] && $isLoggedIn && !$useNewUI): ?> id="admin-page"<?php endif; ?>>
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/components/navigation.inc"; global $navigation; ?>
+
+ <?php if (!$useNewUI): ?>
+ <div style="margin-top: 60px;" id="top-of-page"></div>
+ <?php endif; ?>
+
+ <?php if ($useNewUI): ?>
+ <aside id="navigation-pane">
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/components/pane.inc"; ?>
+ </aside>
+
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/components/mobilenav.inc"; ?>
+
+ <style>
+ body {
+ margin-left: 300px;
+ }
+
+ .modal.show {
+ left: 300px;
+ width: calc(100% - 300px);
+ }
+
+ nav {
+ display: none !important;
+ }
+
+ #navigation-pane {
+ z-index: 9999;
+ background: black;
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ width: 300px;
+ border-right: 1px solid rgba(255, 255, 255, .25);
+ overflow: auto;
+ }
+
+ #title-bar {
+ position: fixed;
+ top: 0;
+ left: 300px;
+ right: 0;
+ height: 34px;
+ background: rgba(0, 0, 0, .75);
+ z-index: 9999;
+ padding: 5px;
+ text-align: center;
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border-bottom: 1px solid rgba(255, 255, 255, .25);
+ }
+
+ #mobile-navigation {
+ display: none;
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 48px;
+ background: rgba(0, 0, 0, .75);
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+ border-top: 1px solid rgba(255, 255, 255, .25);
+ z-index: 999999;
+ }
+
+ @media (max-width: 1360px) {
+ .modal.show {
+ left: 250px !important;
+ width: calc(100% - 250px) !important;
+ }
+
+ #title-bar {
+ left: 250px !important;
+ }
+
+ #navigation-pane {
+ width: 250px !important;
+ }
+
+ body {
+ margin-left: 250px !important;
+ }
+
+ #system-banner-container, #member-banner-container {
+ width: 100% !important;
+ }
+ }
+
+ @media (max-width: 1300px) {
+ .modal.show {
+ left: 200px !important;
+ width: calc(100% - 200px) !important;
+ }
+
+ #title-bar {
+ left: 200px !important;
+ }
+
+ #navigation-pane {
+ width: 200px !important;
+ }
+
+ body {
+ margin-left: 200px !important;
+ }
+ }
+
+ @media (max-width: 1195px) {
+ .modal.show {
+ left: 0 !important;
+ width: 100% !important;
+ }
+
+ #title-bar {
+ left: 0 !important;
+ }
+
+ #navigation-pane {
+ display: none !important;
+ }
+
+ body {
+ margin-left: 0 !important;
+ }
+
+ #mobile-navigation {
+ display: block !important;
+ }
+ }
+
+ @media (max-width: 700px) {
+ #title-bar-parts-mobile {
+ display: inline !important;
+ }
+
+ #title-bar-parts-desktop {
+ display: none;
+ }
+
+ #title-bar {
+ text-align: left;
+ }
+ }
+ </style>
+ <?php endif; ?>
diff --git a/includes/components/mobilenav.inc b/includes/components/mobilenav.inc
new file mode 100644
index 0000000..646738c
--- /dev/null
+++ b/includes/components/mobilenav.inc
@@ -0,0 +1,88 @@
+<?php global $navigation; global $isLoggedIn; ?>
+<div id="mobile-navigation">
+ <div id="mobile-navigation-container" class="container" style="display: grid; grid-template-columns: repeat(<?= count(array_values(array_filter($navigation, function ($item) use ($isLoggedIn) {
+ return !$item["admin"] || $isLoggedIn;
+ }))) + 1 ?>, 1fr); height: 100%;">
+ <a title="Cold Haze" data-bs-toggle="tooltip" href="/" id="mobile-navigation-item--logo" class="tooltip-nohelp mobile-navigation-item">
+ <img src="/assets/logo/newlogo.png" alt="" style="width:24px;vertical-align: middle;">
+ </a>
+ <?php foreach ($navigation as $id => $item): if (!$item["admin"] || $isLoggedIn): ?>
+ <a onclick="toggleMobileNavigation('<?= $id ?>');" title="<?= $item["name"] ?>" data-bs-toggle="tooltip" id="mobile-navigation-item-<?= $id ?>" class="tooltip-nohelp mobile-navigation-item">
+ <img src="<?= $item["icon"] ?>" <?php if ($item["invert"]): ?>class="dropdown-icon"<?php endif; ?> alt="" style="width:24px;vertical-align: middle;border-radius:3px;">
+ </a>
+ <?php endif; endforeach; ?>
+ </div>
+</div>
+
+<div id="mobile-navigation-box-container" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 48px; background-color: rgba(0, 0, 0, .75); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); z-index: 99999; overflow: auto;">
+ <?php foreach ($navigation as $id => $item): if (!$item["admin"] || $isLoggedIn): ?>
+ <div id="mobile-navigation-box-<?= $id ?>" style="display: none; margin-top: 10px; margin-bottom: 10px;" class="mobile-navigation-box container">
+ <div class="pane-group-title">
+ <img src="<?= $item["icon"] ?>" <?php if ($item["invert"]): ?>class="dropdown-icon"<?php endif; ?> alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;"><?= $item["name"] ?></span>
+ </div>
+
+ <?php foreach ($item["items"] as $category): ?>
+ <div class="pane-group-category <?= $category["minimal"] ? "pane-group-category-minimal" : "" ?>" <?= $category["minimal"] ? 'style="display: grid; grid-template-columns: repeat(' . count($category["items"]) . ', 1fr); grid-gap: 10px;"' : "" ?>>
+ <?php if (isset($category["name"])): ?>
+ <div class="pane-group-category-title"><?= $category["name"] ?></div>
+ <?php endif; ?>
+ <?php foreach ($category["items"] as $link): ?>
+ <a class="pane-group-item" href="<?= $link["link"] ?>">
+ <img src="<?= $link["icon"] ?>" <?php if ($link["invert"]): ?>class="dropdown-icon"<?php endif; ?> alt="" style="width:24px; border-radius: 5px; vertical-align: middle;">
+ <?php if (!$category["minimal"]): ?><span style="vertical-align: middle;<?= isset($link["stepped"]) ? "color: $link[stepped];" : "" ?>"><?= isset($link["stepped"]) ? "<b>$link[name]</b>" : $link["name"] ?></span><?php endif; ?>
+ </a>
+ <?php endforeach; ?>
+ </div>
+ <?php endforeach; ?>
+ </div>
+ <?php endif; endforeach; ?>
+</div>
+
+<style>
+ .mobile-navigation-item {
+ display: flex;
+ cursor: pointer;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .mobile-navigation-item:hover {
+ background: rgba(255, 255, 255, .1);
+ }
+
+ .mobile-navigation-item:active, .mobile-navigation-item:focus, .mobile-navigation-item.open {
+ background: rgba(255, 255, 255, .25);
+ }
+</style>
+
+<script>
+ function closeMobileNavigation() {
+ document.getElementById("mobile-navigation-box-container").style.display = "none";
+ document.body.style.overflow = "";
+
+ Array.from(document.getElementsByClassName("mobile-navigation-item")).forEach((i) => {
+ i.classList.remove("open");
+ });
+
+ Array.from(document.getElementsByClassName("mobile-navigation-box")).forEach((i) => {
+ i.style.display = "none";
+ });
+ }
+
+ function openMobileNavigation(id) {
+ document.getElementById("mobile-navigation-item-" + id).classList.add("open");
+ document.body.style.overflow = "hidden";
+ document.getElementById("mobile-navigation-box-container").style.display = "block";
+ document.getElementById("mobile-navigation-box-" + id).style.display = "block";
+ }
+
+ function toggleMobileNavigation(id) {
+ if (document.getElementById("mobile-navigation-box-" + id).style.display !== "block") {
+ closeMobileNavigation();
+ openMobileNavigation(id);
+ } else {
+ closeMobileNavigation();
+ }
+ }
+</script> \ No newline at end of file
diff --git a/includes/components/navigation.inc b/includes/components/navigation.inc
new file mode 100644
index 0000000..beb92bb
--- /dev/null
+++ b/includes/components/navigation.inc
@@ -0,0 +1,424 @@
+<?php
+
+$pages = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/pages.json"), true);
+
+global $navigation;
+global $toplevel;
+global $lang; global $pages; global $app;
+
+$navigation_admin = [
+ "admin" => true,
+ "name" => "Private utilities",
+ "icon" => "/assets/icons/admin.svg",
+ "invert" => true,
+ "items" => [
+ "alerts" => [
+ "name" => null,
+ "minimal" => true,
+ "items" => [
+ [
+ "name" => $pages["emergency"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/emergency.svg",
+ "invert" => false,
+ "link" => "/-/emergency",
+ "stepped" => "rgb(220,53,69)",
+ "private" => false
+ ],
+ [
+ "name" => $pages["wakeup"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/wakeup.svg",
+ "invert" => false,
+ "link" => "/-/wakeup",
+ "stepped" => "rgb(13,202,240)",
+ "private" => false
+ ]
+ ]
+ ],
+ "apps" => [
+ "name" => $lang["navigation"]["apps"],
+ "minimal" => false,
+ "items" => [
+ [
+ "name" => $pages["fronting"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/fronting.svg",
+ "invert" => true,
+ "link" => "/-/fronting",
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["profiles"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/profiles.svg",
+ "invert" => true,
+ "link" => "/-/profiles",
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["money"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/money.svg",
+ "invert" => true,
+ "link" => "/-/money",
+ "stepped" => null,
+ "private" => true
+ ],
+ [
+ "name" => $pages["rules"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/rules.svg",
+ "invert" => true,
+ "link" => "/-/rules",
+ "stepped" => null,
+ "private" => true
+ ],
+ [
+ "name" => $pages["docs"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/docs.svg",
+ "invert" => true,
+ "link" => "/-/docs",
+ "stepped" => null,
+ "private" => true
+ ],
+ [
+ "name" => $pages["computers"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/computers.svg",
+ "invert" => true,
+ "link" => "/-/computers",
+ "stepped" => null,
+ "private" => true
+ ],
+ [
+ "name" => $pages["travelling"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/travelling.svg",
+ "invert" => true,
+ "link" => "/-/travelling",
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["stats"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/stats.svg",
+ "invert" => true,
+ "link" => "/-/stats",
+ "stepped" => null,
+ "private" => true
+ ],
+ [
+ "name" => $pages["logout"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/logout.svg",
+ "invert" => true,
+ "link" => "/-/logout",
+ "stepped" => null,
+ "private" => false
+ ]
+ ]
+ ],
+ "sort" => [
+ "name" => "Sorted members lists",
+ "minimal" => false,
+ "items" => [
+ [
+ "name" => $pages["splitting"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/splitting.svg",
+ "invert" => true,
+ "link" => "/-/splitting",
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["byfront"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/byfront.svg",
+ "invert" => true,
+ "link" => "/-/byfront",
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["alphabet"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/alphabet.svg",
+ "invert" => true,
+ "link" => "/-/alphabet",
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["s:species"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/species.svg",
+ "invert" => true,
+ "link" => "/-/byspecies",
+ "stepped" => null,
+ "private" => false
+ ],
+ ]
+ ],
+ /*"debug" => [
+ "name" => $lang["navigation"]["debug"],
+ "minimal" => false,
+ "items" => [
+ [
+ "name" => $pages["debug"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/debug.svg",
+ "invert" => true,
+ "link" => "/-/debug",
+ "stepped" => null
+ ],
+ [
+ "name" => $pages["bitset"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/bitset.svg",
+ "invert" => true,
+ "link" => "/-/bitset",
+ "stepped" => null
+ ],
+ [
+ "name" => $pages["score"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/score.svg",
+ "invert" => true,
+ "link" => "/-/score",
+ "stepped" => null
+ ],
+ [
+ "name" => $pages["logout"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/logout.svg",
+ "invert" => true,
+ "link" => "/-/logout",
+ "stepped" => null
+ ]
+ ]
+ ]*/
+ ]
+];
+$navigation_global = [
+ "admin" => false,
+ "name" => $lang["navigation"]["general"],
+ "icon" => "/assets/icons/global.svg",
+ "invert" => true,
+ "items" => [
+ "main" => [
+ "name" => null,
+ "minimal" => false,
+ "items" => [
+ [
+ "name" => $pages["home"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/home.svg",
+ "invert" => true,
+ "link" => "/",
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["relations"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/relations.svg",
+ "invert" => true,
+ "link" => "/-/relations",
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["terminology"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/terminology.svg",
+ "invert" => true,
+ "link" => "/-/terminology",
+ "stepped" => null,
+ "private" => false
+ ]
+ ]
+ ]
+ ]
+];
+$navigation_cloudburst = [
+ "admin" => false,
+ "name" => "Cloudburst System",
+ "icon" => getAsset("ynmuc"),
+ "invert" => false,
+ "items" => [
+ "header" => [
+ "name" => null,
+ "minimal" => false,
+ "items" => [
+ [
+ "name" => $lang["navigation"]["about"],
+ "icon" => "/assets/icons/about.svg",
+ "invert" => true,
+ "link" => "/cloudburst",
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["s:history"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/history.svg",
+ "invert" => true,
+ "link" => "/cloudburst/-/history",
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["s:compare"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/compare.svg",
+ "invert" => true,
+ "link" => "/cloudburst/-/compare",
+ "stepped" => null,
+ "private" => false
+ ]
+ ]
+ ],
+ "members" => [
+ "name" => $lang["navigation"]["members"],
+ "minimal" => false,
+ "items" => array_map(function ($member) {
+ return [
+ "name" => $member['display_name'] ?? $member['name'],
+ "icon" => getAsset($member["system"], $member["id"], "heads"),
+ "invert" => false,
+ "link" => "/$member[name]",
+ "stepped" => null,
+ "private" => false
+ ];
+ }, array_filter(scoreOrder(withTravelers(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc/members.json"), true), "ynmuc"), "ynmuc"), function ($member) {
+ return $member['name'] !== "unknown" && $member['name'] !== "fusion" && $member['name'] !== "new";
+ }))
+ ]
+ ]
+];
+$navigation_other = [
+ "admin" => true,
+ "name" => $app["other"]["name"],
+ "icon" => getAsset($app["other"]["id"]),
+ "invert" => false,
+ "items" => [
+ "header" => [
+ "name" => null,
+ "minimal" => false,
+ "items" => [
+ [
+ "name" => $lang["navigation"]["about"],
+ "icon" => "/assets/icons/about.svg",
+ "invert" => true,
+ "link" => "/" . $app["other"]["slug"],
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["s:history"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/history.svg",
+ "invert" => true,
+ "link" => "/" . $app["other"]["slug"] . "/-/history",
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["s:compare"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/compare.svg",
+ "invert" => true,
+ "link" => "/" . $app["other"]["slug"] . "/-/compare",
+ "stepped" => null,
+ "private" => false
+ ]
+ ]
+ ],
+ "members" => [
+ "name" => $lang["navigation"]["members"],
+ "minimal" => false,
+ "items" => array_map(function ($member) {
+ return [
+ "name" => $member['display_name'] ?? $member['name'],
+ "icon" => getAsset($member["system"], $member["id"], "heads"),
+ "invert" => false,
+ "link" => "/$member[name]",
+ "stepped" => null,
+ "private" => false
+ ];
+ }, array_filter(scoreOrder(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/other/members.json"), true), $app["other"]["id"]), function ($member) {
+ return $member['name'] !== "unknown" && $member['name'] !== "fusion" && $member['name'] !== "new";
+ }))
+ ]
+ ]
+];
+$navigation_raindrops = [
+ "admin" => false,
+ "name" => "Raindrops System",
+ "icon" => getAsset("gdapd"),
+ "invert" => false,
+ "items" => [
+ "header" => [
+ "name" => null,
+ "minimal" => false,
+ "items" => [
+ [
+ "name" => $lang["navigation"]["about"],
+ "icon" => "/assets/icons/about.svg",
+ "invert" => true,
+ "link" => "/raindrops",
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["s:history"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/history.svg",
+ "invert" => true,
+ "link" => "/raindrops/-/history",
+ "stepped" => null,
+ "private" => false
+ ],
+ [
+ "name" => $pages["s:compare"]["name"][$lang["_name"]],
+ "icon" => "/assets/icons/compare.svg",
+ "invert" => true,
+ "link" => "/raindrops/-/compare",
+ "stepped" => null,
+ "private" => false
+ ]
+ ]
+ ],
+ "members" => [
+ "name" => $lang["navigation"]["members"],
+ "minimal" => false,
+ "items" => array_map(function ($member) {
+ return [
+ "name" => $member['display_name'] ?? $member['name'],
+ "icon" => getAsset($member["system"], $member["id"], "heads"),
+ "invert" => false,
+ "link" => "/$member[name]",
+ "stepped" => null,
+ "private" => false
+ ];
+ }, array_filter(scoreOrder(withTravelers(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd/members.json"), true), "gdapd"), "gdapd"), function ($member) {
+ return $member['name'] !== "unknown" && $member['name'] !== "fusion" && $member['name'] !== "new";
+ }))
+ ]
+ ]
+];
+
+global $parts;
+
+if (isset($parts) && isset($parts[0]) && $parts[0] === $app["other"]["slug"]) {
+ $navigation = [
+ "other" => $navigation_other,
+ "cloudburst" => $navigation_cloudburst,
+ "raindrops" => $navigation_raindrops,
+ "admin" => $navigation_admin,
+ "global" => $navigation_global
+ ];
+} elseif (isset($parts) && isset($parts[0]) && $parts[0] === "cloudburst") {
+ $navigation = [
+ "cloudburst" => $navigation_cloudburst,
+ "raindrops" => $navigation_raindrops,
+ "other" => $navigation_other,
+ "admin" => $navigation_admin,
+ "global" => $navigation_global
+ ];
+} elseif (isset($parts) && isset($parts[0]) && $parts[0] === "raindrops") {
+ $navigation = [
+ "raindrops" => $navigation_raindrops,
+ "cloudburst" => $navigation_cloudburst,
+ "other" => $navigation_other,
+ "admin" => $navigation_admin,
+ "global" => $navigation_global
+ ];
+} else {
+ $navigation = [
+ "admin" => $navigation_admin,
+ "global" => $navigation_global,
+ "cloudburst" => $navigation_cloudburst,
+ "raindrops" => $navigation_raindrops,
+ "other" => $navigation_other,
+ ];
+} \ No newline at end of file
diff --git a/includes/components/pane.inc b/includes/components/pane.inc
new file mode 100644
index 0000000..28ccd71
--- /dev/null
+++ b/includes/components/pane.inc
@@ -0,0 +1,129 @@
+<?php global $isLoggedIn; global $isLowerLoggedIn; global $pages; global $navigation; $byColor = getMembersByColor(); global $lang; global $pages; ?>
+
+<div id="pane-header-background" style="background-image: linear-gradient(90deg, <?php
+
+$index = 0;
+$list = array_map(function ($i) { return $i["color"]; }, $byColor);
+$length = count($list);
+
+foreach ($list as $color) {
+ $perc = ($index / $length) * 100;
+ echo("#${color}33 $perc%");
+
+ $index++;
+ if ($index < $length) echo(", ");
+}
+
+?>)">
+ <a href="/" id="pane-header" class="login-link-clickable" style="color: white; text-decoration: none; padding: 20px; display: block; text-align: center; border-bottom: 1px solid rgba(255, 255, 255, .25); backdrop-filter: blur(20px);">
+ <img src="/assets/logo/newlogo<?= $isLoggedIn || $isLowerLoggedIn ? "3" : "" ?>.png" alt="" style="width:32px;vertical-align: middle;margin-right:5px;">
+ <span style="vertical-align: middle; font-weight: bold;">Cold Haze</span>
+ </a>
+</div>
+
+<?php if ($isLoggedIn || $isLowerLoggedIn): ?>
+ <a onclick="toggleGlobalSearch();" id="login-link" class="login-link-clickable">
+ <?= $lang["navigation"]["search"] ?>
+ </a>
+<?php else: ?>
+ <a href="/-/login" id="login-link" class="login-link-clickable">
+ <?php global $_PROFILE; ?>
+ <img alt="" src="/assets/icons/login.svg" style="filter:invert(1);width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;"><?= $lang["navigation"]["login"] ?></span>
+ </a>
+<?php endif; ?>
+
+<?php foreach ($navigation as $id => $item): if (!$item["admin"] || $isLoggedIn || $isLowerLoggedIn): ?>
+
+<div class="pane-group" id="pane-group-<?= $id ?>">
+ <div class="pane-group-title">
+ <img src="<?= $item["icon"] ?>" <?php if ($item["invert"]): ?>class="dropdown-icon"<?php endif; ?> alt="" style=" border-radius: 2px;width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;"><?= $item["name"] ?></span>
+ </div>
+
+ <?php foreach ($item["items"] as $category): if (!($category["minimal"] && $isLowerLoggedIn)): ?>
+ <div class="pane-group-category <?= $category["minimal"] ? "pane-group-category-minimal" : "" ?>" <?= $category["minimal"] ? 'style="display: grid; grid-template-columns: repeat(' . count($category["items"]) . ', 1fr); grid-gap: 10px;"' : "" ?>>
+ <?php if (isset($category["name"])): ?>
+ <div class="pane-group-category-title"><?= $category["name"] ?></div>
+ <?php endif; ?>
+ <?php foreach ($category["items"] as $link): if (isset($link)): if (!($isLowerLoggedIn && $link["private"])): ?>
+ <a class="pane-group-item" href="<?= $link["link"] ?>">
+ <img src="<?= $link["icon"] ?>" <?php if ($link["invert"]): ?>class="dropdown-icon"<?php endif; ?> alt="" style="width:24px; border-radius: 5px; vertical-align: middle;">
+ <?php if (!$category["minimal"]): ?><span style="vertical-align: middle;<?= isset($link["stepped"]) ? "color: $link[stepped];" : "" ?>"><?= isset($link["stepped"]) ? "<b>$link[name]</b>" : $link["name"] ?></span><?php endif; ?>
+ </a>
+ <?php endif; endif; endforeach; ?>
+ </div>
+ <?php endif; endforeach; ?>
+</div>
+
+<?php endif; endforeach; ?>
+
+<style>
+
+ .pane-group-category-title {
+ margin-bottom: 5px;
+ font-size: .8em;
+ opacity: .75;
+ padding: 0 5px;
+ }
+
+ .pane-group-category-minimal .pane-group-item {
+ text-align: center;
+ }
+
+ .pane-group-item {
+ display: block;
+ color: white !important;
+ text-decoration: none;
+ }
+
+ .pane-group-category {
+ background: rgba(255, 255, 255, .075);
+ border: 1px solid rgba(255, 255, 255, .1);
+ border-radius: 10px;
+ padding: 10px;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ }
+
+ .pane-group-item {
+ padding: 5px;
+ border-radius: 10px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+
+ .pane-group-item:hover {
+ background-color: rgba(255, 255, 255, .1);
+ }
+
+ .pane-group-title {
+ opacity: .75;
+ padding: 0 15px;
+ margin-top: 10px;
+ }
+
+ .pane-group {
+ padding: 10px;
+ border-bottom: 1px solid rgba(255, 255, 255, .25);
+ }
+
+ #login-link {
+ text-align: center;
+ padding: 10px;
+ display: block;
+ border-bottom: 1px solid rgba(255, 255, 255, .25);
+ }
+
+ .login-link-clickable {
+ color: white !important;
+ cursor: pointer !important;
+ text-decoration: none !important;
+ }
+
+ .login-link-clickable:hover {
+ background-color: rgba(255, 255, 255, .1);
+ }
+
+</style> \ No newline at end of file
diff --git a/includes/components/planner.inc b/includes/components/planner.inc
new file mode 100644
index 0000000..a7ec44f
--- /dev/null
+++ b/includes/components/planner.inc
@@ -0,0 +1,1046 @@
+<?php
+
+require_once $_SERVER["DOCUMENT_ROOT"] . "/includes/util/travelling.inc"; global $travelling;
+require_once $_SERVER["DOCUMENT_ROOT"] . "/includes/util/score.inc";
+require_once $_SERVER["DOCUMENT_ROOT"] . "/includes/util/pronouns.inc";
+require_once $_SERVER["DOCUMENT_ROOT"] . "/includes/util/bitset.inc";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/session.inc"; global $isLoggedIn; global $isUserLoggedIn; global $isLowerLoggedIn;
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/banner.inc";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/rainbow.inc";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/functions.inc";
+global $_PROFILE;
+
+$cloudburst = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/planner/ynmuc.json"), true);
+$raindrops = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/planner/gdapd.json"), true);
+
+function pacifier($id) {
+ global $isLowerLoggedIn;
+
+ $metadata = parseMetadata(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $id . ".json"), true));
+
+ if (isset($metadata["pacifier"]) && $metadata["pacifier"] && !$isLowerLoggedIn) {
+ return "<img title='This pony likes to sleep with a pacifier.' data-bs-toggle='tooltip' src='/assets/logo/pacifier.png' style='margin-left:auto;float: right;opacity:.75;width:24px;height:24px;vertical-align: middle; filter: invert(1);'>";
+ }
+
+ return "";
+}
+
+foreach ($cloudburst as $id => $day) {
+ foreach ($day as $index => $fronter) {
+ if (is_string($fronter)) {
+ $cloudburst[$id][$index] = [$fronter];
+ } else if (is_array($fronter)) {
+ if (count($fronter) < 2) {
+ if (!isset($cloudburst[$id][$index][0])) $cloudburst[$id][$index][0] = null;
+ $cloudburst[$id][$index][1] = null;
+ } else if (count($fronter) > 2) {
+ $array = [];
+
+ if (isset($cloudburst[$id][$index][0])) {
+ $array[0] = $cloudburst[$id][$index][0];
+ } else {
+ $array[0] = null;
+ }
+
+ if (isset($cloudburst[$id][$index][1])) {
+ $array[1] = $cloudburst[$id][$index][1];
+ } else {
+ $array[1] = null;
+ }
+
+ $cloudburst[$id][$index] = $array;
+ }
+ }
+ }
+}
+
+foreach ($raindrops as $id => $day) {
+ foreach ($day as $index => $fronter) {
+ if (is_string($fronter)) {
+ $raindrops[$id][$index] = [$fronter];
+ } else if (is_array($fronter)) {
+ if (count($fronter) < 2) {
+ if (!isset($raindrops[$id][$index][0])) $raindrops[$id][$index][0] = null;
+ $raindrops[$id][$index][1] = null;
+ } else if (count($fronter) > 2) {
+ $array = [];
+
+ if (isset($raindrops[$id][$index][0])) {
+ $array[0] = $raindrops[$id][$index][0];
+ } else {
+ $array[0] = null;
+ }
+
+ if (isset($raindrops[$id][$index][1])) {
+ $array[1] = $raindrops[$id][$index][1];
+ } else {
+ $array[1] = null;
+ }
+
+ $raindrops[$id][$index] = $array;
+ }
+ }
+ }
+}
+
+$school = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/school.json"), true);
+
+function formatTime($time) {
+ if ($time === "0:00") return "midnight";
+ if ($time === "12:00") return "noon";
+
+ $parts = explode(":", $time);
+ $hours = (int)$parts[0];
+ $ampm = "";
+
+ if ($hours === 0 || $hours === 12) {
+ $ampm = ($hours === 0 ? "am" : "pm");
+ $hours = 12;
+ } else {
+ if ($hours > 12) {
+ $ampm = "pm";
+ $hours = $hours - 12;
+ } else {
+ $ampm = "am";
+ }
+ }
+
+ if ((int)$parts[1] > 0) {
+ return $hours . ":" . $parts[1] . $ampm;
+ } else {
+ return $hours . $ampm;
+ }
+}
+
+function school($time, $first = false) {
+ global $school;
+ global $_PROFILE;
+ global $isLowerLoggedIn;
+
+ $day = date('Y-m-d', $time);
+ $parts = [];
+
+ if (isset($school[$day])) {
+ if (isset($school[$day]["wakeUp"]["timestamp"])) {
+ $parts[] = "waking up at " . formatTime($school[$day]["wakeUp"][$_PROFILE["login"]]) . " (your time)";
+ }
+
+ if (isset($school[$day]["sleep"]["timestamp"])) {
+ $parts[] = "sleeping at " . formatTime($school[$day]["sleep"][$_PROFILE["login"]]) . " (your time)";
+ }
+ }
+
+ if ($isLowerLoggedIn) $parts = [];
+
+ if (count($parts) > 0) {
+ if ($first) {
+ return ucfirst(implode(", ", $parts));
+ } else {
+ return " · " . implode(", ", $parts);
+ }
+ } else {
+ if ($first) {
+ return "-";
+ } else {
+ return "";
+ }
+ }
+}
+
+function day($display, $diff): void { if ($diff < 0) $disabled = true; else $disabled = false; global $cloudburst; global $raindrops; global $isLowerLoggedIn; ?>
+ <?php if (!isset($display)) {
+ $display = date('l', time() + (86400 * $diff));
+ }
+
+ $d = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/days.json"), true);
+ $t = date('Y-m-d', time() + (86400 * $diff));
+
+ if (isset($d[$t])) {
+ $display = "$display ($d[$t])";
+ }
+
+ ?>
+ <tr class="planner-day" id="planner-header-<?= $diff ?>" <?php if ($disabled): ?>style="opacity: .75; pointer-events: none;"<?php endif; ?>>
+ <td colspan="10" style="border:none;padding:0;">
+ <div style="padding:5px 10px;margin:-0.5px;border-top:1px solid #404040;border-left:1px solid #404040;border-right:1px solid #404040;border-top-left-radius: 10px; border-top-right-radius: 10px;"><?= $display ?></div>
+ </td>
+ <?php
+
+ if (!isset($cloudburst[date('Y-m-d', time() + (86400 * $diff))])) $cloudburst[date('Y-m-d', time() + (86400 * $diff))] = [];
+ $dayCloudburst = $cloudburst[date('Y-m-d', time() + (86400 * $diff))];
+ if (!isset($raindrops[date('Y-m-d', time() + (86400 * $diff))])) $raindrops[date('Y-m-d', time() + (86400 * $diff))] = [];
+ $dayRaindrops = $raindrops[date('Y-m-d', time() + (86400 * $diff))];
+
+ $index = 0;
+ $lengthCloudburst = count($dayCloudburst);
+ $lengthRaindrops = count($dayRaindrops);
+ $biggest = max($lengthCloudburst, $lengthRaindrops);
+
+ ?>
+ </tr>
+ <tr class="planner-header" <?php if ($disabled): ?>style="opacity: .75; pointer-events: none;"<?php endif; ?>>
+ <td colspan="5" style="border-top-color:transparent;border-right-color: transparent;">Cloudburst System</td>
+ <td colspan="5" style="border-top-color:transparent;border-left-color: transparent;">Raindrops System</td>
+ </tr>
+ <?php for ($i = 0; $i <= $biggest; $i++): ?>
+ <tr class="planner-member" <?php if ($disabled): ?>style="opacity: .75; pointer-events: none;"<?php endif; ?>>
+ <?php if (isset($dayCloudburst[$index])): ?>
+ <td class="planner-member-id" style="border-right-color: transparent;border-bottom-color: transparent;">
+ <?= $index + 1 ?>
+ </td>
+ <td class="planner-link" style="width:50vw;border-right-color: transparent;border-bottom-color: transparent;" <?php if (!isset($dayCloudburst[$index][1])): ?>colspan="3" <?php else: ?>colspan="2"<?php endif; ?>>
+ <?php $member = getMemberWithoutSystem($dayCloudburst[$index][0]); ?>
+ <?php if ($member["name"] === "fusion"): ?>
+ <a class="member-link" onclick="openEditFronter('cloudburst', <?= $index ?>, '<?= date('Y-m-d', time() + (86400 * $diff)) ?>')"><span class="member-link-inner"><img src="/assets/logo/newlogo.png" style="filter:grayscale(1);width:24px;"> <span class="member-link-text"><span class="merge-desktop" style="opacity:.75;">Multiple merged members</span></span></span></a>
+ <?php elseif ($member["name"] === "unknown"): ?>
+ <a class="member-link" onclick="openEditFronter('cloudburst', <?= $index ?>, '<?= date('Y-m-d', time() + (86400 * $diff)) ?>')"><span class="member-link-inner"><img src="/assets/logo/newlogo.png" style="filter:grayscale(1);width:24px;"> <span class="member-link-text"><span class="merge-desktop" style="opacity:.75;">Other/unknown/fallback pony</span></span></span></a>
+ <?php else: ?>
+ <a class="member-link" onclick="openEditFronter('cloudburst', <?= $index ?>, '<?= date('Y-m-d', time() + (86400 * $diff)) ?>')"><span class="member-link-inner"><img src="<?= getAsset($member["system"], $member["id"], "heads") ?>" style="width:24px;"> <span class="member-link-text"><?= !isset($dayCloudburst[$index][1]) ? $member["display_name"] ?? $member["name"] : getMiniName($member["display_name"] ?? $member["name"]) ?></span><?= $index === count($dayCloudburst) - 1 ? pacifier($member["id"]) : "" ?></span></a>
+ <?php endif; ?>
+ </td>
+ <?php if (!isset($dayCloudburst[$index][1])): ?>
+ <td class="planner-cofronter-inner planner-link" style="width:5%;text-align:center;border-left-color: transparent;border-bottom-color: transparent;"><?php if (!$isLowerLoggedIn): ?>
+ <a onclick="addCofronter('cloudburst', <?= $diff ?>, <?= $index ?>);" class="planner-add-link planner-add-link-cofronter">
+ <span class="planner-add-link-cofronter-inner"><img src="/assets/icons/add.svg" alt="" class="planner-add-icon"></span>
+ </a><?php endif; ?>
+ </td>
+ <?php else: ?>
+ <td class="planner-link" style="border-bottom-color: transparent;width:41.35vw;" colspan="2">
+ <?php $member = getMemberWithoutSystem($dayCloudburst[$index][1]); ?>
+ <?php if ($member["name"] === "fusion"): ?>
+ <a class="member-link" onclick="openEditCofronter('cloudburst', <?= $index ?>, '<?= date('Y-m-d', time() + (86400 * $diff)) ?>')"><span class="member-link-inner"><img src="/assets/logo/newlogo.png" style="filter:grayscale(1);width:24px;"> <span class="member-link-text"><span class="merge-desktop" style="opacity:.75;">Multiple merged members</span></span></span></a>
+ <?php elseif ($member["name"] === "unknown"): ?>
+ <a class="member-link" onclick="openEditCofronter('cloudburst', <?= $index ?>, '<?= date('Y-m-d', time() + (86400 * $diff)) ?>')"><span class="member-link-inner"><img src="/assets/logo/newlogo.png" style="filter:grayscale(1);width:24px;"> <span class="member-link-text"><span class="merge-desktop" style="opacity:.75;">Other/unknown/fallback pony</span></span></span></a>
+ <?php else: ?>
+ <a class="member-link" onclick="openEditCofronter('cloudburst', <?= $index ?>, '<?= date('Y-m-d', time() + (86400 * $diff)) ?>')"><span class="member-link-inner"><img src="<?= getAsset($member["system"], $member["id"], "heads") ?>" style="width:24px;"> <span class="member-link-text"><?= !isset($dayCloudburst[$index][1]) ? $member["display_name"] ?? $member["name"] : getMiniName($member["display_name"] ?? $member["name"]) ?></span><?= $index === count($dayCloudburst) - 1 ? pacifier($member["id"]) : "" ?></span></a>
+ <?php endif; ?>
+ </td>
+ <?php endif; ?>
+ <?php elseif ($index === count($dayCloudburst)): if (!$isLowerLoggedIn): ?>
+ <td class="planner-add-inner planner-link" colspan="5" style="border-top-color: transparent;border-bottom-color: transparent;">
+ <a onclick="addFronter('cloudburst', <?= $diff ?>, <?= $index ?>);" id="planner-add-link-cloudburst-<?= $diff ?>" class="planner-add-link">
+ <div class="planner-add-link-inner">
+ <img src="/assets/icons/add.svg" alt="" class="planner-add-icon">
+ <span class="planner-add-text">Add new fronter</span>
+ </div>
+ </a>
+ </td>
+ <?php endif; else: ?>
+ <td colspan="5" class="planner-empty" style="border-top-color:transparent;border-bottom-color:transparent;"></td>
+ <?php endif; ?>
+ <?php if (isset($dayRaindrops[$index])): ?>
+ <td class="planner-member-id" style="border-bottom-color: transparent;border-right-color: transparent;">
+ <?= $index + 1 ?>
+ </td>
+ <?php $member = getMemberWithoutSystem($dayRaindrops[$index][0]); ?>
+ <td class="planner-link" style="border-right-color:transparent;border-bottom-color: transparent;width:50vw;" <?php if (!isset($dayRaindrops[$index][1])): ?>colspan="3" <?php else: ?>colspan="2"<?php endif; ?>>
+ <?php if ($member["name"] === "fusion"): ?>
+ <a class="member-link" onclick="openEditFronter('raindrops', <?= $index ?>, '<?= date('Y-m-d', time() + (86400 * $diff)) ?>')"><span class="member-link-inner"><img src="/assets/logo/newlogo.png" style="filter:grayscale(1);width:24px;"> <span class="member-link-text"><span class="merge-desktop" style="opacity:.75;">Multiple merged members</span></span></span></a>
+ <?php elseif ($member["name"] === "unknown"): ?>
+ <a class="member-link" onclick="openEditFronter('raindrops', <?= $index ?>, '<?= date('Y-m-d', time() + (86400 * $diff)) ?>')"><span class="member-link-inner"><img src="/assets/logo/newlogo.png" style="filter:grayscale(1);width:24px;"> <span class="member-link-text"><span class="merge-desktop" style="opacity:.75;">Other/unknown/fallback pony</span></span></span></a>
+ <?php else: ?>
+ <a class="member-link" onclick="openEditFronter('raindrops', <?= $index ?>, '<?= date('Y-m-d', time() + (86400 * $diff)) ?>')"><span class="member-link-inner"><img src="<?= getAsset($member["system"], $member["id"], "heads") ?>" style="width:24px;"> <span class="member-link-text"><?= !isset($dayRaindrops[$index][1]) ? $member["display_name"] ?? $member["name"] : getMiniName($member["display_name"] ?? $member["name"]) ?></span><?= $index === count($dayRaindrops) - 1 ? pacifier($member["id"]) : "" ?></span></a>
+ <?php endif; ?>
+ </td>
+ <?php if (!isset($dayRaindrops[$index][1])): ?>
+ <td class="planner-cofronter-inner planner-link" style="border-bottom-color: transparent;border-left-color: transparent;width:5%;text-align:center;"><?php if (!$isLowerLoggedIn): ?>
+ <a onclick="addCofronter('raindrops', <?= $diff ?>, <?= $index ?>);" class="planner-add-link planner-add-link-cofronter">
+ <span class="planner-add-link-cofronter-inner"><img src="/assets/icons/add.svg" alt="" class="planner-add-icon"></span>
+ </a><?php endif; ?>
+ </td>
+ <?php else: ?>
+ <td class="planner-link" style="border-bottom-color:transparent;width:41.35vw;" colspan="2">
+ <?php $member = getMemberWithoutSystem($dayRaindrops[$index][1]); ?>
+ <?php if ($member["name"] === "fusion"): ?>
+ <a class="member-link" onclick="openEditCofronter('raindrops', <?= $index ?>, '<?= date('Y-m-d', time() + (86400 * $diff)) ?>')"><span class="member-link-inner"><img src="/assets/logo/newlogo.png" style="filter:grayscale(1);width:24px;"> <span class="member-link-text"><span class="merge-desktop" style="opacity:.75;">Multiple merged members</span></span></span></a>
+ <?php elseif ($member["name"] === "unknown"): ?>
+ <a class="member-link" onclick="openEditCofronter('raindrops', <?= $index ?>, '<?= date('Y-m-d', time() + (86400 * $diff)) ?>')"><span class="member-link-inner"><img src="/assets/logo/newlogo.png" style="filter:grayscale(1);width:24px;"> <span class="member-link-text"><span class="merge-desktop" style="opacity:.75;">Other/unknown/fallback pony</span></span></span></a>
+ <?php else: ?>
+ <a class="member-link" onclick="openEditCofronter('raindrops', <?= $index ?>, '<?= date('Y-m-d', time() + (86400 * $diff)) ?>')"><span class="member-link-inner"><img src="<?= getAsset($member["system"], $member["id"], "heads") ?>" style="width:24px;"> <span class="member-link-text"><?= !isset($dayRaindrops[$index][1]) ? $member["display_name"] ?? $member["name"] : getMiniName($member["display_name"] ?? $member["name"]) ?></span><?= $index === count($dayRaindrops) - 1 ? pacifier($member["id"]) : "" ?></span></a>
+ <?php endif; ?>
+ </td>
+ <?php endif; ?>
+ <?php elseif ($index === count($dayRaindrops)): if (!$isLowerLoggedIn): ?>
+ <td class="planner-add-inner planner-link" colspan="5" style="border-top-color: transparent;border-bottom-color: transparent;">
+ <a onclick="addFronter('raindrops', <?= $diff ?>, <?= $index ?>);" id="planner-add-link-raindrops-<?= $diff ?>" class="planner-add-link">
+ <div class="planner-add-link-inner">
+ <img src="/assets/icons/add.svg" alt="" class="planner-add-icon">
+ <span class="planner-add-text">Add new fronter</span>
+ </div>
+ </a>
+ </td>
+ <?php endif; else: ?>
+ <td colspan="5" class="planner-empty" style="border-top-color:transparent;border-bottom-color:transparent;"></td>
+ <?php endif; ?>
+ </tr>
+ <?php $index++; endfor; ?>
+ <tr class="planner-day planner-end-of-day" <?php if ($disabled): ?>style="opacity: .75; pointer-events: none;"<?php endif; ?>>
+ <td colspan="10" style="padding: 0;border:none;">
+ <div style="padding: 5px 10px;border: 1px solid #404040;margin:-1px;border-bottom-left-radius: 10px;border-bottom-right-radius: 10px;">
+ <?php if (count($dayCloudburst) > 0 && count($dayRaindrops) > 0): ?>
+ <?= getMiniName(getMemberWithoutSystem($dayCloudburst[count($dayCloudburst) - 1][0])["display_name"] ?? getMemberWithoutSystem($dayCloudburst[count($dayCloudburst) - 1][0])["name"]) ?> will sleep with <?= getMiniName(getMemberWithoutSystem($dayRaindrops[count($dayRaindrops) - 1][0])["display_name"] ?? getMemberWithoutSystem($dayRaindrops[count($dayRaindrops) - 1][0])["name"]) ?><?= school(time() + 86400 * $diff, false) ?>
+ <?php else: ?>
+ <?= school(time() + 86400 * $diff, true) ?>
+ <?php endif; ?>
+ </div>
+ </td>
+ </tr>
+ <tr class="planner-separator"></tr>
+<?php
+}
+?>
+
+<h2>Front planner</h2>
+<table id="planner">
+ <tbody>
+ <?php day("Yesterday", -1) ?>
+ <?php day("Today", 0) ?>
+ <?php day("Tomorrow", 1) ?>
+ <?php day(date('l', time() + 86400 * 2), 2) ?>
+ <?php day(date('l', time() + 86400 * 3), 3) ?>
+ <?php day(date('l', time() + 86400 * 4), 4) ?>
+ <?php day(date('l', time() + 86400 * 5), 5) ?>
+ <?php day(date('l', time() + 86400 * 6), 6) ?>
+ <?php day(date('l', time() + 86400 * 7), 7) ?>
+ </tbody>
+</table>
+
+<style>
+ #planner {
+ margin-top: 10px;
+ border-collapse: collapse;
+ width: 100%;
+ }
+
+ .planner-header {
+ font-weight: bold;
+ text-align: center;
+ }
+
+ .planner-header td {
+ width: 50%;
+ }
+
+ td {
+ border: 1px solid #404040;
+ padding: 5px 10px;
+ }
+
+ .planner-day {
+ text-align: center;
+ color: rgba(255, 255, 255, .5);
+ font-weight: bold;
+ }
+
+ .planner-end-of-day {
+ font-weight: normal;
+ }
+
+ .planner-end-of-day td {
+ border-bottom-left-radius: 10px;
+ }
+
+ .planner-separator {
+ height: 20px;
+ }
+
+ .planner-member-id {
+ width: 10%;
+ text-align: right;
+ }
+
+ .planner-link {
+ padding: 0;
+ }
+
+ .planner-link a {
+ padding: 5px 10px;
+ display: block;
+ }
+
+ .planner-add-link {
+ color: rgba(255, 255, 255, .75);
+ text-decoration: none;
+ cursor: pointer;
+ }
+
+ .planner-add-link:hover {
+ color: rgba(255, 255, 255, .75);
+ }
+
+ .planner-link:hover {
+ background-color: rgba(255, 255, 255, .125);
+ }
+
+ .planner-link:active {
+ background-color: rgba(255, 255, 255, .25);
+ }
+
+ .planner-add-icon {
+ filter: invert(1);
+ width: 24px;
+ vertical-align: middle;
+ opacity: .75;
+ }
+
+ .planner-add-text {
+ vertical-align: middle;
+ }
+
+ .modal-header {
+ border-bottom: 1px solid #353738;
+ }
+
+ .modal-content {
+ border: 1px solid rgba(255, 255, 255, .2);
+ background-color: #111;
+ }
+
+ .btn-close {
+ filter: invert(1);
+ }
+
+ .list-group-item {
+ color: #fff;
+ background-color: #222;
+ border: 1px solid rgba(255, 255, 255, .125);
+ }
+
+ .list-group-item.disabled {
+ color: #fff;
+ background-color: #222;
+ border-color: rgba(255, 255, 255, .125);
+ opacity: .75;
+ }
+
+ .list-group-item:hover {
+ background-color: #252525;
+ color: #ddd;
+ }
+
+ .list-group-item:active, .list-group-item:focus {
+ background-color: #272727;
+ color: #bbb;
+ }
+
+ .member-link, .list-group-item-action {
+ cursor: pointer !important;
+ }
+
+ .merge-mobile {
+ display: none;
+ }
+
+ @media (max-width: 768px) {
+ .merge-desktop {
+ display: none;
+ }
+
+ .merge-mobile {
+ display: inline;
+ }
+ }
+
+ @media (max-width: 991px) {
+ .member-link-text {
+ display: none;
+ }
+
+ .planner-link .member-link {
+ text-align: center;
+ }
+ }
+
+ .member-link, .planner-link {
+ background-color: transparent !important;
+ }
+
+ .member-link-inner, .planner-add-link-inner, .planner-add-link-cofronter-inner {
+ display: block;
+ padding: 3px 7px;
+ border-radius: 5px;
+ }
+
+ .planner-add-link-cofronter-inner {
+ border-radius: 100%;
+ width: max-content;
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ .planner-add-link-cofronter-inner .planner-add-icon {
+ margin-top: -2px;
+ }
+
+ .member-link, .planner-add-link-inner, .planner-add-link-cofronter-inner {
+ padding: 2px 3px !important;
+ }
+
+ .member-link:hover .member-link-inner, .planner-add-link:hover .planner-add-link-inner, .planner-add-link-cofronter:hover .planner-add-link-cofronter-inner {
+ background-color: rgba(255, 255, 255, .125);
+ }
+
+</style>
+
+<!--suppress JSUnresolvedVariable, JSUnresolvedFunction -->
+<script>
+ window.currentWorkingDate;
+ window.fronting = JSON.parse(window.atob(`<?= base64_encode(json_encode([
+ "raindrops" => $raindrops,
+ "cloudburst" => $cloudburst
+ ])) ?>`));
+ window.names = JSON.parse(window.atob(`<?php
+
+ $names = [];
+ foreach (json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd/members.json"), true) as $member) {
+ $names[$member['id']] = $member['display_name'] ?? $member['name'];
+ }
+ foreach (json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc/members.json"), true) as $member) {
+ $names[$member['id']] = $member['display_name'] ?? $member['name'];
+ }
+
+ echo(base64_encode(json_encode($names))) ?>`));
+ window.relations = JSON.parse(window.atob(`<?php
+
+ $relations = [];
+ foreach (scoreOrderGlobal() as $member) {
+ $relations[$member['id']] = array_map(function ($i) {
+ return explode("/", $i)[1];
+ }, [...($member["_metadata"]["marefriends"] ?? []), ...($member["_metadata"]["sisters"] ?? []), ...($member["_metadata"]["caretakers"] ?? [])]);
+ }
+
+ echo(base64_encode(json_encode($relations))) ?>`));
+
+ function getSuggestedPonies() {
+ if (window.fronting[window.addSystem === "gdapd" ? "cloudburst" : "raindrops"][window.currentWorkingDate][window.addIndex]) {
+ let otherPony = window.fronting[window.addSystem === "gdapd" ? "cloudburst" : "raindrops"][window.currentWorkingDate][window.addIndex][0];
+ let availablePonies = window.relations[window.fronting[window.addSystem === "gdapd" ? "cloudburst" : "raindrops"][window.currentWorkingDate][window.addIndex][0]];
+
+ if (availablePonies) {
+ document.getElementById("associated-results").innerHTML = "";
+
+ for (let pony of availablePonies) {
+ document.getElementById("associated-results").innerHTML += document.getElementById("list-pony-" + pony).outerHTML;
+ }
+
+ document.getElementById("list").style.display = "none";
+ document.getElementById("associated-results").style.display = "";
+ }
+ }
+ }
+
+ function ordinal(n) {
+ let s = ["th", "st", "nd", "rd"];
+ let v = n % 100;
+ return n + (s[(v - 20) % 10] || s[v] || s[0]);
+ }
+
+ function openEditFronter(system, id, date) {
+ let display = formatDate(date).display;
+
+ window.selectedFronting = {
+ system: system === "cloudburst" ? "ynmuc" : "gdapd",
+ date: date,
+ index: id
+ }
+
+ if (fronting[system][date][id][0] === "lzlaq" || fronting[system][date][id][0] === "irxyh") {
+ document.getElementById("edit-fronter-name").innerText = "merged members";
+ } else {
+ document.getElementById("edit-fronter-name").innerText = names[fronting[system][date][id][0]] ?? fronting[system][date][id][0];
+ }
+
+ document.getElementById("edit-fronter-date").innerText = display;
+ document.getElementById("edit-fronter-pos").innerText = ordinal(id + 1);
+ document.getElementById("edit-fronter-system").innerText = system === "cloudburst" ? "Cloudburst System" : "Raindrops System";
+
+ let modal = new bootstrap.Modal(document.getElementById('edit-fronter'));
+ modal.show();
+ }
+
+ function openEditCofronter(system, id, date) {
+ let display = formatDate(date).display;
+
+ window.selectedFronting = {
+ system: system === "cloudburst" ? "ynmuc" : "gdapd",
+ date: date,
+ index: id
+ }
+
+ document.getElementById("edit-cofronter-name").innerText = names[fronting[system][date][id][1]] ?? fronting[system][date][id][1];
+ document.getElementById("edit-cofronter-name2").innerText = names[fronting[system][date][id][0]] ?? fronting[system][date][id][0];
+
+ document.getElementById("edit-cofronter-date").innerText = display;
+ document.getElementById("edit-cofronter-pos").innerText = ordinal(id + 1);
+ document.getElementById("edit-cofronter-system").innerText = system === "cloudburst" ? "Cloudburst System" : "Raindrops System";
+
+ let modal = new bootstrap.Modal(document.getElementById('edit-cofronter'));
+ modal.show();
+ }
+
+ function formatDate(offset) {
+ let date;
+ let display;
+
+ switch (offset) {
+ case 0:
+ case "<?= date('Y-m-d', time()) ?>":
+ date = "<?= date('Y-m-d') ?>";
+ display = "today";
+ break;
+
+ case 1:
+ case "<?= date('Y-m-d', time() + (86400)) ?>":
+ date = "<?= date('Y-m-d', time() + 86400) ?>";
+ display = "tomorrow";
+ break;
+
+ case 2:
+ case "<?= date('Y-m-d', time() + (86400 * 2)) ?>":
+ date = "<?= date('Y-m-d', time() + (86400 * 2)) ?>";
+ display = "on <?= date('l', time() + (86400 * 2)) ?>";
+ break;
+
+ case 3:
+ case "<?= date('Y-m-d', time() + (86400 * 3)) ?>":
+ date = "<?= date('Y-m-d', time() + (86400 * 3)) ?>";
+ display = "on <?= date('l', time() + (86400 * 3)) ?>";
+ break;
+
+ case 4:
+ case "<?= date('Y-m-d', time() + (86400 * 4)) ?>":
+ date = "<?= date('Y-m-d', time() + (86400 * 4)) ?>";
+ display = "on <?= date('l', time() + (86400 * 4)) ?>";
+ break;
+
+ case 5:
+ case "<?= date('Y-m-d', time() + (86400 * 5)) ?>":
+ date = "<?= date('Y-m-d', time() + (86400 * 5)) ?>";
+ display = "on <?= date('l', time() + (86400 * 5)) ?>";
+ break;
+
+ case 6:
+ case "<?= date('Y-m-d', time() + (86400 * 6)) ?>":
+ date = "<?= date('Y-m-d', time() + (86400 * 6)) ?>";
+ display = "on <?= date('l', time() + (86400 * 6)) ?>";
+ break;
+
+ case 7:
+ case "<?= date('Y-m-d', time() + (86400 * 7)) ?>":
+ date = "<?= date('Y-m-d', time() + (86400 * 7)) ?>";
+ display = "on <?= date('l', time() + (86400 * 7)) ?>";
+ break;
+
+ case 8:
+ case "<?= date('Y-m-d', time() + (86400 * 8)) ?>":
+ date = "<?= date('Y-m-d', time() + (86400 * 8)) ?>";
+ display = "on <?= date('l', time() + (86400 * 8)) ?>";
+ break;
+
+ case 9:
+ case "<?= date('Y-m-d', time() + (86400 * 9)) ?>":
+ date = "<?= date('Y-m-d', time() + (86400 * 9)) ?>";
+ display = "on <?= date('l', time() + (86400 * 9)) ?>";
+ break;
+
+ case 10:
+ case "<?= date('Y-m-d', time() + (86400 * 10)) ?>":
+ date = "<?= date('Y-m-d', time() + (86400 * 10)) ?>";
+ display = "on <?= date('l', time() + (86400 * 10)) ?>";
+ break;
+
+ case 11:
+ case "<?= date('Y-m-d', time() + (86400 * 11)) ?>":
+ date = "<?= date('Y-m-d', time() + (86400 * 11)) ?>";
+ display = "on <?= date('l', time() + (86400 * 11)) ?>";
+ break;
+ }
+
+ return {date, display};
+ }
+
+ function addCofronter(system, offset, index) {
+ let date = formatDate(offset).date;
+ let display = formatDate(offset).display;
+
+ window.selectedFronting = {
+ system: system === "cloudburst" ? "ynmuc" : "gdapd",
+ date: date,
+ index: index
+ }
+
+ window.currentWorkingDate = date;
+ document.getElementById("new-cofronter-date").innerText = display;
+ document.getElementById("new-cofronter-system").innerText = system === "cloudburst" ? "Cloudburst System" : "Raindrops System";
+ window.addSystem = system === "raindrops" ? "gdapd" : "ynmuc";
+
+ if (fronting[system][date][index][0] === "lzlaq" || fronting[system][date][index][0] === "irxyh") {
+ document.getElementById("new-cofronter-main").innerText = "merged members";
+ } else {
+ document.getElementById("new-cofronter-main").innerText = names[fronting[system][date][index][0]] ?? fronting[system][date][index][0];
+ }
+
+ document.getElementById("list2").style.display = "block";
+ document.getElementById("search2-results").style.display = "none";
+ document.getElementById("search2").value = "";
+
+ let modal = new bootstrap.Modal(document.getElementById('new-cofronter'));
+ modal.show();
+ document.getElementById("search2").focus();
+ }
+
+ function addFronter(system, offset, index) {
+ let date = formatDate(offset).date;
+ let display = formatDate(offset).display;
+
+ window.currentWorkingDate = date;
+ window.addIndex = index;
+ document.getElementById("new-fronter-date").innerText = display;
+ document.getElementById("new-fronter-system").innerText = system === "cloudburst" ? "Cloudburst System" : "Raindrops System";
+ window.addSystem = system === "raindrops" ? "gdapd" : "ynmuc";
+
+ document.getElementById("list").style.display = "block";
+ getSuggestedPonies();
+ document.getElementById("search-results").style.display = "none";
+ document.getElementById("search").value = "";
+
+ let modal = new bootstrap.Modal(document.getElementById('new-fronter'));
+ modal.show();
+ document.getElementById("search").focus();
+ }
+
+ function confirmFronterAdd(system, id) {
+ Array.from(document.getElementsByClassName("new-fronter-link")).forEach((i) => {
+ i.classList.add("disabled");
+ });
+
+ document.getElementById("new-fronter-close").classList.add("disabled");
+
+ window.fetch("/api/fronter?t=add&d=" + window.currentWorkingDate + "&m=" + id + "&s=" + system).then(() => {
+ location.reload();
+ });
+ }
+
+ function confirmCofronterAdd(system, id) {
+ Array.from(document.getElementsByClassName("new-cofronter-link")).forEach((i) => {
+ i.classList.add("disabled");
+ });
+
+ document.getElementById("new-cofronter-close").classList.add("disabled");
+
+ window.fetch("/api/fronter?t=cofront&d=" + window.selectedFronting["date"] + "&m=" + id + "&s=" + system + "&i=" + window.selectedFronting["index"]).then(() => {
+ location.reload();
+ });
+ }
+
+ function deleteFronter() {
+ Array.from(document.getElementsByClassName("edit-fronter-link")).forEach((i) => {
+ i.classList.add("disabled");
+ });
+
+ document.getElementById("edit-fronter-close").classList.add("disabled");
+
+ window.fetch("/api/fronter?t=delete&d=" + window.selectedFronting["date"] + "&i=" + window.selectedFronting["index"] + "&s=" + window.selectedFronting["system"]).then(() => {
+ location.reload();
+ });
+ }
+
+ function deleteCofronter() {
+ Array.from(document.getElementsByClassName("edit-cofronter-link")).forEach((i) => {
+ i.classList.add("disabled");
+ });
+
+ document.getElementById("edit-cofronter-close").classList.add("disabled");
+
+ window.fetch("/api/fronter?t=codelete&d=" + window.selectedFronting["date"] + "&i=" + window.selectedFronting["index"] + "&s=" + window.selectedFronting["system"]).then(() => {
+ location.reload();
+ });
+ }
+
+ function moveFronterDown() {
+ Array.from(document.getElementsByClassName("edit-fronter-link")).forEach((i) => {
+ i.classList.add("disabled");
+ });
+
+ document.getElementById("edit-fronter-close").classList.add("disabled");
+
+ window.fetch("/api/fronter?t=down&d=" + window.selectedFronting["date"] + "&i=" + window.selectedFronting["index"] + "&s=" + window.selectedFronting["system"]).then(() => {
+ location.reload();
+ });
+ }
+
+ function viewFronterPage() {
+ let id = fronting[window.selectedFronting["system"] === "ynmuc" ? "cloudburst" : "raindrops"][window.selectedFronting["date"]][window.selectedFronting["index"]][0];
+ location.href = "/" + id;
+ }
+
+ function viewCofronterPage() {
+ let id = fronting[window.selectedFronting["system"] === "ynmuc" ? "cloudburst" : "raindrops"][window.selectedFronting["date"]][window.selectedFronting["index"]][1];
+ location.href = "/" + id;
+ }
+
+ function moveFronterUp() {
+ Array.from(document.getElementsByClassName("edit-fronter-link")).forEach((i) => {
+ i.classList.add("disabled");
+ });
+
+ document.getElementById("edit-fronter-close").classList.add("disabled");
+
+ window.fetch("/api/fronter?t=up&d=" + window.selectedFronting["date"] + "&i=" + window.selectedFronting["index"] + "&s=" + window.selectedFronting["system"]).then(() => {
+ location.reload();
+ });
+ }
+</script>
+
+<div class="modal" id="new-fronter" data-bs-backdrop="static" data-bs-keyboard="false">
+ <div class="modal-dialog">
+ <div class="modal-content">
+
+ <div class="modal-header">
+ <h4 class="modal-title">Add new fronter <span id="new-fronter-date">n/a</span></h4>
+ <button id="new-fronter-close" type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+
+ <div class="modal-body">
+ <p class="text-muted">Adding for the <span id="new-fronter-system">n/a</span></p>
+
+ <input type="text" placeholder="Search for a pony..." class="form-control" style="margin-bottom:15px;color:white;background:#111;border-color:#222;" id="search">
+
+ <div id="list">
+ <div class="list-group">
+ <?php foreach (array_filter(scoreOrderGlobal(), function ($i) { return $i["system"] === "gdapd" || $i["system"] === "ynmuc"; }) as $member): ?>
+ <a id="list-pony-<?= $member['id'] ?>" onclick="confirmFronterAdd(window.addSystem, '<?= $member['id'] ?>');" class="new-fronter-link list-group-item list-group-item-action"><img src="<?= getAsset($member["system"], $member["id"], "heads") ?>" style="width:24px;"> <?= $member["display_name"] ?? $member["name"] ?><peh-schedule-add></peh-schedule-add></a>
+ <?php endforeach; $member = getSystemMember("gdapd", "irxyh") ?>
+ <a id="list-pony-irxyh" onclick="confirmFronterAdd(window.addSystem, 'irxyh');" class="new-fronter-link list-group-item list-group-item-action"><img src="/assets/logo/newlogo3.png" style="filter:grayscale(1);width:24px;"> <span style="opacity:.75">Multiple merged members</span></a>
+ <a id="list-pony-zdtsg" onclick="confirmFronterAdd(window.addSystem, 'zdtsg');" class="new-fronter-link list-group-item list-group-item-action"><img src="/assets/logo/newlogo3.png" style="filter:grayscale(1);width:24px;"> <span style="opacity:.75">Other/unknown</span></a>
+ </div>
+ </div>
+
+ <div id="search-results" class="list-group"></div>
+ <div id="associated-results" class="list-group"></div>
+ </div>
+
+ </div>
+ </div>
+</div>
+
+<div class="modal" id="new-cofronter" data-bs-backdrop="static" data-bs-keyboard="false">
+ <div class="modal-dialog">
+ <div class="modal-content">
+
+ <div class="modal-header">
+ <h4 class="modal-title">Add new co-fronter <span id="new-cofronter-date">n/a</span> with <span id="new-cofronter-main">n/a</span></h4>
+ <button id="new-cofronter-close" type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+
+ <div class="modal-body">
+ <p class="text-muted">Adding for the <span id="new-cofronter-system">n/a</span></p>
+
+ <input type="text" placeholder="Search for a pony..." class="form-control" style="margin-bottom:15px;color:white;background:#111;border-color:#222;" id="search2">
+
+ <div id="list2">
+ <div class="list-group">
+ <?php foreach (array_filter(scoreOrderGlobal(), function ($i) { return $i["system"] === "gdapd" || $i["system"] === "ynmuc"; }) as $member): ?>
+ <a id="list2-pony-<?= $member['id'] ?>" onclick="confirmCofronterAdd(window.addSystem, '<?= $member['id'] ?>');" class="new-cofronter-link list-group-item list-group-item-action"><img src="<?= getAsset($member["system"], $member["id"], "heads") ?>" style="width:24px;"> <?= $member["display_name"] ?? $member["name"] ?><peh-schedule-add></peh-schedule-add></a>
+ <?php endforeach; $member = getSystemMember("gdapd", "irxyh") ?>
+ </div>
+ </div>
+
+ <div id="search2-results" class="list-group"></div>
+ </div>
+
+ </div>
+ </div>
+</div>
+
+<div class="modal" id="edit-fronter" data-bs-backdrop="static" data-bs-keyboard="false">
+ <div class="modal-dialog">
+ <div class="modal-content">
+
+ <div class="modal-header">
+ <h4 class="modal-title">Edit <span id="edit-fronter-name">n/a</span> fronting in <span id="edit-fronter-pos">n/a</span> <span id="edit-fronter-date">n/a</span></h4>
+ <button id="edit-fronter-close" type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+
+ <div class="modal-body">
+ <p class="text-muted">Editing for the <span id="edit-fronter-system">n/a</span></p>
+
+ <div class="list-group" id="list-cloudburst">
+ <a class="list-group-item list-group-item-action edit-fronter-link <?= $isLowerLoggedIn ? "disabled" : "" ?>" onclick="deleteFronter();">
+ <img src="/assets/icons/delete.svg" style="width:24px;filter:invert(1);vertical-align: middle;">
+ <span style="vertical-align: middle;">Delete</span>
+ </a>
+ <a class="list-group-item list-group-item-action edit-fronter-link <?= $isLowerLoggedIn ? "disabled" : "" ?>" onclick="moveFronterUp();">
+ <img src="/assets/icons/up.svg" style="width:24px;filter:invert(1);vertical-align: middle;">
+ <span style="vertical-align: middle;">Move up</span>
+ </a>
+ <a class="list-group-item list-group-item-action edit-fronter-link <?= $isLowerLoggedIn ? "disabled" : "" ?>" onclick="moveFronterDown();">
+ <img src="/assets/icons/down.svg" style="width:24px;filter:invert(1);vertical-align: middle;">
+ <span style="vertical-align: middle;">Move down</span>
+ </a>
+ <a class="list-group-item list-group-item-action edit-fronter-link" onclick="viewFronterPage();">
+ <img src="/assets/icons/page.svg" style="width:24px;filter:invert(1);vertical-align: middle;">
+ <span style="vertical-align: middle;">View page</span>
+ </a>
+ </div>
+ </div>
+
+ </div>
+ </div>
+</div>
+
+<div class="modal" id="edit-cofronter" data-bs-backdrop="static" data-bs-keyboard="false">
+ <div class="modal-dialog">
+ <div class="modal-content">
+
+ <div class="modal-header">
+ <h4 class="modal-title">Edit <span id="edit-cofronter-name">n/a</span> fronting with <span id="edit-cofronter-name2">n/a</span> in <span id="edit-cofronter-pos">n/a</span> <span id="edit-cofronter-date">n/a</span></h4>
+ <button id="edit-cofronter-close" type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+
+ <div class="modal-body">
+ <p class="text-muted">Editing for the <span id="edit-cofronter-system">n/a</span></p>
+
+ <div class="list-group" id="list-cloudburst">
+ <a class="list-group-item list-group-item-action edit-cofronter-link <?= $isLowerLoggedIn ? "disabled" : "" ?>" onclick="deleteCofronter();">
+ <img src="/assets/icons/delete.svg" style="width:24px;filter:invert(1);vertical-align: middle;">
+ <span style="vertical-align: middle;">Delete</span>
+ </a>
+ <a class="list-group-item list-group-item-action edit-cofronter-link" onclick="viewCofronterPage();">
+ <img src="/assets/icons/page.svg" style="width:24px;filter:invert(1);vertical-align: middle;">
+ <span style="vertical-align: middle;">View page</span>
+ </a>
+ </div>
+ </div>
+
+ </div>
+ </div>
+</div>
+
+<script src="/assets/editor/fuse.js"></script>
+<!--suppress JSUnresolvedFunction -->
+<script>
+ window.poniesList = JSON.parse(atob(`<?= base64_encode(json_encode(array_values(scoreOrderGlobal()))) ?>`));
+
+ const fuse = new Fuse(window.poniesList, {
+ includeScore: true,
+ keys: [
+ {
+ name: 'name',
+ weight: 1
+ },
+ {
+ name: 'display_name',
+ weight: 1
+ },
+ {
+ name: 'id',
+ weight: 0.7
+ },
+ {
+ name: 'species',
+ weight: 0.5
+ }
+ ]
+ })
+
+ function search(event) {
+ if (event.key === "Enter") {
+ return;
+ }
+
+ let query = document.getElementById("search").value;
+ let results = fuse.search(query).map((i) => {
+ return {
+ id: i.item.id,
+ score: i.score
+ };
+ });
+
+ let unfiltered = results;
+
+ results = results.filter((i) => {
+ return i.score < 0.7;
+ });
+
+ console.log("Before:", unfiltered, "After:", results);
+
+ document.getElementById("list").style.display = "none";
+ document.getElementById("search-results").style.display = "block";
+ document.getElementById("search-results").innerHTML = "";
+
+ for (let result of results) {
+ document.getElementById("search-results").innerHTML += document.getElementById("list-pony-" + result.id).outerHTML;
+ }
+
+ console.log(results);
+
+ if (query.trim() === "") {
+ document.getElementById("list").style.display = "block";
+ getSuggestedPonies();
+ document.getElementById("search-results").style.display = "none";
+ }
+ }
+
+ document.getElementById("search").onchange = document.getElementById("search").onkeyup = document.getElementById("search").onkeydown = search;
+
+ document.getElementById("search").addEventListener("keydown", (event) => {
+ if (event.key === "Enter") {
+ document.querySelector("#search-results .new-fronter-link").click();
+ event.preventDefault();
+ }
+ });
+
+ function search2(event) {
+ if (event.key === "Enter") {
+ return;
+ }
+
+ let query = document.getElementById("search2").value;
+ let results = fuse.search(query).map((i) => {
+ return {
+ id: i.item.id,
+ score: i.score
+ };
+ });
+
+ let unfiltered = results;
+
+ results = results.filter((i) => {
+ return i.score < 0.7;
+ });
+
+ console.log("Before:", unfiltered, "After:", results);
+
+ document.getElementById("list2").style.display = "none";
+ document.getElementById("search2-results").style.display = "block";
+ document.getElementById("search2-results").innerHTML = "";
+
+ for (let result of results) {
+ document.getElementById("search2-results").innerHTML += document.getElementById("list2-pony-" + result.id).outerHTML;
+ }
+
+ console.log(results);
+
+ if (query.trim() === "") {
+ document.getElementById("list2").style.display = "block";
+ document.getElementById("search2-results").style.display = "none";
+ }
+ }
+
+ document.getElementById("search2").onchange = document.getElementById("search2").onkeyup = document.getElementById("search2").onkeydown = search2;
+
+ document.getElementById("search2").addEventListener("keydown", (event) => {
+ if (event.key === "Enter") {
+ document.querySelector("#search2-results .new-cofronter-link").click();
+ event.preventDefault();
+ }
+ });
+</script>
+
+<?php
+
+file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/planner/ynmuc.json", json_encode($cloudburst));
+file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/planner/gdapd.json", json_encode($raindrops));
+
+?> \ No newline at end of file
diff --git a/includes/components/search.inc b/includes/components/search.inc
new file mode 100644
index 0000000..278ec41
--- /dev/null
+++ b/includes/components/search.inc
@@ -0,0 +1,534 @@
+<?php global $lang; global $pages; global $isLowerLoggedIn; global $isLoggedIn; ?>
+
+<div id="global-search-container" style="display: none; position: fixed; z-index: 99999999;background-color: rgba(0, 0, 0, .75); inset: 0; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); align-items: center; justify-content: center;">
+ <div id="global-search-box" style="background: rgba(50, 50, 50, .5); color: white; backdrop-filter: blur(30px); -webkit-backdrop-filter: blur(30px); border-radius: 15px; width: 768px; max-width: 90vw;">
+ <div id="global-search-input-container" onclick="document.getElementById('global-search-input').focus();" style="cursor:text;padding: 10px 50px; font-size: 22px; border: 1px solid rgba(100, 100, 100, .5); border-top-left-radius: 15px; border-top-right-radius: 15px; width: 768px; max-width: 90vw;">
+ <span onchange="globalSearchPlaceholder(); globalSearch();" onkeyup="globalSearchPlaceholder(); globalSearch();" onkeydown="globalSearchPlaceholder(); globalSearch();" type="text" id="global-search-input" style="vertical-align: middle; display:inline-block; background: transparent; color: white; border: none;" spellcheck="false" contenteditable="true"></span><span id="global-search-placeholder" style="opacity:.5;"><?= $lang["search"]["title"] ?></span><span id="global-search-autocomplete" style="vertical-align: middle; opacity:.5;"></span><span id="global-search-action" style="vertical-align: middle; opacity:.5;font-size:16px;"></span>
+ </div>
+ <div id="global-search-results" style="border-bottom-left-radius: 15px; border-bottom-right-radius: 15px; border-top-style: none; border-right: 1px solid rgba(100, 100, 100, .5); border-bottom: 1px solid rgba(100, 100, 100, .5); border-left: 1px solid rgba(100, 100, 100, .5); width: 768px; max-width: 90vw; height: 400px; max-height: calc(90vh - 60px);">
+ <div id="global-search-intro" style="display: flex; align-items: center; justify-content: center; height: 100%; text-align: center;">
+ <div style="opacity: .5;">
+ <img src="/assets/logo/newlogo<?= $isLoggedIn || $isLowerLoggedIn ? "3" : "" ?>.png" style="width: 64px; height: 64px; margin-bottom: 10px;">
+ <p><?= $lang["search"]["placeholder"] ?></p>
+ </div>
+ </div>
+ <div id="global-search-list" style="overflow: auto; display: flex; align-items: center; justify-content: center; height: 100%; text-align: center;"></div>
+ </div>
+ </div>
+</div>
+
+<script src="/assets/editor/fuse.js"></script>
+
+<!--suppress JSUnresolvedFunction -->
+<script>
+ const pages_list = JSON.parse(atob(`<?php
+
+ $base = array_values(array_filter(scandir($_SERVER['DOCUMENT_ROOT'] . "/pages"), function ($i) {
+ return !str_starts_with($i, ".") && $i !== "page.inc" && $i !== "api.inc" && $i !== "demo.inc" && $i !== "logout.inc" && $i !== "edit.inc" && $i !== "metadata.inc" && $i !== "edit-private.inc" && $i !== "app.inc" && !str_ends_with($i, ".bak.php") && !str_ends_with($i, ".old.php") && !str_ends_with($i, "-dev.php") && !str_ends_with($i, ".bak.inc") && !str_ends_with($i, ".old.inc") && !str_ends_with($i, "-dev.inc");
+ }));
+ $list = array_values(array_filter(array_map(function ($i) use ($lang) {
+ global $pages;
+ global $isLoggedIn;
+ global $isLowerLoggedIn;
+
+ if (in_array(substr($i, 0, strlen($i) - 4), array_keys($pages)) && $pages[substr($i, 0, strlen($i) - 4)]["admin"] && !((!$pages[substr($i, 0, strlen($i) - 4)]["limited"] && $isLoggedIn) || ($pages[substr($i, 0, strlen($i) - 4)]["limited"] && $isLowerLoggedIn))) {
+ return null;
+ } else if (in_array(substr($i, 0, strlen($i) - 4), array_keys($pages))) {
+ return [
+ 'name' => in_array(substr($i, 0, strlen($i) - 4), array_keys($pages)) ? $pages[substr($i, 0, strlen($i) - 4)]["name"][$lang["_name"]] : substr($i, 0, strlen($i) - 4),
+ 'description' => in_array(substr($i, 0, strlen($i) - 4), array_keys($pages)) ? ($pages[substr($i, 0, strlen($i) - 4)]["short"] ?? $pages[substr($i, 0, strlen($i) - 4)]["name"][$lang["_name"]]) : substr($i, 0, strlen($i) - 4),
+ 'url' => "/-/" . substr($i, 0, strlen($i) - 4),
+ 'icon' => file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/icons/uncolored/" . substr($i, 0, strlen($i) - 4) . ".svg") ? ("/assets/icons/uncolored/" . (file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/icons/uncolored/" . substr($i, 0, strlen($i) - 4) . ".svg") ? substr($i, 0, strlen($i) - 4) : "") . ".svg") : ("/assets/icons/" . (file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/icons/" . substr($i, 0, strlen($i) - 4) . ".svg") ? substr($i, 0, strlen($i) - 4) : "") . ".svg"),
+ 'invert' => true
+ ];
+ } else {
+ return null;
+ }
+ }, $base), function ($i) {
+ return isset($i);
+ }));
+
+ echo(base64_encode(json_encode($list)));
+
+ ?>`));
+ const pages = new Fuse(pages_list, {
+ includeScore: true,
+ keys: [
+ {
+ name: 'name',
+ weight: 0.7
+ },
+ {
+ name: 'description',
+ weight: 0.5
+ },
+ {
+ name: 'url',
+ weight: 0.2
+ }
+ ]
+ })
+
+ const ponies_list = JSON.parse(atob(`<?php
+
+ $base = array_values(scoreOrderGlobal());
+ $list = array_map(function ($i) {
+ return [
+ 'name' => $i["display_name"] ?? $i["name"],
+ 'description' => file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$i[id].html") ? strip_tags(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$i[id].html")) : "",
+ 'url' => '/' . $i["name"],
+ 'icon' => getAsset($i["system"], $i["id"], "heads"),
+ 'invert' => false
+ ];
+ }, $base);
+
+ echo(base64_encode(json_encode($list)));
+
+ ?>`));
+ const ponies = new Fuse(ponies_list, {
+ includeScore: true,
+ keys: [
+ {
+ name: 'name',
+ weight: 1
+ },
+ {
+ name: 'description',
+ weight: 0.7
+ },
+ {
+ name: 'url',
+ weight: 0.5
+ }
+ ]
+ })
+
+ const toys_list = JSON.parse(atob(`<?php
+
+ global $isLoggedIn;
+
+ if (false && $isLoggedIn) {
+ $base = array_values(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys/toys.json"), true));
+ $list = array_map(function ($i) {
+ return [
+ 'name' => $i["name"],
+ 'description' => $i["description"],
+ 'icon' => "/assets/icons/toys.svg",
+ 'url' => "/-/toys/$i[id]",
+ 'invert' => true
+ ];
+ }, $base);
+
+ echo(base64_encode(json_encode($list)));
+ } else {
+ echo(base64_encode(json_encode([])));
+ }
+
+ ?>`));
+ const toys = new Fuse(toys_list, {
+ includeScore: true,
+ keys: [
+ {
+ name: 'name',
+ weight: 1
+ },
+ {
+ name: 'description',
+ weight: 0.7
+ },
+ {
+ name: 'url',
+ weight: 0.5
+ }
+ ]
+ })
+
+ const actions_list = JSON.parse(atob(`<?php
+
+ global $isLoggedIn;
+
+ if (false && $isLoggedIn) {
+ $base = array_values(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions/actions.json"), true));
+ $list = array_map(function ($i) {
+ return [
+ 'name' => $i["name"],
+ 'description' => $i["description"],
+ 'icon' => "/assets/icons/actions.svg",
+ 'url' => "/-/actions/$i[id]",
+ 'invert' => true
+ ];
+ }, $base);
+
+ echo(base64_encode(json_encode($list)));
+ } else {
+ echo(base64_encode(json_encode([])));
+ }
+
+ ?>`));
+ const actions = new Fuse(actions_list, {
+ includeScore: true,
+ keys: [
+ {
+ name: 'name',
+ weight: 1
+ },
+ {
+ name: 'description',
+ weight: 0.7
+ },
+ {
+ name: 'url',
+ weight: 0.5
+ }
+ ]
+ })
+
+ const documents_list = JSON.parse(atob(`<?php
+
+ global $isLoggedIn;
+
+ if ($isLoggedIn) {
+ $base = array_values(array_map(function ($i) {
+ return [
+ "id" => substr($i, 0, -5),
+ ...(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/docs/" . $i), true) ?? [])
+ ];
+ }, array_filter(scandir($_SERVER['DOCUMENT_ROOT'] . "/includes/data/docs"), function ($i) {
+ return !str_starts_with($i, ".") && str_ends_with($i, ".json");
+ })));
+ $list = array_map(function ($i) {
+ return [
+ 'name' => $i["name"],
+ 'content' => strip_tags($i["contents"]),
+ 'category' => $i["category"],
+ 'icon' => "/assets/icons/docs.svg",
+ 'url' => "/-/docs/$i[id]",
+ 'invert' => true
+ ];
+ }, $base);
+
+ echo(base64_encode(json_encode($list)));
+ } else {
+ echo(base64_encode(json_encode([])));
+ }
+
+ ?>`));
+ const documents = new Fuse(documents_list, {
+ includeScore: true,
+ keys: [
+ {
+ name: 'name',
+ weight: 1
+ },
+ {
+ name: 'category',
+ weight: 0.7
+ },
+ {
+ name: 'content',
+ weight: 0.4
+ },
+ {
+ name: 'url',
+ weight: 0.5
+ }
+ ]
+ })
+
+ window.shiftPresses = 0;
+ window.searchBoxOpen = false;
+ window.lastKnownSearchQuery = null;
+
+ function toggleGlobalSearch() {
+ window.shiftPresses = 0;
+ console.log("Search!");
+
+ if (document.getElementById("global-search-container").style.display === "none") {
+ document.getElementById("global-search-container").style.display = "flex";
+ document.getElementById("global-search-list").style.display = "none";
+ document.getElementById("global-search-intro").style.display = "flex";
+ document.getElementById("global-search-input").focus();
+ document.getElementById("global-search-input").value = "";
+ window.searchBoxOpen = true;
+ } else if (document.getElementById("global-search-container").style.display === "flex") {
+ document.getElementById("global-search-container").style.display = "none";
+ window.searchBoxOpen = false;
+ }
+ }
+
+ function globalSearchPlaceholder() {
+ if (document.getElementById("global-search-input").innerText === "") {
+ document.getElementById("global-search-placeholder").style.display = "";
+ document.getElementById("global-search-autocomplete").innerText = "";
+ document.getElementById("global-search-action").innerText = "";
+ } else {
+ document.getElementById("global-search-placeholder").style.display = "none";
+ }
+ }
+
+ function globalSearchAutocomplete(firstResult) {
+ let querySearch = document.getElementById("global-search-input").innerText.toLowerCase();
+ let firstResultSearch = firstResult.name.toLowerCase();
+ let isAutocomplete = true;
+
+ if (firstResultSearch.length > querySearch.length) {
+ let count = 0;
+
+ for (let i = 0; i < querySearch.length; i++) {
+ if (querySearch.substring(i, i + 1) === firstResultSearch.substring(i, i + 1)) {
+ count++;
+ } else {
+ isAutocomplete = false;
+ }
+ }
+
+ if (isAutocomplete) {
+ document.getElementById("global-search-autocomplete").innerText = firstResult.name.substring(count);
+ document.getElementById("global-search-action").innerText = " — <?= $lang["search"]["view"] ?>";
+ } else {
+ document.getElementById("global-search-autocomplete").innerText = "";
+ document.getElementById("global-search-action").innerText = " — " + firstResult.name;
+ }
+ } else {
+ document.getElementById("global-search-autocomplete").innerText = "";
+ document.getElementById("global-search-action").innerText = "";
+ }
+ }
+
+ function globalSearch() {
+ let query = document.getElementById("global-search-input").innerText.trim();
+
+ if (query !== lastKnownSearchQuery) {
+ window.lastKnownSearchQuery = query;
+ } else {
+ return;
+ }
+
+ let results, scores;
+ let categories = [];
+ console.log("Query:", query);
+
+ if (query.trim() === "") {
+ document.getElementById("global-search-list").style.display = "none";
+ document.getElementById("global-search-intro").style.display = "flex";
+ return;
+ } else {
+ document.getElementById("global-search-list").style.display = "block";
+ document.getElementById("global-search-intro").style.display = "none";
+ }
+
+ results = ponies.search(query).map((i) => {
+ i.item.score = i.score;
+ return i.item;
+ });
+ scores = results.map((i) => { return i.score; });
+ let results_ponies = {
+ category: {
+ name: "<?= $lang["search"]["categories"][0] ?>",
+ id: "ponies"
+ },
+ average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1,
+ results
+ };
+
+ results = pages.search(query).map((i) => {
+ i.item.score = i.score;
+ return i.item;
+ });
+ scores = results.map((i) => { return i.score; });
+ let results_pages = {
+ category: {
+ name: "<?= $lang["search"]["categories"][1] ?>",
+ id: "pages"
+ },
+ average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1,
+ results
+ };
+
+ results = actions.search(query).map((i) => {
+ i.item.score = i.score;
+ return i.item;
+ });
+ scores = results.map((i) => { return i.score; });
+ let results_actions = {
+ category: {
+ name: "<?= $lang["search"]["categories"][2] ?>",
+ id: "actions"
+ },
+ average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1,
+ results
+ };
+
+ results = documents.search(query).map((i) => {
+ i.item.score = i.score;
+ return i.item;
+ });
+ scores = results.map((i) => { return i.score; });
+ let results_documents = {
+ category: {
+ name: "<?= $lang["search"]["categories"][3] ?>",
+ id: "docs"
+ },
+ average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1,
+ results
+ };
+
+ results = toys.search(query).map((i) => {
+ i.item.score = i.score;
+ return i.item;
+ });
+ scores = results.map((i) => { return i.score; });
+ let results_toys = {
+ category: {
+ name: "<?= $lang["search"]["categories"][4] ?>",
+ id: "toys"
+ },
+ average: scores.length > 0 ? scores.reduce((a, b) => { return a + b; }) / results.length : 1,
+ results
+ };
+
+ window.allSearchResults = [];
+ categories.push(results_ponies, results_pages, results_documents, results_actions, results_toys);
+ categories = categories.sort((a, b) => {
+ return a.average - b.average;
+ }).filter((i) => i.results.length > 0);
+
+ console.log(categories);
+
+ index = -1;
+ document.getElementById("global-search-list").innerHTML = categories.map((i) => `
+ <div class="global-search-category">
+ <div class="global-search-category-header">${i.category.name}</div>
+ <div class="global-search-category-items">
+ ${i.results.map((j) => { index++; allSearchResults.push(j); return `
+ <a href="${j.url}" class="global-search-item ${index === 0 ? 'selected' : ''}" id="global-search-item-${index}">
+ <img src="${j.icon}" class="global-search-item-icon" ${j['invert'] ? 'style="filter:invert(1) brightness(5);"' : ""}>
+ <span class="global-search-item-title">${j.name}</span>
+ </a>
+ ` }).join("")}
+ </div>
+ </div>
+ `).join("");
+
+ globalSearchAutocomplete(allSearchResults[0]);
+
+ console.log("=================================");
+ }
+
+ document.onkeydown = (event) => {
+ if (window.searchBoxOpen) {
+ console.log(event);
+
+ if (event.key === "Escape") {
+ if (document.getElementById("global-search-input").innerText.trim() === "") {
+ toggleGlobalSearch();
+ } else {
+ document.getElementById("global-search-input").innerText = "";
+ globalSearch();
+ }
+ }
+
+ if (event.key === "ArrowDown") {
+ let id = parseInt(document.querySelector(".global-search-item.selected").id.split("-")[3]);
+
+ if (document.getElementById("global-search-item-" + (id + 1)) !== null) {
+ document.getElementById("global-search-item-" + id).classList.remove("selected");
+ document.getElementById("global-search-item-" + (id + 1)).classList.add("selected");
+ document.getElementById("global-search-item-" + (id + 1)).scrollIntoView({
+ block: "center",
+ inline: "center"
+ });
+
+ globalSearchAutocomplete(allSearchResults[id + 1]);
+ }
+
+ event.preventDefault();
+ }
+
+ if (event.key === "ArrowUp") {
+ let id = parseInt(document.querySelector(".global-search-item.selected").id.split("-")[3]);
+
+ if (document.getElementById("global-search-item-" + (id - 1)) !== null) {
+ document.getElementById("global-search-item-" + id).classList.remove("selected");
+ document.getElementById("global-search-item-" + (id - 1)).classList.add("selected");
+ document.getElementById("global-search-item-" + (id - 1)).scrollIntoView({
+ block: "center",
+ inline: "center"
+ });
+
+ globalSearchAutocomplete(allSearchResults[id - 1]);
+ }
+
+ event.preventDefault();
+ }
+
+ if (event.key === "Enter") {
+ document.querySelector(".global-search-item.selected").click();
+
+ event.preventDefault();
+ }
+ }
+
+ if (event.key === "Shift") {
+ window.shiftPresses++;
+ }
+
+ if ((event.code === "Space" && event.ctrlKey) || window.shiftPresses === 2 && document.getElementsByClassName("ck-focused").length === 0) {
+ toggleGlobalSearch();
+ }
+ }
+
+ document.getElementById("global-search-container").onclick = (event) => {
+ if (event.target === document.getElementById("global-search-container")) toggleGlobalSearch();
+ }
+
+ setInterval(() => {
+ window.shiftPresses = 0;
+ }, 500);
+</script>
+
+<style>
+ .global-search-category {
+ text-align: left;
+ padding: 10px 20px;
+ }
+
+ .global-search-category-header {
+ font-size: 12px;
+ opacity: .5;
+ margin-bottom: 5px;
+ margin-top: 10px;
+ }
+
+ .global-search-item-icon {
+ vertical-align: middle;
+ width: 24px;
+ height: 24px;
+ margin-right: 5px;
+ }
+
+ .global-search-item-title {
+ vertical-align: middle;
+ }
+
+ .global-search-item {
+ text-decoration: none;
+ display: block;
+ color: white !important;
+ border-radius: 10px;
+ padding: 3px 5px;
+ height: 32px;
+ }
+
+ .global-search-item:hover, .global-search-item:focus, .global-search-item.selected {
+ background-color: rgba(255, 255, 255, .1);
+ }
+
+ .global-search-item:active {
+ background-color: rgba(255, 255, 255, .2);
+ }
+
+ #global-search-container * {
+ outline: none !important;
+ }
+</style> \ No newline at end of file
diff --git a/includes/components/sysbanner.inc b/includes/components/sysbanner.inc
new file mode 100644
index 0000000..8819617
--- /dev/null
+++ b/includes/components/sysbanner.inc
@@ -0,0 +1,96 @@
+<?php
+
+global $memberData;
+global $memberCommonName;
+global $memberID;
+global $systemCommonName;
+global $systemID;
+global $system;
+global $lang; global $pages; global $app;
+
+$travelling = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/travelling/travelling.json"), true);
+$pages = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/pages.json"), true);
+
+?>
+
+<div id="system-info" style="display:grid;grid-template-columns: 128px 1fr;background-color:rgba(255, 255, 255, .05);margin-left: -20px; margin-right: -20px;margin-top:-20px;padding: 20px 20px 10px;">
+ <div style="display: flex; align-items: center; justify-content: center;">
+ <img src="<?= getAsset($systemID) ?>" alt="" style="width:128px;max-height:128px;border-radius:10px;">
+ </div>
+ <div style="padding:10px 10px 10px 20px;text-align:center;">
+ <div style="display: grid; grid-template-columns: 1fr;height:100%;grid-template-rows: max-content max-content 1fr;">
+ <h3 style="height:max-content;"><?= $systemCommonName ?></h3>
+ <div style="height:max-content;display:grid;grid-template-columns: repeat(4, 1fr);" id="member-card">
+ <span>
+ <?php $fronters = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID/fronters.json"), true); ?>
+ <b><?= $lang["system"]["fronter"] ?> </b>
+ <?php if (isset($fronters["members"][0])): ?>
+ <?php $member = $fronters["members"][0]; ?>
+ <a class="member-link" href="/<?= $member["name"] ?>"><img src="<?= getAsset($systemID, $member["id"], "heads") ?>" style="width:24px;"> <?= getMiniName($member["display_name"] ?? ($member["name"] === "fusion" ? "<peh-muted>" . $lang["system"]["more"] . "</peh-muted>" : $member["name"])) ?></a>
+ <?php if (count($fronters["members"]) > 1): ?>
+ <br>and
+ <?php if (isset($fronters["members"][1])): ?><?php $member2 = $fronters["members"][1]; ?><a class="member-link" href="/<?= $member2["name"] ?>"><img src="<?= getAsset($systemID, $member2["id"], "heads") ?>" style="width:24px;"> <?= getMiniName($member2["display_name"] ?? ($member2["name"] === "fusion" ? "<peh-muted>" . $lang["system"]["more"] . "</peh-muted>" : $member2["name"])) ?></a><?php endif; ?><?php if (isset($fronters["members"][2])): ?><?php $member2 = $fronters["members"][2]; ?>, <a class="member-link" href="/<?= $member2["name"] ?>"><img src="<?= getAsset($systemID, $member2["id"], "heads") ?>" style="width:24px;"> <?= getMiniName($member2["display_name"] ?? ($member2["name"] === "fusion" ? "<peh-muted>" . $lang["system"]["more"] . "</peh-muted>" : $member2["name"])) ?></a><?php endif; ?><?php if (isset($fronters["members"][3])): ?><?php $member2 = $fronters["members"][3]; ?>, <a class="member-link" href="/<?= $member2["name"] ?>"><img src="<?= getAsset($systemID, $member2["id"], "heads") ?>" style="width:24px;"> <?= getMiniName($member2["display_name"] ?? ($member2["name"] === "fusion" ? "<peh-muted>" . $lang["system"]["more"] . "</peh-muted>" : $member2["name"])) ?></a><?php endif; ?><?php endif; ?>
+ <?php else: ?>-<?php endif; ?>
+ </span>
+ <span>
+ <b><?= $lang["system"]["last"] ?> </b>
+ <?php
+
+ $previous = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID/switches.json"), true)[1]["members"];
+
+ if (isset($previous[0])):
+ $previousID = $previous[0];
+ $member = null;
+
+ foreach (json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID/members.json"), true) as $members) {
+ if ($members["id"] === $previousID) {
+ $member = $members;
+ break;
+ }
+ }
+
+ ?>
+ <a class="member-link" href="/<?= $member["name"] ?>"><img src="<?= getAsset($systemID, $member["id"], "heads") ?>" style="width:24px;"> <?= getMiniName($member["display_name"] ?? ($member["name"] === "fusion" ? "<peh-muted>" . $lang["system"]["more"] . "</peh-muted>" : $member["name"])) ?></a>
+ <?php if (count($previous) > 1): ?>
+ <br>and <?php if (isset($previous[1])): ?><?php $member2 = getMemberWithoutSystem($previous[1]); ?><a class="member-link" href="/<?= $member2["name"] ?>"><img src="<?= getAsset($systemID, $member2["id"], "heads") ?>" style="width:24px;"> <?= getMiniName($member2["display_name"] ?? ($member2["name"] === "fusion" ? "<peh-muted>" . $lang["system"]["more"] . "</peh-muted>" : $member2["name"])) ?></a><?php endif; ?><?php if (isset($previous[2])): ?><?php $member2 = getMemberWithoutSystem($previous[2]); ?>, <a class="member-link" href="/<?= $member2["name"] ?>"><img src="<?= getAsset($systemID, $member2["id"], "heads") ?>" style="width:24px;"> <?= getMiniName($member2["display_name"] ?? ($member2["name"] === "fusion" ? "<peh-muted>" . $lang["system"]["more"] . "</peh-muted>" : $member2["name"])) ?></a><?php endif; ?><?php if (isset($previous[3])): ?><?php $member2 = getMemberWithoutSystem($previous[3]); ?>, <a class="member-link" href="/<?= $member2["name"] ?>"><img src="<?= getAsset($systemID, $member2["id"], "heads") ?>" style="width:24px;"> <?= getMiniName($member2["display_name"] ?? ($member2["name"] === "fusion" ? "<peh-muted>" . $lang["system"]["more"] . "</peh-muted>" : $member2["name"])) ?></a><?php endif; ?>
+ <?php endif; ?>
+ <?php else: ?>-<?php endif; ?>
+ </span>
+ <span>
+ <?php
+
+ if ($systemID === $app["other"]["id"]) {
+ $travellers = [];
+ } else {
+ $travellers = array_filter(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/" . ($systemID === "gdapd" ? "ynmuc" : "gdapd") . "/members.json"), true), function ($i) use ($travelling) {
+ return $travelling[$i['id']]['travelling'] && !$travelling[$i['id']]['equestria'];
+ });
+ }
+
+ ?>
+ <b><?= $lang["system"]["members"] ?> </b><?= count(scoreOrder(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID/members.json"), true), $systemID)) ?><?php
+
+ if (count($travellers) > 0) {
+ echo("<br>+ " . count($travellers) . " " . (count($travellers) > 1 ? $lang["system"]["traveller"] : $lang["system"]["travellers"]));
+ }
+
+ ?>
+ </span>
+ <span>
+ <b><?= $lang["system"]["switch"] ?> </b><span data-bs-toggle="tooltip" title="<?= date("D j M Y, G:i:s (e)", strtotime(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID/fronters.json"), true)["timestamp"])) ?>"><?= timeAgo(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID/fronters.json"), true)["timestamp"]) ?></span>
+ </span>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div id="system-actions" style="padding:5px 30px;display:grid;grid-template-columns: 1fr 1fr;background-color:rgba(255, 255, 255, .025);margin-left: -20px; margin-right: -20px;">
+ <a title="<?= $pages["s:history"]["name"][$lang["_name"]] ?>" data-bs-toggle="tooltip" style="display:inline-block;padding:5px 10px;text-align: center" class="system-action tooltip-nohelp" href="/<?= $system ?>/-/history">
+ <img src="/assets/icons/history.svg" style="vertical-align: middle;height: 24px;width: 24px;filter: invert(1)" alt="">
+ <span style="vertical-align: middle;" class="list-separator-desktop"><?= $pages["s:history"]["name"][$lang["_name"]] ?></span>
+ </a>
+ <a title="<?= $pages["s:compare"]["name"][$lang["_name"]] ?>" data-bs-toggle="tooltip" style="display:inline-block;padding:5px 10px;text-align: center" class="system-action tooltip-nohelp" href="/<?= $system ?>/-/compare">
+ <img src="/assets/icons/compare.svg" style="vertical-align: middle;height: 24px;width: 24px;filter: invert(1)" alt="">
+ <span style="vertical-align: middle;" class="list-separator-desktop"><?= $pages["s:compare"]["name"][$lang["_name"]] ?></span>
+ </a>
+</div> \ No newline at end of file
diff --git a/includes/components/wakeup.inc b/includes/components/wakeup.inc
new file mode 100644
index 0000000..587e705
--- /dev/null
+++ b/includes/components/wakeup.inc
@@ -0,0 +1,114 @@
+<h2>Wake-up alert
+ <details style="display: inline-block;font-size:12px;">
+ <summary class="text-muted" style="opacity:.5;"></summary>
+ <label><input id="test-mode" type="checkbox"> Test Mode</label> · <label><input id="fake-requests" type="checkbox"> Fake Requests</label>
+ </details>
+</h2>
+
+<span data-bs-toggle="modal" data-bs-target="#turn-on" id="btn-on" style="background: #7f0000;font-size: 48px;padding: 10px 50px;border-radius: 10px;width: max-content;display: block;margin-left: auto;margin-right: auto;cursor: pointer;">Turn <b>ON</b></span>
+<span onclick="disableAlert()" id="btn-off" style="display:none;background: #007f0b;font-size: 48px;padding: 10px 50px;border-radius: 10px;width: max-content;margin-left: auto;margin-right: auto;cursor: pointer;">Turn <b>OFF</b></span>
+<p style="text-align:center;margin-top:10px;">Sending next notification <b><span id="next-notification">never</span></b></p>
+
+<style>
+ .modal-header {
+ border-bottom: 1px solid #353738;
+ }
+
+ .modal-content {
+ border: 1px solid rgba(255, 255, 255, .2);
+ background-color: #111;
+ }
+
+ .btn-close {
+ filter: invert(1);
+ }
+</style>
+
+<div class="modal fade" id="turn-on">
+ <div class="modal-dialog">
+ <div class="modal-content">
+
+ <div class="modal-header">
+ <h4 class="modal-title">Somepony will be awoken.</h4>
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+
+ <div class="modal-body">
+ <button onclick="enableAlert();" data-bs-dismiss="modal" class="btn btn-success" style="font-size:20px;font-weight:bold;display:block;width:100%;">I wish to proceed.</button>
+
+ <hr>
+
+ <div class="alert alert-warning">
+ This alert system is designed to emit sudden alerts, and may surprise somecreature if e.g. they are sleeping. Keep that in mind.
+ </div>
+
+ <p>
+ <b>Disclaimer:</b> This is NOT an emergency alert system, use the "Emergency alert" option is you need immediate help and comfort.
+ </p>
+ <p>
+ © <?= date('Y') ?> Equestria.dev
+ </p>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script>
+ window.alertInterval = null;
+ window.alertIntervalCounter = 15;
+
+ function sendNotification() {
+ window.alertIntervalCounter = -1;
+
+ if (document.getElementById("test-mode").checked) {
+ document.getElementById("next-notification").innerText = "now";
+ if (document.getElementById("fake-requests").checked) {
+ window.alertIntervalCounter = 15;
+ document.getElementById("next-notification").innerText = "15 seconds";
+ } else {
+ window.fetch("/api/wakeup").then(() => {
+ window.alertIntervalCounter = 15;
+ document.getElementById("next-notification").innerText = "15 seconds";
+ })
+ }
+ } else {
+ document.getElementById("next-notification").innerText = "now";
+ if (document.getElementById("fake-requests").checked) {
+ window.alertIntervalCounter = 15;
+ document.getElementById("next-notification").innerText = "15 seconds";
+ } else {
+ window.fetch("/api/wakeup-real").then(() => {
+ window.alertIntervalCounter = 15;
+ document.getElementById("next-notification").innerText = "15 seconds";
+ })
+ }
+ }
+ }
+
+ function enableAlert() {
+ sendNotification();
+ document.getElementById("btn-on").style.display = "none";
+ document.getElementById("btn-off").style.display = "block";
+ document.getElementById("test-mode").disabled = true;
+ document.getElementById("fake-requests").disabled = true;
+
+ window.alertInterval = setInterval(() => {
+ window.alertIntervalCounter--;
+
+ if (window.alertIntervalCounter === 0) {
+ sendNotification();
+ } else if (window.alertIntervalCounter > -1) {
+ document.getElementById("next-notification").innerText = window.alertIntervalCounter + " second" + (window.alertIntervalCounter > 1 ? "s" : "");
+ }
+ }, 1000);
+ }
+
+ function disableAlert() {
+ clearInterval(window.alertInterval);
+ document.getElementById("next-notification").innerText = "never";
+ document.getElementById("btn-on").style.display = "block";
+ document.getElementById("btn-off").style.display = "none";
+ document.getElementById("test-mode").disabled = false;
+ document.getElementById("fake-requests").disabled = false;
+ }
+</script> \ No newline at end of file