summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
authorMinteck <contact@minteck.org>2022-08-10 00:04:25 +0200
committerMinteck <contact@minteck.org>2022-08-10 00:04:25 +0200
commit0417260bf158c5d7b62888dae2e202b0de115a05 (patch)
tree7f54ee56da118813f2d1b3a324d322d0c74fa51b /includes
downloadpluralconnect-0417260bf158c5d7b62888dae2e202b0de115a05.tar.gz
pluralconnect-0417260bf158c5d7b62888dae2e202b0de115a05.tar.bz2
pluralconnect-0417260bf158c5d7b62888dae2e202b0de115a05.zip
Initial commit
Diffstat (limited to 'includes')
-rw-r--r--includes/banner.php222
-rw-r--r--includes/edit.php174
-rw-r--r--includes/footer.php57
-rw-r--r--includes/header.php719
-rw-r--r--includes/member.php84
-rw-r--r--includes/refresh.php35
-rw-r--r--includes/score.php74
-rw-r--r--includes/session.php19
-rw-r--r--includes/subsysbanner.php68
-rw-r--r--includes/subsysedit.php172
-rw-r--r--includes/sysbanner.php80
-rw-r--r--includes/sysedit.php172
-rw-r--r--includes/system.php61
-rw-r--r--includes/system/compare.php189
-rw-r--r--includes/system/history.php380
-rw-r--r--includes/system/species.php53
-rw-r--r--includes/system/subsystem.php124
-rw-r--r--includes/system/tree.php114
18 files changed, 2797 insertions, 0 deletions
diff --git a/includes/banner.php b/includes/banner.php
new file mode 100644
index 0000000..b5088df
--- /dev/null
+++ b/includes/banner.php
@@ -0,0 +1,222 @@
+<?php
+
+global $memberData;
+global $memberCommonName;
+global $memberID;
+global $systemCommonName;
+global $systemID;
+global $system;
+
+$subsystems = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-subsystems.json"), true) ?? [];
+
+function getMember(string $id) {
+ global $systemID;
+
+ $members = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-members.json"), true);
+ $member = null;
+
+ foreach ($members as $m) {
+ if ($m["id"] === $id) $member = $m;
+ }
+
+ return $member;
+}
+
+function memberPartOfSubsystem(array $member) {
+ global $subsystems;
+ $is = false;
+
+ foreach ($subsystems as $subsystem) {
+ if (in_array($member["id"], $subsystem["members"])) {
+ $is = true;
+ }
+ }
+
+ return $is;
+}
+
+function getSubsystemFromMember(array $member) {
+ global $subsystems;
+ $ss = false;
+
+ foreach ($subsystems as $subsystem) {
+ if (in_array($member["id"], $subsystem["members"])) {
+ $ss = $subsystem;
+ }
+ }
+
+ return $ss;
+}
+
+?>
+
+<style>
+ .bg-light, .bg-light * {
+ color: black !important;
+ }
+</style>
+<div id="system-info" style="border:1px solid #<?= $memberData["color"] ?>;background:rgba(255, 255, 255, .1);border-radius:10px;display:grid;grid-template-columns: 128px 1fr;">
+ <div style="margin:10px;width:100%;display:flex;align-items: center;justify-content: center;">
+ <img id="member-icon" src="<?= $memberData['avatar_url'] ?>" alt="" style="height:128px;border-radius:5px;">
+ </div>
+ <div style="padding:10px 10px 10px 20px;text-align:center;">
+ <h3 style="margin-bottom:0;">
+ <?= $memberCommonName ?>
+ </h3>
+ <div style="margin-bottom:0.5rem;">
+ <?php if ($metadata["host"] ?? false): ?>
+ <span data-bs-toggle="tooltip" data-bs-html="true" title="<b>Host</b><br>This pony is the one who fronts the most often in their system." class="badge rounded-pill bg-primary">Host</span>
+ <?php endif; ?>
+ <?php if ($metadata["fictive"] ?? false): ?>
+ <span data-bs-toggle="tooltip" data-bs-html="true" title="<b>Fictive</b><br>This pony is based on the personality, look and behavior of a character that is fictional in this world." class="badge rounded-pill bg-info">Fictive</span>
+ <?php endif; ?>
+ <?php if (($metadata["little"] ?? 0) === 2): ?>
+ <span data-bs-toggle="tooltip" data-bs-html="true" title="<b>Little</b><br>This pony is mental younger, and therefore behaves and feels younger than the body is." class="badge rounded-pill bg-success">Little</span>
+ <?php endif; ?>
+ <?php if (($metadata["little"] ?? 0) === 1): ?>
+ <?php if ($metadata["regression"] !== null && $metadata["regression"] !== false): $regression = getMember($metadata["regression"]); ?>
+ <span data-bs-toggle="tooltip" data-bs-html="true" title="<b>Age regressor</b><br>This pony is capable of regressing their mental age, which causes them to become <?= getMiniName($regression["display_name"] ?? $regression["name"]) ?>, temporarily behaving and feeling younger than the body is." class="badge rounded-pill bg-secondary">Age regresses into <a href="/<?= $system ?>/<?= $regression["name"] ?>"><?= getMiniName($regression["display_name"] ?? $regression["name"]) ?></a></span>
+ <?php else: ?>
+ <span data-bs-toggle="tooltip" data-bs-html="true" title="<b>Age regressor</b><br>This pony is capable of regressing their mental age, temporarily behaving and feeling younger than the body is." class="badge rounded-pill bg-secondary">Age regressor</span>
+ <?php endif; ?>
+ <?php endif; ?>
+ <?php if ($metadata["median"] !== null && $metadata["median"] !== false): $source = getMember($metadata["median"]) ?>
+ <?php if ($metadata["little"] > 0): ?>
+ <span data-bs-toggle="tooltip" data-bs-html="true" title="<b>Age regressed</b><br>This pony has regressed their mental age, making them <?= getMiniName($memberData["display_name"] ?? $memberData["name"]) ?> instead of <?= getMiniName($source["display_name"] ?? $source["name"]) ?>, temporarily behaving and feeling younger than the body is." class="badge rounded-pill bg-warning">Age regressed from <a href="/<?= $system ?>/<?= $source["name"] ?>"><?= getMiniName($source["display_name"] ?? $source["name"]) ?></a></span>
+ <?php else: ?>
+ <span data-bs-toggle="tooltip" data-bs-html="true" title="<b>Facet</b><br>This pony is a facet of <?= getMiniName($source["display_name"] ?? $source["name"]) ?>, meaning they are not totally independent from <?= getMiniName($source["display_name"] ?? $source["name"]) ?>." class="badge rounded-pill bg-light">Facet of <a href="/<?= $system ?>/<?= $source["name"] ?>"><?= getMiniName($source["display_name"] ?? $source["name"]) ?></a></span>
+ <?php endif; ?>
+ <?php endif; ?>
+ <?php if ($metadata["not_talking"] ?? false): ?>
+ <span data-bs-toggle="tooltip" data-bs-html="true" title="<b>Not talking</b><br>Although they are present in the system, this pony does not want to communicate with other members." class="badge rounded-pill bg-danger">Not talking</span>
+ <?php endif; ?>
+ <?php if (!($metadata["fictive"] ?? false) && !($metadata["host"] ?? false) && !($metadata["little"] ?? false) && !($metadata["not_talking"] ?? false)): ?>
+ &nbsp;
+ <?php endif; ?>
+ </div>
+ <div style="display:grid;grid-template-columns: repeat(<?php if (!$metadata["median"]): ?>5<?php else: ?>4<?php endif; ?>, 1fr);" id="member-card">
+ <span>
+ <b>Prefixes: </b>
+ <?php $index = 0; foreach ($memberData['proxy_tags'] as $proxy): ?>
+ <code style="color: white;"><?= $proxy["prefix"] ?><?= $proxy["suffix"] !== "" && $proxy["suffix"] !== null ? "..." . $proxy["suffix"] : "" ?></code><?php if ($index + 2 <= count($memberData["proxy_tags"])) echo(", "); ?>
+ <?php $index++; endforeach; ?>
+ </span>
+ <span>
+ <b>Pronouns: </b>
+ <?= $memberData["pronouns"] ?>
+ </span>
+ <?php if (!$metadata["median"]): ?>
+ <span>
+ <b>Last fronted: </b>
+ <?php
+
+ $fronters = array_map(function ($item) {
+ return $item["id"];
+ }, json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-fronters.json"), true)["members"]);
+
+ if (in_array($memberID, $fronters)) {
+ echo("Right now<br>(started <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"])) . "\">" . trim(timeAgo(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-fronters.json"), true)["timestamp"])) . "</span>)");
+ } else {
+ $switches = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-switches.json"), true);
+
+ $thisMember = array_filter($switches, function ($item) {
+ global $memberData;
+ return in_array($memberData["id"], $item["members"]);
+ });
+ $thisMember = array_values($thisMember);
+ $thisIndex = array_search($thisMember[0], $switches);
+
+ $frontingStart = $thisMember[0];
+ $frontingEnd = $switches[$thisIndex - 1];
+
+ if ($frontingEnd === null) {
+ echo("A long time ago<br>-");
+ } else {
+ echo('<span data-bs-toggle="tooltip" title="' . date("D j M Y, G:i:s (e)", strtotime($frontingEnd["timestamp"])) . '">' . timeAgo($frontingEnd["timestamp"]) . '</span>');
+
+ $seconds = (strtotime($frontingEnd["timestamp"]) - strtotime($frontingStart["timestamp"]));
+ if ($seconds > 60) {
+ if ($seconds > 3600) {
+ echo("<br>(for " . round($seconds / 3600) . " hours)");
+ } else {
+ echo("<br>(for " . round($seconds / 60) . " minutes)");
+ }
+ } else {
+ echo("<br>(for " . $seconds . " seconds)");
+ }
+ }
+ }
+
+ ?>
+ </span>
+ <?php endif; ?>
+ <span>
+ <span style="vertical-align: middle;position:relative;top:-5px;"><b>Species: </b></span>
+ <?php foreach ($metadata["species"] ?? [] as $species): ?>
+ <img data-bs-toggle="tooltip" title="<?php switch ($species) {
+ case "earth":
+ echo "Earth pony";
+ break;
+
+ case "alicorn":
+ echo "Alicorn";
+ break;
+
+ case "pegasus":
+ echo "Pegasus";
+ break;
+
+ case "batpony":
+ echo "Bat pony";
+ break;
+
+ case "unicorn":
+ echo "Unicorn";
+ break;
+
+ default:
+ echo $species;
+ break;
+ } ?>" style="width:32px;vertical-align: middle;position:relative;top:-5px;" src="/assets/species/<?= $species ?>.png" alt="<?= $species ?>">
+ <?php endforeach; ?>
+ </span>
+ <span>
+ <b>System: </b><a class="member-link" href="/<?= $system ?>"><img style="width:24px;border-radius:5px;" src="/assets/uploads/<?= $system ?>.png"> <?= getMiniName($systemCommonName) ?></a>
+ <?php if (memberPartOfSubsystem($memberData) && getSubsystemFromMember($memberData)["source_type"] !== "member"): $subsystem = getSubsystemFromMember($memberData); ?>
+ <br><b>Subsystem: </b><a class="member-link" href="/<?= $system ?>/-/subsystem/<?= $subsystem["source"] ?>"><img style="width:24px;border-radius:5px;" src="/assets/uploads/ss-<?= $subsystem['source'] ?>.png"> <?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-subsystem-$subsystem[source].json") ? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-subsystem-$subsystem[source].json"), true)["name"] : $subsystem["source"] ?></a>
+ <?php endif; ?>
+ </span>
+ </div>
+ <div style="display:grid;grid-template-columns: repeat(2, 1fr);margin-top:5px;">
+ <?php
+
+ if ($memberData["name"] === "scootaloo") {
+ if ((int)date('j') % 2 === 0) {
+ $metadata["marefriends"] = array_reverse($metadata["marefriends"]);
+ }
+ }
+
+ ?>
+ <span>
+ <b>Marefriends: </b><?= count($metadata["marefriends"]) > 1 ? '<span class="list-separator-mobile"><br></span>' : '' ?>
+ <?php $index = 0; foreach ($metadata["marefriends"] as $marefriend): $mfSystem = explode("/", $marefriend)[0]; $mfMemberID = explode("/", $marefriend)[1]; $mfMember = array_filter(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$mfSystem-members.json"), true), function ($item) {
+ global $mfMemberID;
+ return $item["id"] === $mfMemberID;
+ }); sort($mfMember); $mfMember = $mfMember[0]; ?>
+ <a class="member-link" href="/<?= $mfSystem === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $mfMember["name"] ?>"><img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $mfMember['name'] . ".png") ? "-" . $mfMember['name'] : "" ?>.png" style="width:24px;"> <?= getMiniName($mfMember["display_name"] ?? $mfMember["name"]) ?></a><?php if ($index + 2 <= count($metadata["marefriends"])) echo('<span class="list-separator-desktop">, </span><span class="list-separator-mobile"><br></span>'); ?>
+ <?php $index++; endforeach; ?>
+ <?php if (count($metadata["marefriends"]) === 0): ?>-<?php endif; ?>
+ </span>
+ <span>
+ <b>Sisters: </b><?= count($metadata["sisters"]) > 1 ? '<span class="list-separator-mobile"><br></span>' : '' ?>
+ <?php $index = 0; foreach ($metadata["sisters"] as $marefriend): $mfSystem = explode("/", $marefriend)[0]; $mfMemberID = explode("/", $marefriend)[1]; $mfMember = array_filter(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$mfSystem-members.json"), true), function ($item) {
+ global $mfMemberID;
+ return $item["id"] === $mfMemberID;
+ }); sort($mfMember); $mfMember = $mfMember[0]; ?>
+ <a class="member-link" href="/<?= $mfSystem === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $mfMember["name"] ?>"><img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $mfMember['name'] . ".png") ? "-" . $mfMember['name'] : "" ?>.png" style="width:24px;"> <?= getMiniName($mfMember["display_name"] ?? $mfMember["name"]) ?></a><?php if ($index + 2 <= count($metadata["sisters"])) echo('<span class="list-separator-desktop">, </span><span class="list-separator-mobile"><br></span>'); ?>
+ <?php $index++; endforeach; ?>
+ <?php if (count($metadata["sisters"]) === 0): ?>-<?php endif; ?>
+ </span>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/includes/edit.php b/includes/edit.php
new file mode 100644
index 0000000..839c130
--- /dev/null
+++ b/includes/edit.php
@@ -0,0 +1,174 @@
+<?php global $system; global $systemCommonName; global $systemID; global $member; global $memberData; global $memberCommonName; global $memberID; $title = "Editing " . $memberCommonName . " · " . $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+
+function timeAgo($time): string {
+ if (!is_numeric($time)) {
+ $time = strtotime($time);
+ }
+
+ $periods = ["second", "minute", "hour", "day", "week", "month", "year", "age"];
+ $lengths = array("60", "60", "24", "7", "4.35", "12", "100");
+
+ $now = time();
+
+ $difference = $now - $time;
+ if ($difference <= 10 && $difference >= 0) {
+ return $tense = "now";
+ } elseif ($difference > 0) {
+ $tense = "ago";
+ } else {
+ $tense = "later";
+ }
+
+ for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) {
+ $difference /= $lengths[$j];
+ }
+
+ $difference = round($difference);
+
+ $period = $periods[$j] . ($difference >1 ? "s" :'');
+ return "{$difference} {$period} {$tense} ";
+}
+
+$metadata = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/" . $systemID . "-" . $memberID . "-metadata.json"), true);
+
+?>
+
+<br>
+<div class="container">
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/banner.php"; ?>
+ <br>
+
+ <p class="text-muted">
+ <span id="editor-save-status">Saved</span> · <span id="editor-size"><?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-$memberID-disclaimers.html") ? strlen(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-$memberID-disclaimers.html")) : "0" ?></span> bytes
+ </p>
+
+ <!--suppress HtmlFormInputWithoutLabel -->
+ <textarea id="page-editor">
+ <?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-$memberID-disclaimers.html") ? file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-$memberID-disclaimers.html") : "" ?>
+ </textarea>
+
+ <script src="/assets/editor/editor.js"></script>
+ <script>
+ let editor;
+ ClassicEditor
+ .create( document.querySelector( '#page-editor' ), {
+ toolbar: [
+ 'undo', 'redo', '|', 'removeFormat', '|', 'heading', '|', 'fontSize', 'fontColor', 'fontBackgroundColor', 'alignment', '|', 'bold', 'italic', 'underline', 'strikethrough', '|', 'subscript', 'superscript', '|', 'code', '|', 'outdent', 'indent', '|', 'bulletedList', 'numberedList', '|', 'link', 'imageUpload', 'mediaEmbed', 'blockQuote', 'insertTable', 'codeBlock', '|', 'horizontalLine'
+ ]
+ } )
+
+ .then( newEditor => {
+ editor = newEditor;
+ } )
+ .catch( error => {
+ console.error( error );
+ } );
+ </script>
+ <style>
+ :root {
+ --ck-color-base-background: transparent;
+ }
+
+ .ck-toolbar {
+ filter: invert(1);
+ }
+
+ .ck-tooltip__text {
+ color: white !important;
+ }
+
+ .ck-dropdown__panel {
+ background: #ddd !important;
+ }
+
+ .ck-color-grid__tile {
+ filter: invert(1);
+ }
+
+ .ck-balloon-rotator {
+ background-color: #ccc !important;
+ }
+
+ .ck-balloon-panel {
+ filter: invert(1);
+ }
+ </style>
+ <script>
+ let lastSavedData = editor.getData();
+ let lastFetchedData = editor.getData();
+ let timeSinceLastModified = 0;
+ let saving = false;
+
+ async function save() {
+ let data = editor.getData();
+ document.getElementById("editor-save-status").innerHTML = "Saving...";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.add("text-primary");
+ saving = true;
+
+ try {
+ await window.fetch("/api/save?system=<?= $systemID ?>&member=<?= $memberID ?>", {
+ method: "POST",
+ body: JSON.stringify({ content: data })
+ });
+ document.getElementById("editor-save-status").innerHTML = "Saved";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.add("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ lastSavedData = data;
+ saving = false;
+ } catch (e) {
+ console.error(e);
+ document.getElementById("editor-save-status").innerHTML = "Failed to save";
+ document.getElementById("editor-save-status").classList.add("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ }
+ }
+
+ document.onclick = async () => {
+ if (saving) return;
+
+ if (editor.getData() !== lastSavedData) {
+ await save();
+ }
+ }
+
+ setInterval(async () => {
+ if (saving) return;
+
+ document.getElementById("editor-size").innerHTML = editor.getData().length;
+
+ if (editor.getData() !== lastSavedData) {
+ document.getElementById("editor-save-status").innerHTML = "Modified";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.add("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+
+ if (editor.getData() !== lastFetchedData) {
+ lastFetchedData = editor.getData();
+ timeSinceLastModified = 0;
+ } else {
+ timeSinceLastModified++;
+ }
+
+ if (timeSinceLastModified > 20) {
+ await save();
+ }
+ } else {
+ timeSinceLastModified = 0;
+ document.getElementById("editor-save-status").innerHTML = "Saved";
+ document.getElementById("editor-save-status").classList.add("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ }
+ }, 100)
+ </script>
+</div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?> \ No newline at end of file
diff --git a/includes/footer.php b/includes/footer.php
new file mode 100644
index 0000000..c120941
--- /dev/null
+++ b/includes/footer.php
@@ -0,0 +1,57 @@
+<?php
+
+if (!function_exists("timeAgo")) {
+ function timeAgo($time): string {
+ if (!is_numeric($time)) {
+ $time = strtotime($time);
+ }
+
+ $periods = ["second", "minute", "hour", "day", "week", "month", "year", "age"];
+ $lengths = array("60", "60", "24", "7", "4.35", "12", "100");
+
+ $now = time();
+
+ $difference = $now - $time;
+ if ($difference <= 10 && $difference >= 0) {
+ return $tense = "now";
+ } elseif ($difference > 0) {
+ $tense = "ago";
+ } else {
+ $tense = "later";
+ }
+
+ for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) {
+ $difference /= $lengths[$j];
+ }
+
+ $difference = round($difference);
+
+ $period = $periods[$j] . ($difference >1 ? "s" :'');
+ return "{$difference} {$period} {$tense} ";
+ }
+}
+
+?>
+
+<hr>
+<div class="container text-muted">
+ © <?= date("Y") ?> <a href="https://equestria.horse" target="_blank" class="text-muted">Equestria.dev Developers</a><br>
+ PluralKit data updated <?= trim(timeAgo(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/refresh.json"), true)["timestamp"])) ?>, next update in <?php $t = 5 - round((time() - json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/refresh.json"), true)["timestamp"]) / 60); ?><?= $t > 1 ? $t . " minutes" : ($t > 0 ? "a minute" : "a few seconds") ?>
+ <br><br><br>
+</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>
+
+</body>
+</html> \ No newline at end of file
diff --git a/includes/header.php b/includes/header.php
new file mode 100644
index 0000000..af12bcb
--- /dev/null
+++ b/includes/header.php
@@ -0,0 +1,719 @@
+<?php global $title;
+
+require_once $_SERVER["DOCUMENT_ROOT"] . "/includes/score.php";
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
+
+function getMiniName(string $name) {
+ $parts = explode(" ", $name);
+
+ if (strlen($parts[0]) > 3 && !str_ends_with($parts[0], "e") && $parts[0] !== "Filly") {
+ if (str_contains($parts[0], "/")) {
+ return explode("/", $parts[0])[0];
+ } else {
+ return $parts[0];
+ }
+ } else {
+ return $name;
+ }
+}
+
+function getSystemMember(string $system, string $id) {
+ $systemID = $system;
+
+ $members = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-members.json"), true);
+ $member = null;
+
+ foreach ($members as $m) {
+ if ($m["id"] === $id) $member = $m;
+ }
+
+ return $member;
+}
+
+function getBrightness(string $hexCode) {
+ if (str_starts_with("#", $hexCode)) {
+ $hexCode = substr($hexCode, 1);
+ }
+
+ $red = hexdec(substr($hexCode, 0, 2));
+ $green = hexdec(substr($hexCode, 2, 2));
+ $blue = hexdec(substr($hexCode, 4, 2));
+ $brightness = $red + $green + $blue;
+
+ return $brightness > 382;
+}
+
+function showMembersFromList(array $list, string $id) {
+ foreach ($list as $member) { if ($member['name'] !== "unknown") {
+ echo('<!-- ' . ($member['display_name'] ?? $member['name']) . ' -->
+<a href="/' . ($id === "gdapd" ? "raindrops" : "cloudburst") . '/' . $member['name'] . '" style="text-decoration:none !important;filter:none !important;"><div class="hpd-item-card" style="background-color:rgba(255, 255, 255, .1);border-radius:10px;text-align:center;display:flex;align-items:center;justify-content:center;padding:5px;"><div>
+<img alt="" src="/assets/uploads/pt' . (file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "") . '.png" style="height:48px;display:block;margin-left:auto;margin-right:auto;">
+<div style="text-decoration:none;color:white;margin-top:5px;">' . ($member['display_name'] ?? $member['name']) . '</div>
+<div style="text-decoration:none !important;color:black !important;"><code style="text-decoration:none !important;color:white !important;">' . $member['proxy_tags'][0]['prefix'] . '</code></div>
+</div></div></a>');
+ }}
+}
+
+function showSubsystem(array $data, string $parentSystem) {
+ $subsystemData = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$parentSystem-subsystem-$data[source].json"), true);
+
+ echo('<!-- ' . $subsystemData["name"] . ' -->
+<div id="hpd-cloudburst" style="background:rgba(255, 255, 255, .1);border-radius:10px;padding:10px;display:grid;grid-template-columns: 1fr;margin-bottom:10px;">');
+ echo(' <div style="display:grid;grid-template-columns:repeat(6, 1fr);grid-gap:10px;">');
+
+ showMembersFromList(scoreOrder(array_map(function ($i) use ($parentSystem) {
+ return getSystemMember($parentSystem, $i);
+ }, $data["members"]), $parentSystem), $parentSystem);
+
+ echo('</div>
+
+</div>');
+}
+
+function showSystem(string $id, string $name, string $color, bool $hideTitle) {
+ if ($hideTitle) {
+ echo('<!-- ' . $name . ' -->
+<div id="hpd-' . ($id === "gdapd" ? "raindrops" : "cloudburst") . '" style="background:rgba(255, 255, 255, .1);border-radius:10px;padding:10px;display:grid;grid-template-columns: 1fr;margin-bottom:10px;">');
+ } else {
+ echo('<!-- ' . $name . ' -->
+<div id="hpd-' . ($id === "gdapd" ? "raindrops" : "cloudburst") . '" style="background:rgba(255, 255, 255, .1);border-radius:10px;padding:10px 10px 10px 20px;display:grid;grid-template-columns: 128px 1fr;margin-bottom:10px;">');
+ }
+
+ if (!$hideTitle) echo('<!-- System Name -->
+<a style="display:flex;margin: -10px -20px;align-items:center;justify-content:center;text-align:center;padding: 10px 20px;border-radius: 10px;background: ' . $color . ';width: 148px;text-decoration:none;color:white;filter:none !important;" href="/' . ($id === "gdapd" ? "raindrops" : "cloudburst") . '" class="hpd-system">
+' . $name . '
+</a>');
+
+ if ($hideTitle) {
+ echo(' <div style="display:grid;grid-template-columns:repeat(6, 1fr);grid-gap:10px;">');
+ } else {
+ echo(' <div style="display:grid;grid-template-columns:repeat(6, 1fr);padding-left:10px;grid-gap:10px;">');
+ }
+
+ showMembersFromList(scoreOrder(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$id-members.json"), true), $id), $id);
+
+ echo('</div>
+
+</div>');
+}
+
+function cloudburst(bool $hideTitle): void {
+ showSystem("ynmuc", "Cloudburst System", "#5f08a9a6", $hideTitle);
+}
+
+function raindrops(bool $hideTitle): void {
+ showSystem("gdapd", "Raindrops System", "#a95f08a6", $hideTitle);
+}
+
+?>
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <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 . " · " : "" ?>Cuties and Plurality</title>
+ <link rel="shortcut icon" href="/assets/uploads/logo.jpg" type="image/jpg">
+ <style>
+ nav.navbar {
+ background-color: black !important;
+ border-bottom: 1px solid rgba(255, 255, 255, .25);
+ }
+
+ body {
+ background-color: black !important;
+ color: white;
+ }
+
+ .hpd-item-card:hover {
+ background-color: rgba(255, 255, 255, .15) !important;
+ }
+
+ .hpd-item-card:active, .hpd-item-card:focus {
+ background-color: rgba(255, 255, 255, .2) !important;
+ }
+
+ .hpd-system:hover {
+ opacity: .9 !important;
+ }
+
+ .hpd-system:active, .hpd-system:focus {
+ opacity: .8 !important;
+ }
+
+ .hpd-link:hover {
+ background-color: rgba(255, 255, 255, .15) !important;
+ }
+
+ .hpd-link:active, .hpd-link:focus {
+ background-color: rgba(255, 255, 255, .2) !important;
+ }
+
+ .list-separator-mobile {
+ display: none;
+ }
+
+ @media (max-width: 991px) {
+ #hpd-cloudburst > div, #hpd-raindrops > div {
+ grid-template-columns: repeat(3, 1fr) !important;
+ }
+
+ .list-separator-desktop {
+ display: none;
+ }
+
+ span.list-separator-mobile {
+ display: inline;
+ }
+ }
+
+ @media (max-width: 768px) {
+ #hpd-cloudburst > div, #hpd-raindrops > div {
+ grid-template-columns: repeat(2, 1fr) !important;
+ }
+ }
+
+ @media (max-width: 575px) {
+ #hpd-cloudburst > div, #hpd-raindrops > div {
+ grid-template-columns: repeat(1, 1fr) !important;
+ }
+
+ .hpd-item-card img {
+ display: inline-block !important;
+ margin-right: 5px !important;
+ height: 32px !important;
+ }
+
+ #hpd-cloudburst > div, #hpd-raindrops > div {
+ grid-gap: 5px !important;
+ }
+
+ .hpd-item-card div {
+ display: inline-block !important;
+ }
+
+ .hpd-item-card div:nth-child(3)::before {
+ content: "(";
+ padding-left: 5px;
+ color: white !important;
+ }
+
+ .hpd-item-card div:nth-child(3)::after {
+ content: ")";
+ color: white !important;
+ }
+ }
+
+ .dropdown-menu {
+ background-color: #222;
+ }
+
+ .dropdown-item:hover {
+ background-color: rgba(255, 255, 255, .1);
+ }
+
+ .dropdown-item:active, .dropdown-item:focus {
+ background-color: rgba(255, 255, 255, .2);
+ }
+
+ .dropdown-item {
+ color: white !important;
+ }
+
+ .dropdown-icon {
+ filter: invert(1);
+ }
+
+ .dropdown-toggle .dropdown-icon {
+ opacity: .5;
+ transition: 200ms opacity;
+ }
+
+ .dropdown-toggle:hover .dropdown-icon, .dropdown-toggle:active .dropdown-icon, .dropdown-toggle:focus .dropdown-icon {
+ opacity: .75;
+ }
+
+ dd {
+ margin-left: 20px;
+ }
+
+ #system-info a {
+ color: white;
+ }
+
+ #system-info a:hover {
+ opacity: .75;
+ }
+
+ #system-info a:active, #system-info a:focus {
+ opacity: .5;
+ }
+
+ @media (max-width: 991px) {
+ #member-card {
+ grid-template-columns: repeat(3, 1fr) !important;
+ }
+
+ .species-name {
+ display: none;
+ }
+ }
+
+ @media (max-width: 767px) {
+ #member-card {
+ grid-template-columns: 1fr !important;
+ text-align: left;
+ }
+ }
+
+ #page-content a {
+ color: #afd0ff;
+ }
+
+ #page-content a:hover {
+ opacity: .75;
+ }
+
+ #page-content a:active, #page-content a:focus {
+ opacity: .5;
+ }
+
+ .tooltip.show {
+ opacity: 1;
+ }
+
+ .tooltip-inner {
+ background: #151515;
+ box-shadow: 3px 4px 10px #ffffff26;
+ }
+
+ .alert {
+ filter: invert(1) hue-rotate(180deg);
+ }
+
+ .member-link {
+ color: white !important;
+ text-decoration: none !important;
+ }
+
+ .system-action {
+ border-radius: 10px;
+ color: white !important;
+ text-decoration: none !important;
+ cursor: pointer;
+ }
+
+ .system-action:hover {
+ background:rgba(255, 255, 255, .1);
+ }
+
+ .table-dark {
+ --bs-table-bg: #000000;
+ }
+
+ .comparison {
+ display: grid;
+ grid-template-columns: 3fr repeat(2, 2fr) repeat(5, 1fr);
+ }
+
+ .comparison-header {
+ border-bottom: 2px solid rgba(255, 255, 255, .25);
+ font-weight: bold;
+ }
+
+ .comparison-item {
+ padding: 5px 10px;
+ text-align: center;
+ }
+
+ .comparison-item-clickable:hover {
+ background-color: rgba(255, 255, 255, .1);
+ }
+
+ .comparison-item-clickable:active, .comparison-item-clickable:focus {
+ background-color: rgba(255, 255, 255, .25);
+ }
+
+ @media (min-width: 1400px) {
+ .comparison-header-l0 {
+ display: inline;
+ }
+ .comparison-header-l1 {
+ display: none;
+ }
+ .comparison-header-l2 {
+ display: none;
+ }
+ .comparison-header-l3 {
+ display: none;
+ }
+ .comparison-header-l4 {
+ display: none;
+ }
+ .comparison-header-l5 {
+ display: none;
+ }
+ .comparison-name-full {
+ display: inline;
+ }
+ .comparison-name-small {
+ display: none;
+ }
+ .comparison-colors {
+ display: inline;
+ }
+ .comparison-relations-count {
+ display: none;
+ }
+ .comparison-relations-full {
+ display: inline;
+ }
+ }
+
+ @media (max-width: 1399px) {
+ .comparison-header-l0 {
+ display: none;
+ }
+ .comparison-header-l1 {
+ display: inline;
+ }
+ .comparison-header-l2 {
+ display: none;
+ }
+ .comparison-header-l3 {
+ display: none;
+ }
+ .comparison-header-l4 {
+ display: none;
+ }
+ .comparison-header-l5 {
+ display: none;
+ }
+ .comparison-name-full {
+ display: inline;
+ }
+ .comparison-name-small {
+ display: none;
+ }
+ .comparison-colors {
+ display: inline;
+ }
+ .comparison-relations-count {
+ display: none;
+ }
+ .comparison-relations-full {
+ display: inline;
+ }
+ }
+
+ @media (max-width: 1199px) {
+ .comparison-header-l0 {
+ display: none;
+ }
+ .comparison-header-l1 {
+ display: none;
+ }
+ .comparison-header-l2 {
+ display: initial;
+ }
+ .comparison-header-l3 {
+ display: none;
+ }
+ .comparison-header-l4 {
+ display: none;
+ }
+ .comparison-header-l5 {
+ display: none;
+ }
+ .comparison-name-full {
+ display: none;
+ }
+ .comparison-name-small {
+ display: inline;
+ }
+ .comparison-colors {
+ display: inline;
+ }
+ .comparison-relations-count {
+ display: inline;
+ }
+ .comparison-relations-full {
+ display: none;
+ }
+ }
+
+ @media (max-width: 991px) {
+ .comparison-header-l0 {
+ display: none;
+ }
+ .comparison-header-l1 {
+ display: none;
+ }
+ .comparison-header-l2 {
+ display: none;
+ }
+ .comparison-header-l3 {
+ display: initial;
+ }
+ .comparison-header-l4 {
+ display: none;
+ }
+ .comparison-header-l5 {
+ display: none;
+ }
+ .comparison-name-full {
+ display: none;
+ }
+ .comparison-name-small {
+ display: inline;
+ }
+ .comparison-colors {
+ display: none !important;
+ }
+ .comparison-relations-count {
+ display: inline;
+ }
+ .comparison-relations-full {
+ display: none;
+ }
+ }
+
+ @media (max-width: 767px) {
+ .comparison-header-l0 {
+ display: none;
+ }
+ .comparison-header-l1 {
+ display: none;
+ }
+ .comparison-header-l2 {
+ display: none;
+ }
+ .comparison-header-l3 {
+ display: none;
+ }
+ .comparison-header-l4 {
+ display: initial;
+ }
+ .comparison-header-l5 {
+ display: none;
+ }
+ .comparison-name-full {
+ display: none;
+ }
+ .comparison-name-small {
+ display: none;
+ }
+ .comparison-colors {
+ display: none !important;
+ }
+ .comparison-relations-count {
+ display: inline;
+ }
+ .comparison-relations-full {
+ display: none;
+ }
+ }
+
+ @media (max-width: 575px) {
+ .comparison-header-l0 {
+ display: none;
+ }
+ .comparison-header-l1 {
+ display: none;
+ }
+ .comparison-header-l2 {
+ display: none;
+ }
+ .comparison-header-l3 {
+ display: none;
+ }
+ .comparison-header-l4 {
+ display: none;
+ }
+ .comparison-header-l5 {
+ display: initial;
+ }
+ .comparison-name-full {
+ display: none;
+ }
+ .comparison-name-small {
+ display: none;
+ }
+ .comparison-colors {
+ display: none !important;
+ }
+ .comparison {
+ grid-template-columns: repeat(3, 2fr) repeat(5, 1fr) !important;
+ }
+ .comparison-relations-count {
+ display: inline;
+ }
+ .comparison-relations-full {
+ display: none;
+ }
+ }
+
+ .tree-first-separator {
+ height: 14px !important;
+ top: 0 !important;
+ }
+
+ .tree-l0-separator {
+ display: inline-block;
+ width: 20px;
+ margin-left: 35px;
+ border-bottom: 1px solid white;
+ border-left: 1px solid white;
+ height: 26px;
+ position: relative;
+ top: -12px;
+ }
+
+ .tree-l1 .tree-l0-separator {
+ border-bottom: none !important;
+ }
+
+ .tree-l1-separator {
+ display: inline-block;
+ width: 20px;
+ margin-left: 35px;
+ border-bottom: 1px solid white;
+ border-left: 1px solid white;
+ height: 26px;
+ position: relative;
+ top: -12px;
+ left: -10px;
+ }
+
+ .tree-l1 .tree-l0-separator {
+ width: 30px;
+ }
+
+ .tree-l1 .tree-inner {
+ position: relative;
+ left: -10px;
+ }
+
+ .tree-inner {
+ display: inline-block;
+ }
+ </style>
+</head>
+<body>
+ <nav class="navbar navbar-expand-md bg-dark navbar-dark">
+ <div class="container-fluid">
+ <a class="navbar-brand" href="/"><img src="/assets/uploads/logo.jpg" alt="" style="width:32px;vertical-align: middle;margin-right:5px;"> <span style="vertical-align: middle;">Cuties and Plurality</span><a>
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#collapsibleNavbar">
+ <span class="navbar-toggler-icon"></span>
+ </button>
+ <div class="collapse navbar-collapse" id="collapsibleNavbar">
+ <ul class="navbar-nav">
+ <li class="nav-item dropdown">
+ <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
+ <img src="<?= $isLoggedIn ? "/assets/icons/loggedin.svg" : "/assets/icons/global.svg" ?>" class="dropdown-icon" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">Global</span>
+ </a>
+ <ul class="dropdown-menu">
+ <?php if ($isLoggedIn): ?>
+ <li><a class="dropdown-item" href="/emergency">
+ <img src="/assets/icons/emergency.svg" alt="" style="width:24px;vertical-align: middle;">
+ <span class="text-danger" style="vertical-align: middle;">Emergency Alert</span>
+ </a></li>
+ <li><hr class="dropdown-divider"></li>
+ <?php endif; ?>
+ <li><a class="dropdown-item" href="/">
+ <img src="/assets/icons/home.svg" class="dropdown-icon" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">Home</span>
+ </a></li>
+ <li><a class="dropdown-item" href="/disclaimers">
+ <img src="/assets/icons/disclaimers.svg" class="dropdown-icon" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">Disclaimers</span>
+ </a></li>
+ <li><a class="dropdown-item" href="/relations">
+ <img src="/assets/icons/relations.svg" class="dropdown-icon" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">Relations</span>
+ </a></li>
+ <li><a class="dropdown-item" href="/terminology">
+ <img src="/assets/icons/terminology.svg" class="dropdown-icon" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">Terminology</span>
+ </a></li>
+ <li><hr class="dropdown-divider"></li>
+ <li><h5 class="dropdown-header">Tools</h5></li>
+ <li><a class="dropdown-item" href="/parser">
+ <img src="/assets/icons/parser.svg" class="dropdown-icon" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">Message Parser</span>
+ </a></li>
+ <li><a class="dropdown-item" href="/prefix">
+ <img src="/assets/icons/prefix.svg" class="dropdown-icon" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">Prefix Generator</span>
+ </a></li>
+ <li><hr class="dropdown-divider"></li>
+ <li><h5 class="dropdown-header">Administrator</h5></li>
+ <?php if ($isLoggedIn): ?>
+ <li><a class="dropdown-item" href="/fronting">
+ <img src="/assets/icons/fronting.svg" class="dropdown-icon" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">Front Planner</span>
+ </a></li>
+ <li><a class="dropdown-item" href="/score">
+ <img src="/assets/icons/score.svg" class="dropdown-icon" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">Score System Testing</span>
+ </a></li>
+ <li><a class="dropdown-item" href="/logout">
+ <img src="/assets/icons/logout.svg" class="dropdown-icon" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">Logout</span>
+ </a></li>
+ <?php else: ?>
+ <li><a class="dropdown-item" href="/login">
+ <img src="/assets/icons/login.svg" class="dropdown-icon" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">Login</span>
+ </a></li>
+ <?php endif; ?>
+ </ul>
+ </li>
+ <?php if (!isset($emergencyHeader) || !$emergencyHeader): ?>
+ <li class="nav-item dropdown">
+ <a class="nav-link dropdown-toggle" href="/cloudburst" role="button" data-bs-toggle="dropdown">
+ <img src="/assets/uploads/cloudburst.png" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">Cloudburst System</span>
+ </a>
+ <ul class="dropdown-menu">
+ <li><a class="dropdown-item" href="/cloudburst">
+ <img src="/assets/icons/about.svg" class="dropdown-icon" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">About us</span>
+ </a></li>
+ <li><hr class="dropdown-divider"></li>
+ <li><h5 class="dropdown-header">Members (<?= count(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc-members.json"), true)) - 1 ?>)</h5></li>
+ <?php foreach (scoreOrder(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc-members.json"), true), "ynmuc") as $member): if ($member['name'] !== "unknown"): ?>
+ <li><a class="dropdown-item" href="/cloudburst/<?= $member['name'] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;"><?= $member['display_name'] ?? $member['name'] ?></span>
+ </a></li>
+ <?php endif; endforeach; ?>
+ </ul>
+ </li>
+ <li class="nav-item dropdown">
+ <a class="nav-link dropdown-toggle" href="/raindrops" role="button" data-bs-toggle="dropdown">
+ <img src="/assets/uploads/raindrops.png" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">Raindrops System</span>
+ </a>
+ <ul class="dropdown-menu">
+ <li><a class="dropdown-item" href="/raindrops">
+ <img src="/assets/icons/about.svg" class="dropdown-icon" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;">About us</span>
+ </a></li>
+ <li><hr class="dropdown-divider"></li>
+ <li><h5 class="dropdown-header">Members (<?= count(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd-members.json"), true)) - 1 ?>)</h5></li>
+ <?php foreach (scoreOrder(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd-members.json"), true), "gdapd") as $member): if ($member['name'] !== "unknown"): ?>
+ <li><a class="dropdown-item" href="/raindrops/<?= $member['name'] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" alt="" style="width:24px;vertical-align: middle;">
+ <span style="vertical-align: middle;"><?= $member['display_name'] ?? $member['name'] ?></span>
+ </a></li>
+ <?php endif; endforeach; ?>
+ </ul>
+ </li>
+ <?php endif; ?>
+ </ul>
+ </div>
+ </div>
+ </nav> \ No newline at end of file
diff --git a/includes/member.php b/includes/member.php
new file mode 100644
index 0000000..ad7995f
--- /dev/null
+++ b/includes/member.php
@@ -0,0 +1,84 @@
+<?php global $system; global $systemCommonName; global $systemID; global $member; global $memberData; global $memberCommonName; global $memberID; $title = $memberCommonName . " · " . $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+
+function timeAgo($time): string {
+ if (!is_numeric($time)) {
+ $time = strtotime($time);
+ }
+
+ $periods = ["second", "minute", "hour", "day", "week", "month", "year", "age"];
+ $lengths = array("60", "60", "24", "7", "4.35", "12", "100");
+
+ $now = time();
+
+ $difference = $now - $time;
+ if ($difference <= 10 && $difference >= 0) {
+ return $tense = "now";
+ } elseif ($difference > 0) {
+ $tense = "ago";
+ } else {
+ $tense = "later";
+ }
+
+ for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) {
+ $difference /= $lengths[$j];
+ }
+
+ $difference = round($difference);
+
+ $period = $periods[$j] . ($difference >1 ? "s" :'');
+ return "{$difference} {$period} {$tense} ";
+}
+
+$metadata = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/" . $systemID . "-" . $memberID . "-metadata.json"), true);
+
+?>
+
+<br>
+<div class="container">
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/banner.php"; ?>
+ <br>
+
+ <?php global $isLoggedIn; if ($isLoggedIn): ?>
+ <div class="alert alert-dark">
+ <details>
+ <summary>Private administrator information</summary>
+ <ul style="margin-bottom:0;">
+ <li><b>ID:</b> <code><?= $memberID ?></code> (<code><?= $systemID . "/" . $memberID ?></code>, <?= $memberData["name"] ?>)</li>
+ <li><b>Reduced name:</b> <?= getMiniName($memberData["display_name"] ?? $member["name"]) ?></li>
+ <li><b>Shared memory access:</b> <code><?= $metadata["shared_memory"] ?></code> (<?= $metadata["shared_memory"] === 2 ? "Full direct access" : ($metadata["shared_memory"] === 0 ? "No direct access" : "Partial direct access") ?>)</li>
+ <li><b>Protector:</b> <code><?= $metadata["protector"] ? "1" : "0" ?></code> (<?= $metadata["protector"] ? "Yes" : "No" ?>)</li>
+ <li><b>Little:</b> <code><?= $metadata["little"] ?></code> (<?= $metadata["little"] === 2 ? "Is a little" : ($metadata["little"] === 1 ? "Is an age regressor" : "No") ?>)</li>
+ <li><b>Relations count:</b> <code><?= count($metadata["marefriends"]) + count($metadata["sisters"]) ?></code></li>
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/score.php"; $score = calculateScore($metadata, $memberData["display_name"] ?? $memberData["name"]); ?>
+ <li>
+ <b>Score breakdown:</b> <code><?= $score["total"] ?></code>
+ <details>
+ <summary>Show details</summary>
+ <ul>
+ <li><b>Host score:</b> <code><?= $score["host"] ?></code></li>
+ <li><b>Relationships score:</b> <code><?= $score["relations"] ?></code></li>
+ <li><b>Fictive score:</b> <code><?= $score["fictive"] ?></code></li>
+ <li><b>Median score:</b> <code><?= $score["median"] ?></code></li>
+ <li><b>Species score:</b> <code><?= $score["species"] ?></code></li>
+ <li><b>Little score:</b> <code><?= $score["little"] ?></code></li>
+ <li><b>Not talking score:</b> <code><?= $score["not_talking"] ?></code></li>
+ <li><b>Protector score:</b> <code><?= $score["protector"] ?></code></li>
+ <li><b>Name score:</b> <code><?= $score["name"] ?></code></li>
+ <li><b>Shared memory score:</b> <code><?= $score["shared_memory"] ?></code></li>
+ </ul>
+ </details>
+ </li>
+ </ul>
+ </details>
+ </div>
+ <?php endif; ?>
+
+ <div id="page-content">
+ <?php global $isLoggedIn; if ($isLoggedIn): ?>
+ <small style="opacity:.5;display:block;">(<a href="/edit/<?= $system ?>/<?= $memberData['name'] ?>">edit</a>)</small>
+ <?php endif; ?>
+ <?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-$memberID-content.html") ? file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-$memberID-content.html") : "<i>This page is empty.</i>" ?>
+ </div>
+</div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?> \ No newline at end of file
diff --git a/includes/refresh.php b/includes/refresh.php
new file mode 100644
index 0000000..c29136f
--- /dev/null
+++ b/includes/refresh.php
@@ -0,0 +1,35 @@
+<?php
+
+$start = time();
+@mkdir("./data");
+
+function getSystem(string $id) {
+ echo("System: $id\n");
+
+ echo(" Base system info\n");
+ file_put_contents("./data/$id-general.json", file_get_contents("https://api.pluralkit.me/v2/systems/$id"));
+ sleep(1);
+
+ echo(" System members\n");
+ file_put_contents("./data/$id-members.json", file_get_contents("https://api.pluralkit.me/v2/systems/$id/members"));
+ sleep(1);
+
+ echo(" Fronters\n");
+ file_put_contents("./data/$id-fronters.json", file_get_contents("https://api.pluralkit.me/v2/systems/$id/fronters"));
+ sleep(1);
+
+ echo(" Switches\n");
+ file_put_contents("./data/$id-switches.json", file_get_contents("https://api.pluralkit.me/v2/systems/$id/switches"));
+ sleep(1);
+}
+
+getSystem("gdapd"); // Raindrops
+getSystem("ynmuc"); // Cloudburst
+$time = (time() - $start);
+
+echo("Completed in " . $time . " seconds.\n");
+
+file_put_contents("./data/refresh.json", json_encode([
+ "timestamp" => time(),
+ "duration" => $time
+])); \ No newline at end of file
diff --git a/includes/score.php b/includes/score.php
new file mode 100644
index 0000000..c3108f2
--- /dev/null
+++ b/includes/score.php
@@ -0,0 +1,74 @@
+<?php
+
+function calculateScore($metadata, $name) {
+ $scoreHost = $metadata["host"] ? 10000 : 0;
+ $scoreFictive = $metadata["fictive"] ? 200 : 0;
+ $scoreLittle = $metadata["little"] === 2 ? 100 : ($metadata["little"] === 1 ? 50 : 0);
+ $scoreNotTalking = $metadata["not_talking"] ? -200 : 0;
+ $scoreMedian = $metadata["median"] !== false ? -50 : 0;
+ $scoreProtector = $metadata["protector"] ? 1000 : 0;
+ $scoreSharedMemory = $metadata["shared_memory"] === 0 ? 200 : ($metadata["shared_memory"] === 1 ? 50 : 0);
+ $scoreSpecies = (in_array("pegasus", $metadata["species"]) ? 100 : 0) + (in_array("unicorn", $metadata["species"]) ? 75 : 0) + (in_array("earth", $metadata["species"]) ? 50 : 0) + (in_array("alicorn", $metadata["species"]) ? 150 : 0) + (in_array("batpony", $metadata["species"]) ? 125 : 0);
+ $scoreName = strlen($name) * 5;
+ $relations = (count($metadata["marefriends"]) * ($metadata["little"] ? 1 : 2)) + count($metadata["sisters"]);
+ $scoreRelations = $relations * 50;
+
+ $score = $scoreHost + $scoreFictive + $scoreLittle + $scoreNotTalking + $scoreProtector + $scoreSharedMemory + $scoreRelations + $scoreSpecies + $scoreName + $scoreMedian;
+
+ return [
+ "host" => $scoreHost,
+ "fictive" => $scoreFictive,
+ "little" => $scoreLittle,
+ "median" => $scoreMedian,
+ "not_talking" => $scoreNotTalking,
+ "name" => $scoreName,
+ "protector" => $scoreProtector,
+ "shared_memory" => $scoreSharedMemory,
+ "relations" => $scoreRelations,
+ "species" => $scoreSpecies,
+ "total" => $score
+ ];
+}
+
+function scoreOrder($members, $system) {
+ $ordered = [];
+ foreach ($members as $member) {
+ if ($member["name"] !== "unknown") {
+ $member["_metadata"] = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$system-$member[id]-metadata.json"), true);
+ $member["_score"] = calculateScore(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$system-$member[id]-metadata.json"), true), $member["display_name"] ?? $member["name"]);
+ $ordered[] = $member;
+ }
+ }
+
+ uasort($ordered, function($a, $b) {
+ return $b["_score"]["total"] - $a["_score"]["total"];
+ });
+
+ return $ordered;
+}
+
+function scoreOrderGlobal() {
+ $ordered = [];
+ foreach (json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd-members.json"), true) as $member) {
+ if ($member["name"] !== "unknown") {
+ $member["_system"] = "gdapd";
+ $member["_metadata"] = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd-$member[id]-metadata.json"), true);
+ $member["_score"] = calculateScore(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd-$member[id]-metadata.json"), true), $member["display_name"] ?? $member["name"]);
+ $ordered[] = $member;
+ }
+ }
+ foreach (json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc-members.json"), true) as $member) {
+ if ($member["name"] !== "unknown") {
+ $member["_system"] = "ynmuc";
+ $member["_metadata"] = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc-$member[id]-metadata.json"), true);
+ $member["_score"] = calculateScore(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc-$member[id]-metadata.json"), true), $member["display_name"] ?? $member["name"]);
+ $ordered[] = $member;
+ }
+ }
+
+ uasort($ordered, function($a, $b) {
+ return $b["_score"]["total"] - $a["_score"]["total"];
+ });
+
+ return $ordered;
+} \ No newline at end of file
diff --git a/includes/session.php b/includes/session.php
new file mode 100644
index 0000000..27acf6b
--- /dev/null
+++ b/includes/session.php
@@ -0,0 +1,19 @@
+<?php
+
+global $isLoggedIn;
+global $_PROFILE;
+
+if (isset($_COOKIE['PEH2_SESSION_TOKEN'])) {
+ if (str_contains($_COOKIE['PEH2_SESSION_TOKEN'], ".") || str_contains($_COOKIE['PEH2_SESSION_TOKEN'], "/")) {
+ $isLoggedIn = false;
+ }
+
+ if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace(".", "", str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN'])))) {
+ $_PROFILE = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/tokens/" . str_replace(".", "", str_replace("/", "", $_COOKIE['PEH2_SESSION_TOKEN']))), true);
+ $isLoggedIn = true;
+ } else {
+ $isLoggedIn = false;
+ }
+} else {
+ $isLoggedIn = false;
+} \ No newline at end of file
diff --git a/includes/subsysbanner.php b/includes/subsysbanner.php
new file mode 100644
index 0000000..be06b06
--- /dev/null
+++ b/includes/subsysbanner.php
@@ -0,0 +1,68 @@
+<?php
+
+global $memberData;
+global $memberCommonName;
+global $memberID;
+global $systemCommonName;
+global $systemID;
+global $system;
+global $subsystemData;
+global $subsystemID;
+global $subsystem;
+
+$fronters = array_map(function ($item) {
+ return $item["id"];
+}, json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-fronters.json"), true)["members"]);
+
+?>
+
+<div id="system-info" style="background:rgba(255, 255, 255, .1);border-radius:10px;display:grid;grid-template-columns: 128px 1fr;">
+ <img src="/assets/uploads/ss-<?= $subsystemID ?>.png" alt="" style="height:128px;border-top-left-radius:10px;border-bottom-left-radius:10px;">
+ <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;"><?= $subsystemData["name"] ?></h3>
+ <div style="height:max-content;display:grid;grid-template-columns: repeat(4, 1fr);" id="member-card">
+ <span>
+ <b>Current Fronter:</b>
+ <?php if (in_array($fronters[0], getSubsystemByID($subsystemID)["members"])): $member = getMember($fronters[0]); ?>
+ <a class="member-link" href="/<?= $system ?>/<?= $member["name"] ?>"><img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= getMiniName($member["display_name"] ?? $member["name"]) ?></a>
+ <?php else: ?>
+ <span class="text-muted">N/A</span>
+ <?php endif; ?>
+ </span>
+ <span>
+ <?php
+
+ $arr = array_filter(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-switches.json"), true), function ($i) use ($subsystemID) {
+ return in_array($i["members"][0], getSubsystemByID($subsystemID)["members"]);
+ });
+ sort($arr);
+ $previousID = $arr[in_array($fronters[0], getSubsystemByID($subsystemID)["members"]) ? 1 : 0]["members"][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;
+ }
+ }
+
+ ?>
+ <b>Previous Fronter: </b><a class="member-link" href="/<?= $system ?>/<?= $member["name"] ?>"><img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= getMiniName($member["display_name"] ?? $member["name"]) ?></a>
+ </span>
+ <span>
+ <b>Members:</b> <?= count(getSubsystemByID($subsystemID)["members"]) ?>
+ </span>
+ <span>
+ <b>Parent System:</b> <a class="member-link" href="/<?= $system ?>"><img style="width:24px;border-radius:5px;" src="/assets/uploads/<?= $system ?>.png"> <?= getMiniName($systemCommonName) ?></a>
+ </span>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div id="system-actions" style="margin-top:10px;padding:5px 10px;background:rgba(255, 255, 255, .1);border-radius:10px;">
+ <div style="padding: 5px 10px;text-align: center;">
+ You are viewing the page of a subsystem of the <b><a style="color:white;text-decoration: none;" href="/<?= $system ?>"><?= $systemCommonName ?></a></b>.
+ </div>
+</div> \ No newline at end of file
diff --git a/includes/subsysedit.php b/includes/subsysedit.php
new file mode 100644
index 0000000..a19683d
--- /dev/null
+++ b/includes/subsysedit.php
@@ -0,0 +1,172 @@
+<?php global $system; global $systemCommonName; global $systemID; global $subsystem; global $subsystemData; global $subsystemCommonName; global $subsystemID; $title = "Editing " . $subsystemCommonName . " · " . $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+
+function timeAgo($time): string {
+ if (!is_numeric($time)) {
+ $time = strtotime($time);
+ }
+
+ $periods = ["second", "minute", "hour", "day", "week", "month", "year", "age"];
+ $lengths = array("60", "60", "24", "7", "4.35", "12", "100");
+
+ $now = time();
+
+ $difference = $now - $time;
+ if ($difference <= 10 && $difference >= 0) {
+ return $tense = "now";
+ } elseif ($difference > 0) {
+ $tense = "ago";
+ } else {
+ $tense = "later";
+ }
+
+ for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) {
+ $difference /= $lengths[$j];
+ }
+
+ $difference = round($difference);
+
+ $period = $periods[$j] . ($difference >1 ? "s" :'');
+ return "{$difference} {$period} {$tense} ";
+}
+
+?>
+
+<br>
+<div class="container">
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/subsysbanner.php"; ?>
+ <br>
+
+ <p class="text-muted">
+ <span id="editor-save-status">Saved</span> · <span id="editor-size"><?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-subsystem-$subsystemID.html") ? strlen(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-subsystem-$subsystemID.html")) : "0" ?></span> bytes
+ </p>
+
+ <!--suppress HtmlFormInputWithoutLabel -->
+ <textarea id="page-editor">
+ <?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-subsystem-$subsystemID.html") ? file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-subsystem-$subsystemID.html") : "" ?>
+ </textarea>
+
+ <script src="/assets/editor/editor.js"></script>
+ <script>
+ let editor;
+ ClassicEditor
+ .create( document.querySelector( '#page-editor' ), {
+ toolbar: [
+ 'undo', 'redo', '|', 'removeFormat', '|', 'heading', '|', 'fontSize', 'fontColor', 'fontBackgroundColor', 'alignment', '|', 'bold', 'italic', 'underline', 'strikethrough', '|', 'subscript', 'superscript', '|', 'code', '|', 'outdent', 'indent', '|', 'bulletedList', 'numberedList', '|', 'link', 'imageUpload', 'mediaEmbed', 'blockQuote', 'insertTable', 'codeBlock', '|', 'horizontalLine'
+ ]
+ } )
+
+ .then( newEditor => {
+ editor = newEditor;
+ } )
+ .catch( error => {
+ console.error( error );
+ } );
+ </script>
+ <style>
+ :root {
+ --ck-color-base-background: transparent;
+ }
+
+ .ck-toolbar {
+ filter: invert(1);
+ }
+
+ .ck-tooltip__text {
+ color: white !important;
+ }
+
+ .ck-dropdown__panel {
+ background: #ddd !important;
+ }
+
+ .ck-color-grid__tile {
+ filter: invert(1);
+ }
+
+ .ck-balloon-rotator {
+ background-color: #ccc !important;
+ }
+
+ .ck-balloon-panel {
+ filter: invert(1);
+ }
+ </style>
+ <script>
+ let lastSavedData = editor.getData();
+ let lastFetchedData = editor.getData();
+ let timeSinceLastModified = 0;
+ let saving = false;
+
+ async function save() {
+ let data = editor.getData();
+ document.getElementById("editor-save-status").innerHTML = "Saving...";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.add("text-primary");
+ saving = true;
+
+ try {
+ await window.fetch("/api/save?system=<?= $systemID ?>&subsystem=<?= $subsystemID ?>", {
+ method: "POST",
+ body: JSON.stringify({ content: data })
+ });
+ document.getElementById("editor-save-status").innerHTML = "Saved";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.add("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ lastSavedData = data;
+ saving = false;
+ } catch (e) {
+ console.error(e);
+ document.getElementById("editor-save-status").innerHTML = "Failed to save";
+ document.getElementById("editor-save-status").classList.add("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ }
+ }
+
+ document.onclick = async () => {
+ if (saving) return;
+
+ if (editor.getData() !== lastSavedData) {
+ await save();
+ }
+ }
+
+ setInterval(async () => {
+ if (saving) return;
+
+ document.getElementById("editor-size").innerHTML = editor.getData().length;
+
+ if (editor.getData() !== lastSavedData) {
+ document.getElementById("editor-save-status").innerHTML = "Modified";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.add("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+
+ if (editor.getData() !== lastFetchedData) {
+ lastFetchedData = editor.getData();
+ timeSinceLastModified = 0;
+ } else {
+ timeSinceLastModified++;
+ }
+
+ if (timeSinceLastModified > 20) {
+ await save();
+ }
+ } else {
+ timeSinceLastModified = 0;
+ document.getElementById("editor-save-status").innerHTML = "Saved";
+ document.getElementById("editor-save-status").classList.add("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ }
+ }, 100)
+ </script>
+</div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?> \ No newline at end of file
diff --git a/includes/sysbanner.php b/includes/sysbanner.php
new file mode 100644
index 0000000..d76e601
--- /dev/null
+++ b/includes/sysbanner.php
@@ -0,0 +1,80 @@
+<?php
+
+global $memberData;
+global $memberCommonName;
+global $memberID;
+global $systemCommonName;
+global $systemID;
+global $system;
+
+?>
+
+<div id="system-info" style="background:rgba(255, 255, 255, .1);border-radius:10px;display:grid;grid-template-columns: 128px 1fr;">
+ <img src="/assets/uploads/<?= $system ?>.png" alt="" style="height:128px;border-top-left-radius:10px;border-bottom-left-radius:10px;">
+ <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(5, 1fr);" id="member-card">
+ <span>
+ <b>Host: </b><?php
+
+ $members = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-members.json"), true);
+ foreach ($members as $member) {
+ $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-$member[id]-metadata.json"), true);
+ if ($data["host"]): ?>
+ <a class="member-link" href="/<?= $system ?>/<?= $member["name"] ?>"><img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= getMiniName($member["display_name"] ?? $member["name"]) ?></a>
+ <?php endif;
+ }
+
+ ?>
+
+ </span>
+ <span>
+ <?php $member = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-fronters.json"), true)["members"][0]; ?>
+ <b>Current Fronter: </b><a class="member-link" href="/<?= $system ?>/<?= $member["name"] ?>"><img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= getMiniName($member["display_name"] ?? $member["name"]) ?></a>
+ </span>
+ <span>
+ <?php
+
+ $previousID = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-switches.json"), true)[1]["members"][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;
+ }
+ }
+
+ ?>
+ <b>Previous Fronter: </b><a class="member-link" href="/<?= $system ?>/<?= $member["name"] ?>"><img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= getMiniName($member["display_name"] ?? $member["name"]) ?></a>
+ </span>
+ <span>
+ <b>Members: </b><?= count(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-members.json"), true)) - 1 ?>
+ </span>
+ <span>
+ <b>Last 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="margin-top:10px;padding:5px 10px;background:rgba(255, 255, 255, .1);border-radius:10px;display:grid;grid-template-columns: 1fr 1fr 1fr 1fr;">
+ <a title="Front history" style="display:inline-block;padding:5px 10px;text-align: center" class="system-action" 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">Front history</span>
+ </a>
+ <a title="Compare members" style="display:inline-block;padding:5px 10px;text-align: center" class="system-action" 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">Compare members</span>
+ </a>
+ <a title="System tree" style="display:inline-block;padding:5px 10px;text-align: center" class="system-action" href="/<?= $system ?>/-/tree">
+ <img src="/assets/icons/tree.svg" style="vertical-align: middle;height: 24px;width: 24px;filter: invert(1)" alt="">
+ <span style="vertical-align: middle;" class="list-separator-desktop">System tree</span>
+ </a>
+ <a title="Members by species" style="display:inline-block;padding:5px 10px;text-align: center" class="system-action" href="/<?= $system ?>/-/species">
+ <img src="/assets/icons/species.svg" style="vertical-align: middle;height: 24px;width: 24px;filter: invert(1)" alt="">
+ <span style="vertical-align: middle;" class="list-separator-desktop">Members by species</span>
+ </a>
+</div> \ No newline at end of file
diff --git a/includes/sysedit.php b/includes/sysedit.php
new file mode 100644
index 0000000..0e3c9a7
--- /dev/null
+++ b/includes/sysedit.php
@@ -0,0 +1,172 @@
+<?php global $system; global $systemCommonName; global $systemID; $title = "Editing " . $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+
+function timeAgo($time): string {
+ if (!is_numeric($time)) {
+ $time = strtotime($time);
+ }
+
+ $periods = ["second", "minute", "hour", "day", "week", "month", "year", "age"];
+ $lengths = array("60", "60", "24", "7", "4.35", "12", "100");
+
+ $now = time();
+
+ $difference = $now - $time;
+ if ($difference <= 10 && $difference >= 0) {
+ return $tense = "now";
+ } elseif ($difference > 0) {
+ $tense = "ago";
+ } else {
+ $tense = "later";
+ }
+
+ for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) {
+ $difference /= $lengths[$j];
+ }
+
+ $difference = round($difference);
+
+ $period = $periods[$j] . ($difference >1 ? "s" :'');
+ return "{$difference} {$period} {$tense} ";
+}
+
+?>
+
+<br>
+<div class="container">
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/sysbanner.php"; ?>
+ <br>
+
+ <p class="text-muted">
+ <span id="editor-save-status">Saved</span> · <span id="editor-size"><?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-disclaimers.html") ? strlen(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-disclaimers.html")) : "0" ?></span> bytes
+ </p>
+
+ <!--suppress HtmlFormInputWithoutLabel -->
+ <textarea id="page-editor">
+ <?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-disclaimers.html") ? file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-disclaimers.html") : "" ?>
+ </textarea>
+
+ <script src="/assets/editor/editor.js"></script>
+ <script>
+ let editor;
+ ClassicEditor
+ .create( document.querySelector( '#page-editor' ), {
+ toolbar: [
+ 'undo', 'redo', '|', 'removeFormat', '|', 'heading', '|', 'fontSize', 'fontColor', 'fontBackgroundColor', 'alignment', '|', 'bold', 'italic', 'underline', 'strikethrough', '|', 'subscript', 'superscript', '|', 'code', '|', 'outdent', 'indent', '|', 'bulletedList', 'numberedList', '|', 'link', 'imageUpload', 'mediaEmbed', 'blockQuote', 'insertTable', 'codeBlock', '|', 'horizontalLine'
+ ]
+ } )
+
+ .then( newEditor => {
+ editor = newEditor;
+ } )
+ .catch( error => {
+ console.error( error );
+ } );
+ </script>
+ <style>
+ :root {
+ --ck-color-base-background: transparent;
+ }
+
+ .ck-toolbar {
+ filter: invert(1);
+ }
+
+ .ck-tooltip__text {
+ color: white !important;
+ }
+
+ .ck-dropdown__panel {
+ background: #ddd !important;
+ }
+
+ .ck-color-grid__tile {
+ filter: invert(1);
+ }
+
+ .ck-balloon-rotator {
+ background-color: #ccc !important;
+ }
+
+ .ck-balloon-panel {
+ filter: invert(1);
+ }
+ </style>
+ <script>
+ let lastSavedData = editor.getData();
+ let lastFetchedData = editor.getData();
+ let timeSinceLastModified = 0;
+ let saving = false;
+
+ async function save() {
+ let data = editor.getData();
+ document.getElementById("editor-save-status").innerHTML = "Saving...";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.add("text-primary");
+ saving = true;
+
+ try {
+ await window.fetch("/api/save?system=<?= $systemID ?>&member=null", {
+ method: "POST",
+ body: JSON.stringify({ content: data })
+ });
+ document.getElementById("editor-save-status").innerHTML = "Saved";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.add("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ lastSavedData = data;
+ saving = false;
+ } catch (e) {
+ console.error(e);
+ document.getElementById("editor-save-status").innerHTML = "Failed to save";
+ document.getElementById("editor-save-status").classList.add("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ }
+ }
+
+ document.onclick = async () => {
+ if (saving) return;
+
+ if (editor.getData() !== lastSavedData) {
+ await save();
+ }
+ }
+
+ setInterval(async () => {
+ if (saving) return;
+
+ document.getElementById("editor-size").innerHTML = editor.getData().length;
+
+ if (editor.getData() !== lastSavedData) {
+ document.getElementById("editor-save-status").innerHTML = "Modified";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.add("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+
+ if (editor.getData() !== lastFetchedData) {
+ lastFetchedData = editor.getData();
+ timeSinceLastModified = 0;
+ } else {
+ timeSinceLastModified++;
+ }
+
+ if (timeSinceLastModified > 20) {
+ await save();
+ }
+ } else {
+ timeSinceLastModified = 0;
+ document.getElementById("editor-save-status").innerHTML = "Saved";
+ document.getElementById("editor-save-status").classList.add("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ }
+ }, 100)
+ </script>
+</div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?> \ No newline at end of file
diff --git a/includes/system.php b/includes/system.php
new file mode 100644
index 0000000..606d63d
--- /dev/null
+++ b/includes/system.php
@@ -0,0 +1,61 @@
+<?php global $system; global $systemCommonName; global $systemID; $title = $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+
+function getMember(string $id) {
+ global $systemID;
+
+ $members = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-members.json"), true);
+ $member = null;
+
+ foreach ($members as $m) {
+ if ($m["id"] === $id) $member = $m;
+ }
+
+ return $member;
+}
+
+function timeAgo($time): string {
+ if (!is_numeric($time)) {
+ $time = strtotime($time);
+ }
+
+ $periods = ["second", "minute", "hour", "day", "week", "month", "year", "age"];
+ $lengths = array("60", "60", "24", "7", "4.35", "12", "100");
+
+ $now = time();
+
+ $difference = $now - $time;
+ if ($difference <= 10 && $difference >= 0) {
+ return $tense = "now";
+ } elseif ($difference > 0) {
+ $tense = "ago";
+ } else {
+ $tense = "later";
+ }
+
+ for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) {
+ $difference /= $lengths[$j];
+ }
+
+ $difference = round($difference);
+
+ $period = $periods[$j] . ($difference >1 ? "s" :'');
+ return "{$difference} {$period} {$tense} ";
+}
+
+?>
+
+<br>
+<div class="container">
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/sysbanner.php"; ?>
+ <br>
+
+ <div id="page-content">
+ <?php global $isLoggedIn; if ($isLoggedIn): ?>
+ <small style="opacity:.5;display:block;">(<a href="/edit/<?= $system ?>">edit</a>)</small>
+ <?php endif; ?>
+ <?= file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-disclaimers.html") ?>
+ </div>
+ <?php if ($system === "cloudburst") cloudburst(true); else raindrops(true); ?>
+</div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?> \ No newline at end of file
diff --git a/includes/system/compare.php b/includes/system/compare.php
new file mode 100644
index 0000000..96eb5d9
--- /dev/null
+++ b/includes/system/compare.php
@@ -0,0 +1,189 @@
+<?php global $system; global $systemCommonName; global $systemID; $title = "Compare members · " . $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+
+$members = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-members.json"), true);
+
+function getMember(string $id) {
+ global $systemID;
+ global $members;
+
+ $member = null;
+
+ foreach ($members as $m) {
+ if ($m["id"] === $id) $member = $m;
+ }
+
+ return $member;
+}
+
+?>
+
+ <br>
+ <div class="container">
+ <h2>Compare members of the <?= $systemCommonName ?></h2>
+ <div class="comparison">
+ <span class="comparison-header comparison-item">
+ <span class="comparison-header-l0">Member</span>
+ <span class="comparison-header-l1">Member</span>
+ <span class="comparison-header-l2">Member</span>
+ <span class="comparison-header-l3">Member</span>
+ <span class="comparison-header-l4">Member</span>
+ <span class="comparison-header-l5">Mmbr.</span>
+ </span>
+ <span class="comparison-header comparison-item">
+ <span class="comparison-header-l0">Species</span>
+ <span class="comparison-header-l1">Species</span>
+ <span class="comparison-header-l2">Species</span>
+ <span class="comparison-header-l3">Species</span>
+ <span class="comparison-header-l4">Spec.</span>
+ <span class="comparison-header-l5">Spec.</span>
+ </span>
+ <span class="comparison-header comparison-item">
+ <span class="comparison-header-l0">Relations</span>
+ <span class="comparison-header-l1">Relations</span>
+ <span class="comparison-header-l2">Relations</span>
+ <span class="comparison-header-l3">Relations</span>
+ <span class="comparison-header-l4">Relt.</span>
+ <span class="comparison-header-l5">Relt.</span>
+ </span>
+ <span class="comparison-header comparison-item">
+ <span class="comparison-header-l0">Host</span>
+ <span class="comparison-header-l1">Host</span>
+ <span class="comparison-header-l2">Host</span>
+ <span class="comparison-header-l3">Host</span>
+ <span class="comparison-header-l4">Hst.</span>
+ <span class="comparison-header-l5">Hst.</span>
+ </span>
+ <span class="comparison-header comparison-item">
+ <span class="comparison-header-l0">Fictive</span>
+ <span class="comparison-header-l1">Fictive</span>
+ <span class="comparison-header-l2">Fictive</span>
+ <span class="comparison-header-l3">Fictive</span>
+ <span class="comparison-header-l4">Fic.</span>
+ <span class="comparison-header-l5">Fic.</span>
+ </span>
+ <span class="comparison-header comparison-item">
+ <span class="comparison-header-l0">Little</span>
+ <span class="comparison-header-l1">Little</span>
+ <span class="comparison-header-l2">Little</span>
+ <span class="comparison-header-l3">Little</span>
+ <span class="comparison-header-l4">Ltl.</span>
+ <span class="comparison-header-l5">Ltl.</span>
+ </span>
+ <span class="comparison-header comparison-item">
+ <span class="comparison-header-l0">Not talking</span>
+ <span class="comparison-header-l1">No talk</span>
+ <span class="comparison-header-l2">No talk</span>
+ <span class="comparison-header-l3">NT.</span>
+ <span class="comparison-header-l4">NT.</span>
+ <span class="comparison-header-l5">NT.</span>
+ </span>
+ <span class="comparison-header comparison-item">
+ <span class="comparison-header-l0">Protector</span>
+ <span class="comparison-header-l1">Protector</span>
+ <span class="comparison-header-l2">Protect.</span>
+ <span class="comparison-header-l3">Protect.</span>
+ <span class="comparison-header-l4">Prt.</span>
+ <span class="comparison-header-l5">Prt.</span>
+ </span>
+
+ <?php foreach (scoreOrder($members, $systemID) as $member):
+
+ $metadata = $member["_metadata"];
+
+ if ($member["name"] === "scootaloo") {
+ if ((int)date('j') % 2 === 0) {
+ $metadata["marefriends"] = array_reverse($metadata["marefriends"]);
+ }
+ }
+
+ ?>
+ <a title="<?= $member["display_name"] ?? $member["name"] ?>" data-bs-toggle="tooltip" class="member-link comparison-item comparison-item-clickable" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $member["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <span style="vertical-align: middle;"><span class="comparison-name-full"><?= $member["display_name"] ?? $member["name"] ?></span><span class="comparison-name-small"><?= getMiniName($member["display_name"] ?? $member["name"]) ?></span></span>
+ <span class="comparison-colors" style="background-color: #<?= $member["color"] ?? "ffffff" ?>; height: 16px; width: 16px;display: inline-block;vertical-align: middle;border-radius:2px;"></span>
+ </a>
+ <span class="comparison-item">
+ <?php foreach ($metadata["species"] ?? [] as $species): ?>
+ <img data-bs-toggle="tooltip" title="<?php switch ($species) {
+ case "earth":
+ echo "Earth pony";
+ break;
+
+ case "alicorn":
+ echo "Alicorn";
+ break;
+
+ case "pegasus":
+ echo "Pegasus";
+ break;
+
+ case "batpony":
+ echo "Bat pony";
+ break;
+
+ case "unicorn":
+ echo "Unicorn";
+ break;
+
+ default:
+ echo $species;
+ break;
+ } ?>" style="width:24px;vertical-align: middle;position:relative;top:-5px;" src="/assets/species/<?= $species ?>.png" alt="<?= $species ?>">
+ <?php endforeach; ?>
+ </span>
+ <span class="comparison-item">
+ <?= count($metadata["marefriends"]) + count($metadata["sisters"]) === 0 ? "-" : "" ?>
+ <span class="comparison-relations-count">
+ <?= count($metadata["marefriends"]) + count($metadata["sisters"]) > 0 ? count($metadata["marefriends"]) + count($metadata["sisters"]) : "" ?>
+ </span>
+ <span class="comparison-relations-full">
+ <?php $index = 0; foreach ($metadata["marefriends"] as $marefriend): $mfSystem = explode("/", $marefriend)[0]; $mfMemberID = explode("/", $marefriend)[1]; $mfMember = array_filter(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$mfSystem-members.json"), true), function ($item) {
+ global $mfMemberID;
+ return $item["id"] === $mfMemberID;
+ }); sort($mfMember); $mfMember = $mfMember[0]; ?><a title="<b><?= $mfMember["display_name"] ?? $mfMember["name"] ?></b><br>Marefriend" data-bs-toggle="tooltip" data-bs-html="true" class="member-link" href="/<?= $mfSystem === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $mfMember["name"] ?>"><img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $mfMember['name'] . ".png") ? "-" . $mfMember['name'] : "" ?>.png" style="width:24px;"></a><?php $index++; endforeach; ?><?php $index = 0; foreach ($metadata["sisters"] as $marefriend): $mfSystem = explode("/", $marefriend)[0]; $mfMemberID = explode("/", $marefriend)[1]; $mfMember = array_filter(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$mfSystem-members.json"), true), function ($item) {
+ global $mfMemberID;
+ return $item["id"] === $mfMemberID;
+ }); sort($mfMember); $mfMember = $mfMember[0]; ?><a title="<b><?= $mfMember["display_name"] ?? $mfMember["name"] ?></b><br>Sister" data-bs-toggle="tooltip" data-bs-html="true" class="member-link" href="/<?= $mfSystem === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $mfMember["name"] ?>"><img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $mfMember['name'] . ".png") ? "-" . $mfMember['name'] : "" ?>.png" style="width:24px;"></a><?php $index++; endforeach; ?>
+ </span>
+ </span>
+ <span class="comparison-item">
+ <?php if ($metadata["host"]): ?>
+ <img data-bs-toggle="tooltip" title="Yes" src="/assets/icons/complete.svg" alt="Yes" style="width:24px;">
+ <?php else: ?>
+ <img data-bs-toggle="tooltip" title="No" src="/assets/icons/none.svg" alt="No" style="width:24px;">
+ <?php endif; ?>
+ </span>
+ <span class="comparison-item">
+ <?php if ($metadata["fictive"]): ?>
+ <img data-bs-toggle="tooltip" title="Yes" src="/assets/icons/complete.svg" alt="Yes" style="width:24px;">
+ <?php else: ?>
+ <img data-bs-toggle="tooltip" title="No" src="/assets/icons/none.svg" alt="No" style="width:24px;">
+ <?php endif; ?>
+ </span>
+ <span class="comparison-item">
+ <?php if ($metadata["little"] >= 2): ?>
+ <img data-bs-toggle="tooltip" title="Yes" src="/assets/icons/complete.svg" alt="Yes" style="width:24px;">
+ <?php elseif ($metadata["little"] === 1): ?>
+ <img data-bs-toggle="tooltip" title="Age regressor" src="/assets/icons/partial.svg" alt="Partial" style="width:24px;">
+ <?php else: ?>
+ <img data-bs-toggle="tooltip" title="No" src="/assets/icons/none.svg" alt="No" style="width:24px;">
+ <?php endif; ?>
+ </span>
+ <span class="comparison-item">
+ <?php if ($metadata["not_talking"]): ?>
+ <img data-bs-toggle="tooltip" title="Yes" src="/assets/icons/complete.svg" alt="Yes" style="width:24px;">
+ <?php else: ?>
+ <img data-bs-toggle="tooltip" title="No" src="/assets/icons/none.svg" alt="No" style="width:24px;">
+ <?php endif; ?>
+ </span>
+ <span class="comparison-item">
+ <?php if ($metadata["protector"]): ?>
+ <img data-bs-toggle="tooltip" title="Yes" src="/assets/icons/complete.svg" alt="Yes" style="width:24px;">
+ <?php else: ?>
+ <img data-bs-toggle="tooltip" title="No" src="/assets/icons/none.svg" alt="No" style="width:24px;">
+ <?php endif; ?>
+ </span>
+ <?php endforeach; ?>
+ </div>
+ </div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?> \ No newline at end of file
diff --git a/includes/system/history.php b/includes/system/history.php
new file mode 100644
index 0000000..ad4e30e
--- /dev/null
+++ b/includes/system/history.php
@@ -0,0 +1,380 @@
+<?php global $system; global $systemCommonName; global $systemID; $title = "Front history · " . $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+
+function getMember(string $id) {
+ global $systemID;
+
+ $members = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-members.json"), true);
+ $member = null;
+
+ foreach ($members as $m) {
+ if ($m["id"] === $id) $member = $m;
+ }
+
+ return $member;
+}
+
+?>
+
+ <br>
+ <div class="container" id="page-content">
+ <?php
+
+ $switches = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-switches.json"), true);
+ uksort($switches, function ($a, $b) {
+ return strtotime($b["timestamp"]) - strtotime($a["timestamp"]);
+ });
+
+ function getSwitchesForDay(int $day) {
+ global $switches;
+
+ $filtered = array_values(array_filter($switches, function ($i) use ($day) {
+ $diff = strtotime(date("Y-m-d")) - strtotime(explode("T", $i["timestamp"])[0]);
+ return $diff <= (86400 * $day) && $diff > (86400 * ($day - 1));
+ }));
+
+ uksort($filtered, function ($a, $b) {
+ return strtotime($b["timestamp"]) - strtotime($a["timestamp"]);
+ });
+
+ return $filtered;
+ }
+
+ function getSwitchBefore(string $id) {
+ global $switches;
+
+ $currentPassed = false;
+ $before = null;
+
+ foreach ($switches as $switch) {
+ if ($currentPassed) {
+ $before = $switch;
+ break;
+ } else {
+ if ($switch["id"] === $id) {
+ $currentPassed = true;
+ }
+ }
+ }
+
+ return $before;
+ }
+
+ function isNotToday(int $timestamp, int $offset) {
+ if (date('Y-m-d', $timestamp) !== date('Y-m-d', time() - (86400 * $offset))) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ $switches1 = getSwitchesForDay(0);
+ $switches2 = getSwitchesForDay(1);
+ $switches3 = getSwitchesForDay(2);
+ $switches4 = getSwitchesForDay(3);
+ $switches5 = getSwitchesForDay(4);
+ $switches6 = getSwitchesForDay(5);
+ $switches7 = getSwitchesForDay(6);
+ $switches8 = getSwitchesForDay(7);
+ $switches9 = getSwitchesForDay(8);
+ $switches10 = getSwitchesForDay(9);
+
+ ?>
+ <h2>Front history in the <?= $systemCommonName ?></h2>
+ <h4>Today</h4>
+ <?php
+
+ $fronters = array_map(function ($i) {
+ return [
+ "member" => $i["members"][0],
+ "date" => strtotime($i["timestamp"])
+ ];
+ }, $switches1);
+
+ $fronters[] = [
+ "member" => $switches2[0] ? $switches2[0]["members"][0] : ($switches3[0] ? $switches3[0]["members"][0] : ($switches4[0] ? $switches4[0]["members"][0] : ($switches5[0] ? $switches5[0]["members"][0] : ($switches6[0] ? $switches6[0]["members"][0] : ($switches7[0] ? $switches7[0]["members"][0] : ($switches8[0] ? $switches8[0]["members"][0] : ($switches9[0] ? $switches9[0]["members"][0] : $switches10[0]["members"][0]))))))),
+ "date" => strtotime($switches2[0] ? $switches2[0]["timestamp"] : ($switches3[0] ? $switches3[0]["timestamp"] : ($switches4[0] ? $switches4[0]["timestamp"] : ($switches5[0] ? $switches5[0]["timestamp"] : ($switches6[0] ? $switches6[0]["timestamp"] : ($switches7[0] ? $switches7[0]["timestamp"] : ($switches8[0] ? $switches8[0]["timestamp"] : ($switches9[0] ? $switches9[0]["timestamp"] : $switches10[0]["timestamp"]))))))))
+ ];
+
+ $fronters = array_unique($fronters, SORT_REGULAR);
+
+ foreach ($fronters as $fronter): $member = getMember($fronter["member"]);
+ ?>
+ <div class="fronter">
+ <span class="fronter-date" style="opacity:.5;font-family: monospace;font-size:14px;vertical-align: middle;">
+ <?= isNotToday($fronter["date"], 0) ? "00:00" : date('H:i', $fronter["date"]) ?>
+ </span>
+ <span class="fronter-profile" style="vertical-align: middle;">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $member["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= $member["display_name"] ?? $member["name"] ?>
+ </a>
+ </span>
+ </div>
+ <?php endforeach; ?>
+ <h4 style="margin-top:15px;">Yesterday</h4>
+ <?php
+
+ $fronters = array_map(function ($i) {
+ return [
+ "member" => $i["members"][0],
+ "date" => strtotime($i["timestamp"])
+ ];
+ }, $switches2);
+
+ $fronters[] = [
+ "member" => $switches3[0] ? $switches3[0]["members"][0] : ($switches4[0] ? $switches4[0]["members"][0] : ($switches5[0] ? $switches5[0]["members"][0] : ($switches6[0] ? $switches6[0]["members"][0] : ($switches7[0] ? $switches7[0]["members"][0] : ($switches8[0] ? $switches8[0]["members"][0] : ($switches9[0] ? $switches9[0]["members"][0] : $switches10[0]["members"][0])))))),
+ "date" => strtotime($switches3[0] ? $switches3[0]["timestamp"] : ($switches4[0] ? $switches4[0]["timestamp"] : ($switches5[0] ? $switches5[0]["timestamp"] : ($switches6[0] ? $switches6[0]["timestamp"] : ($switches7[0] ? $switches7[0]["timestamp"] : ($switches8[0] ? $switches8[0]["timestamp"] : ($switches9[0] ? $switches9[0]["timestamp"] : $switches10[0]["timestamp"])))))))
+ ];
+
+ $fronters = array_unique($fronters, SORT_REGULAR);
+
+ foreach ($fronters as $fronter): $member = getMember($fronter["member"]);
+ ?>
+ <div class="fronter">
+ <span class="fronter-date" style="opacity:.5;font-family: monospace;font-size:14px;vertical-align: middle;">
+ <?= isNotToday($fronter["date"], 1) ? "00:00" : date('H:i', $fronter["date"]) ?>
+ </span>
+ <span class="fronter-profile" style="vertical-align: middle;">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $member["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= $member["display_name"] ?? $member["name"] ?>
+ </a>
+ </span>
+ </div>
+ <?php endforeach; ?>
+ <h4 style="margin-top:15px;"><?= date('D j M', time() - (86400 * 2)) ?></h4>
+ <?php
+
+ $fronters = array_map(function ($i) {
+ return [
+ "member" => $i["members"][0],
+ "date" => strtotime($i["timestamp"])
+ ];
+ }, $switches3);
+
+ $fronters[] = [
+ "member" => $switches4[0] ? $switches4[0]["members"][0] : ($switches5[0] ? $switches5[0]["members"][0] : ($switches6[0] ? $switches6[0]["members"][0] : ($switches7[0] ? $switches7[0]["members"][0] : ($switches8[0] ? $switches8[0]["members"][0] : ($switches9[0] ? $switches9[0]["members"][0] : $switches10[0]["members"][0]))))),
+ "date" => strtotime($switches4[0] ? $switches4[0]["timestamp"] : ($switches5[0] ? $switches5[0]["timestamp"] : ($switches6[0] ? $switches6[0]["timestamp"] : ($switches7[0] ? $switches7[0]["timestamp"] : ($switches8[0] ? $switches8[0]["timestamp"] : ($switches9[0] ? $switches9[0]["timestamp"] : $switches10[0]["timestamp"]))))))
+ ];
+
+ $fronters = array_unique($fronters, SORT_REGULAR);
+
+ foreach ($fronters as $fronter): $member = getMember($fronter["member"]);
+ ?>
+ <div class="fronter">
+ <span class="fronter-date" style="opacity:.5;font-family: monospace;font-size:14px;vertical-align: middle;">
+ <?= isNotToday($fronter["date"], 2) ? "00:00" : date('H:i', $fronter["date"]) ?>
+ </span>
+ <span class="fronter-profile" style="vertical-align: middle;">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $member["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= $member["display_name"] ?? $member["name"] ?>
+ </a>
+ </span>
+ </div>
+ <?php endforeach; ?>
+ <h4 style="margin-top:15px;"><?= date('D j M', time() - (86400 * 3)) ?></h4>
+ <?php
+
+ $fronters = array_map(function ($i) {
+ return [
+ "member" => $i["members"][0],
+ "date" => strtotime($i["timestamp"])
+ ];
+ }, $switches4);
+
+ $fronters[] = [
+ "member" => $switches5[0] ? $switches5[0]["members"][0] : ($switches6[0] ? $switches6[0]["members"][0] : ($switches7[0] ? $switches7[0]["members"][0] : ($switches8[0] ? $switches8[0]["members"][0] : ($switches9[0] ? $switches9[0]["members"][0] : $switches10[0]["members"][0])))),
+ "date" => strtotime($switches5[0] ? $switches5[0]["timestamp"] : ($switches6[0] ? $switches6[0]["timestamp"] : ($switches7[0] ? $switches7[0]["timestamp"] : ($switches8[0] ? $switches8[0]["timestamp"] : ($switches9[0] ? $switches9[0]["timestamp"] : $switches10[0]["timestamp"])))))
+ ];
+
+ $fronters = array_unique($fronters, SORT_REGULAR);
+
+ foreach ($fronters as $fronter): $member = getMember($fronter["member"]);
+ ?>
+ <div class="fronter">
+ <span class="fronter-date" style="opacity:.5;font-family: monospace;font-size:14px;vertical-align: middle;">
+ <?= isNotToday($fronter["date"], 3) ? "00:00" : date('H:i', $fronter["date"]) ?>
+ </span>
+ <span class="fronter-profile" style="vertical-align: middle;">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $member["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= $member["display_name"] ?? $member["name"] ?>
+ </a>
+ </span>
+ </div>
+ <?php endforeach; ?>
+ <h4 style="margin-top:15px;"><?= date('D j M', time() - (86400 * 4)) ?></h4>
+ <?php
+
+ $fronters = array_map(function ($i) {
+ return [
+ "member" => $i["members"][0],
+ "date" => strtotime($i["timestamp"])
+ ];
+ }, $switches5);
+
+ $fronters[] = [
+ "member" => $switches6[0] ? $switches6[0]["members"][0] : ($switches7[0] ? $switches7[0]["members"][0] : ($switches8[0] ? $switches8[0]["members"][0] : ($switches9[0] ? $switches9[0]["members"][0] : $switches10[0]["members"][0]))),
+ "date" => strtotime($switches6[0] ? $switches6[0]["timestamp"] : ($switches7[0] ? $switches7[0]["timestamp"] : ($switches8[0] ? $switches8[0]["timestamp"] : ($switches9[0] ? $switches9[0]["timestamp"] : $switches10[0]["timestamp"]))))
+ ];
+
+ $fronters = array_unique($fronters, SORT_REGULAR);
+
+ foreach ($fronters as $fronter): $member = getMember($fronter["member"]);
+ ?>
+ <div class="fronter">
+ <span class="fronter-date" style="opacity:.5;font-family: monospace;font-size:14px;vertical-align: middle;">
+ <?= isNotToday($fronter["date"], 4) ? "00:00" : date('H:i', $fronter["date"]) ?>
+ </span>
+ <span class="fronter-profile" style="vertical-align: middle;">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $member["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= $member["display_name"] ?? $member["name"] ?>
+ </a>
+ </span>
+ </div>
+ <?php endforeach; ?>
+ <h4 style="margin-top:15px;"><?= date('D j M', time() - (86400 * 5)) ?></h4>
+ <?php
+
+ $fronters = array_map(function ($i) {
+ return [
+ "member" => $i["members"][0],
+ "date" => strtotime($i["timestamp"])
+ ];
+ }, $switches6);
+
+ $fronters[] = [
+ "member" => $switches7[0] ? $switches7[0]["members"][0] : ($switches8[0] ? $switches8[0]["members"][0] : ($switches9[0] ? $switches9[0]["members"][0] : $switches10[0]["members"][0])),
+ "date" => strtotime($switches7[0] ? $switches7[0]["timestamp"] : ($switches8[0] ? $switches8[0]["timestamp"] : ($switches9[0] ? $switches9[0]["timestamp"] : $switches10[0]["timestamp"])))
+ ];
+
+ $fronters = array_unique($fronters, SORT_REGULAR);
+
+ foreach ($fronters as $fronter): $member = getMember($fronter["member"]);
+ ?>
+ <div class="fronter">
+ <span class="fronter-date" style="opacity:.5;font-family: monospace;font-size:14px;vertical-align: middle;">
+ <?= isNotToday($fronter["date"], 5) ? "00:00" : date('H:i', $fronter["date"]) ?>
+ </span>
+ <span class="fronter-profile" style="vertical-align: middle;">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $member["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= $member["display_name"] ?? $member["name"] ?>
+ </a>
+ </span>
+ </div>
+ <?php endforeach; ?>
+ <h4 style="margin-top:15px;"><?= date('D j M', time() - (86400 * 6)) ?></h4>
+ <?php
+
+ $fronters = array_map(function ($i) {
+ return [
+ "member" => $i["members"][0],
+ "date" => strtotime($i["timestamp"])
+ ];
+ }, $switches7);
+
+ $fronters[] = [
+ "member" => $switches8[0] ? $switches8[0]["members"][0] : ($switches9[0] ? $switches9[0]["members"][0] : $switches10[0]["members"][0]),
+ "date" => strtotime($switches8[0] ? $switches8[0]["timestamp"] : ($switches9[0] ? $switches9[0]["timestamp"] : $switches10[0]["timestamp"]))
+ ];
+
+ $fronters = array_unique($fronters, SORT_REGULAR);
+
+ foreach ($fronters as $fronter): $member = getMember($fronter["member"]);
+ ?>
+ <div class="fronter">
+ <span class="fronter-date" style="opacity:.5;font-family: monospace;font-size:14px;vertical-align: middle;">
+ <?= isNotToday($fronter["date"], 6) ? "00:00" : date('H:i', $fronter["date"]) ?>
+ </span>
+ <span class="fronter-profile" style="vertical-align: middle;">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $member["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= $member["display_name"] ?? $member["name"] ?>
+ </a>
+ </span>
+ </div>
+ <?php endforeach; ?>
+ <h4 style="margin-top:15px;"><?= date('D j M', time() - (86400 * 7)) ?></h4>
+ <?php
+
+ $fronters = array_map(function ($i) {
+ return [
+ "member" => $i["members"][0],
+ "date" => strtotime($i["timestamp"])
+ ];
+ }, $switches8);
+
+ $fronters[] = [
+ "member" => $switches9[0] ? $switches9[0]["members"][0] : $switches10[0]["members"][0],
+ "date" => strtotime($switches9[0] ? $switches9[0]["timestamp"] : $switches10[0]["timestamp"])
+ ];
+
+ $fronters = array_unique($fronters, SORT_REGULAR);
+
+ foreach ($fronters as $fronter): $member = getMember($fronter["member"]);
+ ?>
+ <div class="fronter">
+ <span class="fronter-date" style="opacity:.5;font-family: monospace;font-size:14px;vertical-align: middle;">
+ <?= isNotToday($fronter["date"], 7) ? "00:00" : date('H:i', $fronter["date"]) ?>
+ </span>
+ <span class="fronter-profile" style="vertical-align: middle;">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $member["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= $member["display_name"] ?? $member["name"] ?>
+ </a>
+ </span>
+ </div>
+ <?php endforeach; ?>
+ <h4 style="margin-top:15px;"><?= date('D j M', time() - (86400 * 8)) ?></h4>
+ <?php
+
+ $fronters = array_map(function ($i) {
+ return [
+ "member" => $i["members"][0],
+ "date" => strtotime($i["timestamp"])
+ ];
+ }, $switches9);
+
+ $fronters[] = [
+ "member" => $switches10[0] ? $switches10[0]["members"][0] : getSwitchBefore($switches9[count($switches9) - 1]["id"])["members"][0],
+ "date" => strtotime($switches10[0] ? $switches10[0]["timestamp"] : getSwitchBefore($switches9[count($switches9) - 1]["id"])["timestamp"])
+ ];
+
+ $fronters = array_unique($fronters, SORT_REGULAR);
+
+ foreach ($fronters as $fronter): $member = getMember($fronter["member"]);
+ ?>
+ <div class="fronter">
+ <span class="fronter-date" style="opacity:.5;font-family: monospace;font-size:14px;vertical-align: middle;">
+ <?= isNotToday($fronter["date"], 8) ? "00:00" : date('H:i', $fronter["date"]) ?>
+ </span>
+ <span class="fronter-profile" style="vertical-align: middle;">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $member["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= $member["display_name"] ?? $member["name"] ?>
+ </a>
+ </span>
+ </div>
+ <?php endforeach; ?>
+ <h4 style="margin-top:15px;"><?= date('D j M', time() - (86400 * 9)) ?></h4>
+ <?php
+
+ $fronters = array_map(function ($i) {
+ return [
+ "member" => $i["members"][0],
+ "date" => strtotime($i["timestamp"])
+ ];
+ }, $switches10);
+
+ $fronters = array_unique($fronters, SORT_REGULAR);
+
+ foreach ($fronters as $fronter): $member = getMember($fronter["member"]);
+ ?>
+ <div class="fronter">
+ <span class="fronter-date" style="opacity:.5;font-family: monospace;font-size:14px;vertical-align: middle;">
+ <?= isNotToday($fronter["date"], 9) ? "00:00" : date('H:i', $fronter["date"]) ?>
+ </span>
+ <span class="fronter-profile" style="vertical-align: middle;">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $member["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= $member["display_name"] ?? $member["name"] ?>
+ </a>
+ </span>
+ </div>
+ <?php endforeach; ?>
+ </div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?> \ No newline at end of file
diff --git a/includes/system/species.php b/includes/system/species.php
new file mode 100644
index 0000000..a2251d0
--- /dev/null
+++ b/includes/system/species.php
@@ -0,0 +1,53 @@
+<?php global $system; global $systemCommonName; global $systemID; $title = "Members by species · " . $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+
+$members = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-members.json"), true);
+$members = scoreOrder($members, $systemID);
+
+function species(array $members, string $id, string $name) { global $systemID; ?>
+ <div class="relation" style="background-color:rgba(255, 255, 255, .1);margin-bottom:10px;padding:10px;border-radius:10px;display:grid;grid-template-columns: 1fr 4fr;">
+ <div class="relation-intro" style="background-color:rgba(255, 255, 255, .05);border-right:1px solid rgba(255, 255, 255, .1);margin:-10px;padding:10px;border-top-left-radius:10px;border-bottom-left-radius:10px;color: white;text-decoration: none;">
+ <img src="/assets/species/<?= $id ?>.png" style="width:24px;"><span class="species-name"> <?= $name ?></span> (<?= count($members) ?>)
+ </div>
+
+ <div class="relation-item" style="margin-left:10px;padding:0 20px;">
+ <?php if (count($members) > 0): ?>
+ <?php $index = 0; foreach ($members as $member): ?>
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $member["name"] ?>"><img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <?= getMiniName($member["display_name"] ?? $member["name"]) ?></a><?php if ($index + 2 <= count($members)) echo('<span class="list-separator-desktop">, &nbsp;</span><span class="list-separator-mobile"><br></span>'); $index++; endforeach; ?>
+ <?php else: ?>-<?php endif; ?>
+ </div>
+ </div>
+<?php }
+
+?>
+
+ <br>
+ <div class="container" id="page-content">
+ <h2><?= $systemCommonName ?> members by species</h2>
+ <?php
+
+ $earth = [];
+ $pegasus = [];
+ $unicorn = [];
+ $alicorn = [];
+ $batpony = [];
+
+ foreach ($members as $member) {
+ foreach ($member["_metadata"]["species"] as $species) {
+ if ($species === "earth") $earth[] = $member;
+ if ($species === "pegasus") $pegasus[] = $member;
+ if ($species === "unicorn") $unicorn[] = $member;
+ if ($species === "alicorn") $alicorn[] = $member;
+ if ($species === "batpony") $batpony[] = $member;
+ }
+ }
+
+ ?>
+
+ <?php species($earth, "earth", "Earth ponies"); ?>
+ <?php species($pegasus, "pegasus", "Pegasi"); ?>
+ <?php species($unicorn, "unicorn", "Unicorns"); ?>
+ <?php species($alicorn, "alicorn", "Alicorns"); ?>
+ <?php species($batpony, "batpony", "Bat ponies"); ?>
+ </div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?> \ No newline at end of file
diff --git a/includes/system/subsystem.php b/includes/system/subsystem.php
new file mode 100644
index 0000000..5d28cba
--- /dev/null
+++ b/includes/system/subsystem.php
@@ -0,0 +1,124 @@
+<?php global $system; global $systemCommonName; global $parts; global $systemID;
+
+$members = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-members.json"), true);
+
+$subsystemID = $parts[3];
+
+$subsystems = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-subsystems.json"), true) ?? [];
+
+function getMember(string $id) {
+ global $systemID;
+ global $members;
+
+ $member = null;
+
+ foreach ($members as $m) {
+ if ($m["id"] === $id) $member = $m;
+ }
+
+ return $member;
+}
+
+function memberHasSubsystem(array $member) {
+ global $subsystems;
+ $has = false;
+
+ foreach ($subsystems as $subsystem) {
+ if ($subsystem["source_type"] === "member" && $subsystem["source"] === $member["id"]) {
+ $has = true;
+ }
+ }
+
+ return $has;
+}
+
+function memberPartOfSubsystem(array $member) {
+ global $subsystems;
+ $is = false;
+
+ foreach ($subsystems as $subsystem) {
+ if (in_array($member["id"], $subsystem["members"])) {
+ $is = true;
+ }
+ }
+
+ return $is;
+}
+
+function getMemberSubsystem(array $member) {
+ global $subsystems;
+ $subsystem = null;
+
+ foreach ($subsystems as $ss) {
+ if ($ss["source_type"] === "member" && $ss["source"] === $member["id"]) {
+ $subsystem = $ss;
+ }
+ }
+
+ return $subsystem;
+}
+
+function getSubsystemByID(string $id) {
+ global $subsystems;
+ $subsystem = null;
+
+ foreach ($subsystems as $ss) {
+ if ($ss["source"] === $id) {
+ $subsystem = $ss;
+ }
+ }
+
+ return $subsystem;
+}
+
+function timeAgo($time): string {
+ if (!is_numeric($time)) {
+ $time = strtotime($time);
+ }
+
+ $periods = ["second", "minute", "hour", "day", "week", "month", "year", "age"];
+ $lengths = array("60", "60", "24", "7", "4.35", "12", "100");
+
+ $now = time();
+
+ $difference = $now - $time;
+ if ($difference <= 10 && $difference >= 0) {
+ return $tense = "now";
+ } elseif ($difference > 0) {
+ $tense = "ago";
+ } else {
+ $tense = "later";
+ }
+
+ for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) {
+ $difference /= $lengths[$j];
+ }
+
+ $difference = round($difference);
+
+ $period = $periods[$j] . ($difference >1 ? "s" :'');
+ return "{$difference} {$period} {$tense} ";
+}
+
+if (getSubsystemByID($subsystemID) === null) header("Location: /?error=Invalid subsystem ID") and die();
+$subsystemData = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-subsystem-$subsystemID.json"), true);
+
+$title = $subsystemData["name"] . " · " . $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+
+?>
+
+ <br>
+ <div class="container">
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/subsysbanner.php"; ?>
+ <br>
+
+ <div id="page-content">
+ <?php global $isLoggedIn; if ($isLoggedIn): ?>
+ <small style="opacity:.5;display:block;">(<a href="/edit/<?= $system ?>/<?= $subsystemID ?>">edit</a>)</small>
+ <?php endif; ?>
+ <?= file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-subsystem-$subsystemID.html") ?>
+ </div>
+ <?php showSubsystem(getSubsystemByID($subsystemID), $systemID); ?>
+ </div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?> \ No newline at end of file
diff --git a/includes/system/tree.php b/includes/system/tree.php
new file mode 100644
index 0000000..04cbf83
--- /dev/null
+++ b/includes/system/tree.php
@@ -0,0 +1,114 @@
+<?php global $system; global $systemCommonName; global $systemID; $title = "System tree · " . $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+
+$members = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-members.json"), true);
+$members = scoreOrder($members, $systemID);
+
+$subsystems = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-subsystems.json"), true) ?? [];
+
+function getMember(string $id) {
+ global $systemID;
+ global $members;
+
+ $member = null;
+
+ foreach ($members as $m) {
+ if ($m["id"] === $id) $member = $m;
+ }
+
+ return $member;
+}
+
+function memberHasSubsystem(array $member) {
+ global $subsystems;
+ $has = false;
+
+ foreach ($subsystems as $subsystem) {
+ if ($subsystem["source_type"] === "member" && $subsystem["source"] === $member["id"]) {
+ $has = true;
+ }
+ }
+
+ return $has;
+}
+
+function memberPartOfSubsystem(array $member) {
+ global $subsystems;
+ $is = false;
+
+ foreach ($subsystems as $subsystem) {
+ if (in_array($member["id"], $subsystem["members"])) {
+ $is = true;
+ }
+ }
+
+ return $is;
+}
+
+function getMemberSubsystem(array $member) {
+ global $subsystems;
+ $subsystem = null;
+
+ foreach ($subsystems as $ss) {
+ if ($ss["source_type"] === "member" && $ss["source"] === $member["id"]) {
+ $subsystem = $ss;
+ }
+ }
+
+ return $subsystem;
+}
+
+?>
+
+ <br>
+ <div class="container" id="page-content">
+ <h2>System tree for the <?= $systemCommonName ?></h2>
+ <a class="tree-root member-link" href="/<?= $system ?>">
+ <img src="/assets/uploads/<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/" . $system . ".png") ? $system : "" ?>.png" style="width:24px;"> <span style="vertical-align: middle;"><?= $systemCommonName ?></span></a>
+ <?php $first = true; ?>
+ <?php foreach ($subsystems as $subsystem): if ($subsystem["source_type"] === "trait"): ?>
+ <div class="tree-l0">
+ <div class="tree-l0-separator<?= $first ? " tree-first-separator" : "" ?>">&nbsp;</div>
+ <div class="tree-inner">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/-/subsystem/<?= $subsystem["source"] ?>">
+ <img src="/assets/uploads/ss-<?= $subsystem["source"] ?>.png" style="width:24px;border-radius:5px;"> <span style="vertical-align: middle;"><?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-subsystem-$subsystem[source].json") ? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID-subsystem-$subsystem[source].json"), true)["name"] : $subsystem["source"] ?></span>
+ </a>
+ </div>
+ <?php $ssfirst = true; foreach ($subsystem["members"] as $ssm): $ssmember = getMember($ssm); ?>
+ <div class="tree-l1">
+ <div class="tree-l0-separator">&nbsp;</div>
+ <div class="tree-l1-separator<?= $ssfirst ? " tree-first-separator" : "" ?>">&nbsp;</div>
+ <div class="tree-inner">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $ssmember["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $ssmember['name'] . ".png") ? "-" . $ssmember['name'] : "" ?>.png" style="width:24px;"> <span style="vertical-align: middle;"><?= $ssmember["display_name"] ?? $ssmember["name"] ?></span>
+ </a>
+ </div>
+ </div>
+ <?php $ssfirst = false; endforeach; ?>
+ </div>
+ <?php $first = false; endif; endforeach; ?>
+ <?php foreach ($members as $member): if (!memberPartOfSubsystem($member)): ?>
+ <div class="tree-l0">
+ <div class="tree-l0-separator<?= $first ? " tree-first-separator" : "" ?>">&nbsp;</div>
+ <div class="tree-inner">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $member["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $member['name'] . ".png") ? "-" . $member['name'] : "" ?>.png" style="width:24px;"> <span style="vertical-align: middle;"><?= $member["display_name"] ?? $member["name"] ?></span>
+ </a>
+ </div>
+ <?php if (memberHasSubsystem($member)): ?>
+ <?php $ssfirst = true; foreach (getMemberSubsystem($member)["members"] as $ssm): $ssmember = getMember($ssm); ?>
+ <div class="tree-l1">
+ <div class="tree-l0-separator">&nbsp;</div>
+ <div class="tree-l1-separator<?= $ssfirst ? " tree-first-separator" : "" ?>">&nbsp;</div>
+ <div class="tree-inner">
+ <a class="member-link" href="/<?= $systemID === "gdapd" ? "raindrops" : "cloudburst" ?>/<?= $ssmember["name"] ?>">
+ <img src="/assets/uploads/pt<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-" . $ssmember['name'] . ".png") ? "-" . $ssmember['name'] : "" ?>.png" style="width:24px;"> <span style="vertical-align: middle;"><?= $ssmember["display_name"] ?? $ssmember["name"] ?></span>
+ </a>
+ </div>
+ </div>
+ <?php $ssfirst = false;endforeach; ?>
+ <?php endif; ?>
+ </div>
+ <?php $first = false; endif; endforeach; ?>
+ </div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?> \ No newline at end of file