summaryrefslogtreecommitdiff
path: root/pages
diff options
context:
space:
mode:
Diffstat (limited to 'pages')
-rw-r--r--pages/actions.php1132
-rw-r--r--pages/bitset.php32
-rw-r--r--pages/dashboard.php299
-rw-r--r--pages/debug.php91
-rw-r--r--pages/edit.php2
-rw-r--r--pages/emergency.php2
-rw-r--r--pages/fronting.php2
-rw-r--r--pages/nicknames.php117
-rw-r--r--pages/parser.php2
-rw-r--r--pages/pleasure.php2
-rw-r--r--pages/prefix.php2
-rw-r--r--pages/rules.php254
-rw-r--r--pages/score.php4
-rw-r--r--pages/splitting.php2
-rw-r--r--pages/together.php2
-rw-r--r--pages/toys.php915
-rw-r--r--pages/travelling.php4
17 files changed, 2846 insertions, 18 deletions
diff --git a/pages/actions.php b/pages/actions.php
new file mode 100644
index 0000000..1fa998a
--- /dev/null
+++ b/pages/actions.php
@@ -0,0 +1,1132 @@
+<?php
+
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
+if (!$isLoggedIn) header("Location: /-/login") and die();
+
+if (isset($_POST['deleteAction'])) {
+ $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json"), true);
+
+ $selected = null;
+ $selectedIndex = -1;
+ $id = $_POST['action'];
+
+ foreach ($data as $index => $item) {
+ if ($item["id"] === $id) {
+ $selectedIndex = $index;
+ $selected = $item;
+ break;
+ }
+ }
+
+ if ($selected === null) {
+ header("Location: /-/actions");
+ die();
+ }
+
+ unset($data[$selectedIndex]);
+ @mkdir($_SERVER['DOCUMENT_ROOT'] . "/includes/data/oldactions");
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/oldactions/" . date('c') . ".json", utf8_encode(json_encode($data)));
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json", utf8_encode(json_encode($data)));
+ header("Location: /-/actions/?d&id=" . $id);
+ die();
+}
+
+if (isset($_POST['updateAction'])) {
+ $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json"), true);
+
+ $selected = null;
+ $selectedIndex = -1;
+ $id = $_POST['action'];
+
+ foreach ($data as $index => $item) {
+ if ($item["id"] === $id) {
+ $selectedIndex = $index;
+ $selected = $item;
+ break;
+ }
+ }
+
+ if ($selected === null) {
+ header("Location: /-/actions");
+ die();
+ }
+
+ if (isset($_POST["consent"])) {
+ $selected["consent"] = true;
+ } else {
+ $selected["consent"] = false;
+ }
+
+ if (isset($_POST["name"])) $selected["name"] = strip_tags(trim($_POST["name"]));
+ if (isset($_POST["example"])) $selected["example"] = strip_tags(trim($_POST["example"]));
+ if (isset($_POST["irl"])) $selected["irl"] = strip_tags(trim($_POST["irl"]));
+ if (isset($_POST["keywords"])) $selected["keywords"] = array_map(function ($i) {
+ return trim($i);
+ }, explode(",", strip_tags(trim($_POST["keywords"]))));
+ if (isset($_POST["description"])) $selected["description"] = strip_tags(trim($_POST["description"]));
+ if (isset($_POST["type"])) $selected["type"] = match ($_POST["type"]) {
+ "0" => "affectionate",
+ "1" => "sexual",
+ "2" => "mixed"
+ };
+
+ if (isset($_POST["relations"])) {
+ $ponies = [];
+
+ foreach ($_POST["relations"] as $relation => $d) {
+ $ponies[] = [
+ "members" => explode("-", $relation),
+ "deprecated" => isset($d["deprecated"]),
+ "sexual" => isset($d["sexual"])
+ ];
+ }
+
+ $selected["ponies"] = $ponies;
+ }
+
+ global $_PROFILE;
+ if ($_PROFILE['login'] === "raindrops" && isset($_POST["verified"])) {
+ $selected["verified"] = true;
+ } else {
+ unset($selected["verified"]);
+ }
+
+ if (isset($_POST["untested"])) {
+ $selected["untested"] = true;
+ } else {
+ unset($selected["untested"]);
+ }
+
+ $data[$selectedIndex] = $selected;
+ @mkdir($_SERVER['DOCUMENT_ROOT'] . "/includes/data/oldactions");
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/oldactions/" . date('c') . ".json", utf8_encode(json_encode($data)));
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json", utf8_encode(json_encode($data)));
+
+ header("Location: /-/actions/" . $id);
+ die();
+}
+
+if (isset($_POST['createAction'])) {
+ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/random.php";
+ $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json"), true);
+
+ if (!isset($_POST["name"]) || !isset($_POST["type"])) {
+ header("Location: /-/actions");
+ die();
+ }
+ if (trim($_POST["name"]) === "" || !is_numeric($_POST["type"])) {
+ header("Location: /-/actions");
+ die();
+ }
+
+ $type = match ($_POST["type"]) {
+ "0" => "affectionate",
+ "1" => "sexual",
+ "2" => "mixed"
+ };
+ $name = strip_tags(trim($_POST["name"]));
+ $id = random();
+
+ $ponies = [];
+
+ if (isset($_POST["relations"])) {
+ foreach ($_POST["relations"] as $relation => $_) {
+ $ponies[] = [
+ "members" => explode("-", $relation),
+ "deprecated" => false,
+ "sexual" => false
+ ];
+ }
+ }
+
+ if (isset($_POST["consent"])) {
+ $consent = true;
+ } else {
+ $consent = false;
+ }
+
+ $data[] = [
+ "id" => $id,
+ "name" => $name,
+ "type" => $type,
+ "description" => null,
+ "ponies" => $ponies,
+ "example" => null,
+ "irl" => null,
+ "keywords" => [],
+ "consent" => $consent
+ ];
+
+ @mkdir($_SERVER['DOCUMENT_ROOT'] . "/includes/data/oldactions");
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/oldactions/" . date('c') . ".json", utf8_encode(json_encode($data)));
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json", utf8_encode(json_encode($data)));
+ header("Location: /-/actions/" . $id);
+ die();
+}
+
+global $pagename;
+$parts = explode("/", $pagename);
+$data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json"), true);
+$toys = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json"), true);
+
+$selected = null;
+$title = "Actions database";
+
+if (isset($parts[1])) {
+ $id = $parts[1];
+
+ foreach ($data as $item) {
+ if ($item["id"] === $id) {
+ $selected = $item;
+ break;
+ }
+ }
+
+ if ($selected === null) {
+ header("Location: /-/actions/?nf&id=" . $id);
+ die();
+ } else {
+ $title = $selected["name"] . " · Actions database";
+ }
+}
+
+require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/keywords.php';
+
+if (!file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json")) file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json", "[]");
+
+global $_PROFILE;
+
+?>
+
+<script src="/assets/editor/fuse.js"></script>
+
+<br>
+<div class="container">
+ <div id="<?= isset($parts[1]) ? "page-content" : "" ?>">
+ <?php if (isset($_GET['nf'])): ?>
+ <div class="alert alert-danger alert-dismissible">
+ <button onclick='window.history.pushState({"html":null,"pageTitle":document.title},"", "/-/actions");' type="button" class="btn-close" data-bs-dismiss="alert" style="filter: none !important;"></button>
+ <b>Error: </b> The requested action (<code><?= strip_tags($_GET['id'] ?? "-") ?></code>) was not found, it may have been deleted or has never existed.
+ </div>
+ <?php endif; ?>
+
+ <?php if (isset($_GET['d'])): ?>
+ <div class="alert alert-success alert-dismissible">
+ <button onclick='window.history.pushState({"html":null,"pageTitle":document.title},"", "/-/actions");' type="button" class="btn-close" data-bs-dismiss="alert" style="filter: none !important;"></button>
+ <b>Success: </b> The action with ID <code><?= strip_tags($_GET['id'] ?? "-") ?></code> has been successfully deleted.
+ </div>
+ <?php endif; ?>
+
+ <?php if (isset($parts[1])): ?>
+
+ <h2>
+ <span style="vertical-align: middle;"><?= $selected["name"] ?></span>
+ <a href="/-/actions" class="small btn btn-outline-light" style="float:right;margin-top:5px;vertical-align:middle;opacity:1 !important; color:white;">Back</a>
+ </h2>
+ <p>
+ <a onclick="event.target.blur();" data-bs-toggle="modal" data-bs-target="#editor" href="#">Edit</a> ·
+ <?php if ($selected["type"] === "affectionate"): ?>
+ <span style="" class="badge rounded-pill bg-primary">Affectionate</span>
+ <?php endif; ?>
+ <?php if ($selected["type"] === "sexual"): ?>
+ <span style="" class="badge rounded-pill bg-danger">Sexual</span>
+ <?php endif; ?>
+ <?php if ($selected["type"] === "mixed"): ?>
+ <span style="" class="badge rounded-pill bg-primary">Affectionate</span> <span style="" class="badge rounded-pill bg-danger">Sexual</span>
+ <?php endif; ?>
+ <?php if (!isset($item["verified"])): ?><span class="badge bg-warning rounded-pill text-black">Unverified</span><?php endif; ?>
+ <?php if (isset($item["untested"])): ?><span class="badge bg-info rounded-pill">Untested</span><?php endif; ?>
+ </p>
+
+ <hr>
+ <?php if ($selected["consent"] && $selected["type"] !== "affectionate"): ?>
+ <div class="alert alert-danger">
+ <b>This action requires consent.</b> Since this action constitues a sexual act, verbal consent from both parties is absolutely required. Both parties must be in a mental state where they are able to consent. Furthermore, if one of the parties involved wishes to stop, the request must be immediately honoured.
+ </div>
+ <?php elseif ($selected["consent"]): ?>
+ <div class="alert alert-warning">
+ <b>This action is better with consent.</b> This action is extremely personal to all parties involved, and it is best to ask if they wish to do this. If they ask to stop the action, you must stop immediately and reverse any potential effects.
+ </div>
+ <?php endif; ?>
+
+ <?php if (isset($selected["description"]) && trim($selected["description"]) !== ""): ?>
+ <?= replaceKeyWords(str_replace("\n", "<br>", strip_tags($selected["description"]))); ?>
+ <?php else: ?>
+ <p><i>No description provided for this action. Enter edit mode to add a description to this action.</i></p>
+ <?php endif; ?>
+
+ <?php if (isset($selected["toys"]) && $selected["toys"]): ?>
+ <hr>
+ <p><b>Available toys:</b></p>
+ <div id="toys-list">
+ <div class="toys-list-inner">
+ <?php
+
+ $init = [];
+ foreach ($toys as $value) {
+ $init[$value["name"]] = $value;
+ }
+
+ ksort($init);
+
+ $sorted = array_values($init);
+ uasort($sorted, function ($a, $b) {
+ $uniquePonies1 = [];
+
+ foreach ($a["ponies"] as $ponies) {
+ foreach ($ponies["members"] as $member) {
+ $uniquePonies1[$member] = true;
+ }
+ }
+
+ $uniquePonies2 = [];
+
+ foreach ($b["ponies"] as $ponies) {
+ foreach ($ponies["members"] as $member) {
+ $uniquePonies2[$member] = true;
+ }
+ }
+
+ return count($uniquePonies2) - count($uniquePonies1);
+ });
+
+ foreach ($sorted as $item): ?>
+ <a href="/-/toys/<?= $item["id"] ?>" id="action-<?= $item["id"] ?>" style="color:white !important;">
+ <div>
+ <span><?= $item["name"] ?></span>
+ </div>
+ </a>
+ <?php endforeach; ?>
+ </div>
+ </div>
+ <?php endif; ?>
+
+ <hr>
+ <b>Can be done by:</b>
+ <?php
+
+ $hasMultipleTypes = false;
+ $seenType = "";
+
+ foreach ($selected["ponies"] as $relation) {
+ if (isset($relation["sexual"]) && $relation["sexual"]) {
+ $type = "sexual";
+ } else {
+ $type = "affectionate";
+ }
+
+ if ($type !== $seenType && $seenType !== "") {
+ $hasMultipleTypes = true;
+ }
+
+ if (trim($seenType) === "") {
+ $seenType = $type;
+ }
+ }
+
+ ?>
+ <?php if ($hasMultipleTypes): ?>
+ <ul>
+ <li>
+ Affectionately:
+ <ul>
+ <?php foreach ($selected["ponies"] as $relation): if (!$relation["deprecated"] && (!isset($relation["sexual"]) || !$relation["sexual"])):
+
+ $member1 = getMemberWithoutSystem($relation["members"][0]);
+ $member2 = getMemberWithoutSystem($relation["members"][1]);
+
+ ?>
+ <li>
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member1["name"] ?>.png">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member2["name"] ?>.png">
+
+ <span style="vertical-align: middle;"><?= getMiniName($member1["display_name"] ?? $member1["name"]) ?> and <?= getMiniName($member2["display_name"] ?? $member2["name"]) ?></span>
+ </li>
+ <?php endif; endforeach; ?>
+ <?php foreach ($selected["ponies"] as $relation): if ($relation["deprecated"] && (!isset($relation["sexual"]) || !$relation["sexual"])):
+
+ $member1 = getMemberWithoutSystem($relation["members"][0]);
+ $member2 = getMemberWithoutSystem($relation["members"][1]);
+
+ ?>
+ <li>
+ <span style="opacity:.5;">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member1["name"] ?>.png">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member2["name"] ?>.png">
+
+ <span style="vertical-align: middle;"><?= getMiniName($member1["display_name"] ?? $member1["name"]) ?> and <?= getMiniName($member2["display_name"] ?? $member2["name"]) ?></span>
+ </span>
+ <span class="badge bg-danger rounded-pill">Deprecated</span>
+ </li>
+ <?php endif; endforeach; ?>
+ </ul>
+ </li>
+ <li>
+ Sexually:
+ <ul>
+ <?php foreach ($selected["ponies"] as $relation): if (!$relation["deprecated"] && (isset($relation["sexual"]) && $relation["sexual"])):
+
+ $member1 = getMemberWithoutSystem($relation["members"][0]);
+ $member2 = getMemberWithoutSystem($relation["members"][1]);
+
+ ?>
+ <li>
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member1["name"] ?>.png">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member2["name"] ?>.png">
+
+ <span style="vertical-align: middle;"><?= getMiniName($member1["display_name"] ?? $member1["name"]) ?> and <?= getMiniName($member2["display_name"] ?? $member2["name"]) ?></span>
+ </li>
+ <?php endif; endforeach; ?>
+ <?php foreach ($selected["ponies"] as $relation): if ($relation["deprecated"] && (isset($relation["sexual"]) && $relation["sexual"])):
+
+ $member1 = getMemberWithoutSystem($relation["members"][0]);
+ $member2 = getMemberWithoutSystem($relation["members"][1]);
+
+ ?>
+ <li>
+ <span style="opacity:.5;">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member1["name"] ?>.png">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member2["name"] ?>.png">
+
+ <span style="vertical-align: middle;"><?= getMiniName($member1["display_name"] ?? $member1["name"]) ?> and <?= getMiniName($member2["display_name"] ?? $member2["name"]) ?></span>
+ </span>
+ <span class="badge bg-danger rounded-pill">Deprecated</span>
+ </li>
+ <?php endif; endforeach; ?>
+ </ul>
+ </li>
+ </ul>
+ <?php else: ?>
+ <ul>
+ <?php foreach ($selected["ponies"] as $relation): if (!$relation["deprecated"]):
+
+ $member1 = getMemberWithoutSystem($relation["members"][0]);
+ $member2 = getMemberWithoutSystem($relation["members"][1]);
+
+ ?>
+ <li>
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member1["name"] ?>.png">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member2["name"] ?>.png">
+
+ <span style="vertical-align: middle;"><?= getMiniName($member1["display_name"] ?? $member1["name"]) ?> and <?= getMiniName($member2["display_name"] ?? $member2["name"]) ?></span>
+ </li>
+ <?php endif; endforeach; ?>
+ <?php foreach ($selected["ponies"] as $relation): if ($relation["deprecated"]):
+
+ $member1 = getMemberWithoutSystem($relation["members"][0]);
+ $member2 = getMemberWithoutSystem($relation["members"][1]);
+
+ ?>
+ <li>
+ <span style="opacity:.5;">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member1["name"] ?>.png">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member2["name"] ?>.png">
+
+ <span style="vertical-align: middle;"><?= getMiniName($member1["display_name"] ?? $member1["name"]) ?> and <?= getMiniName($member2["display_name"] ?? $member2["name"]) ?></span>
+ </span>
+ <span class="badge bg-danger rounded-pill">Deprecated</span>
+ </li>
+ <?php endif; endforeach; ?>
+ </ul>
+ <?php endif; ?>
+
+ <div style="margin-top:10px;"></div>
+
+ <?php $multipleExamples = count(explode(",", strip_tags($selected["example"]))) > 1; ?>
+ <b>Example<?= $multipleExamples ? "s" : "" ?>:</b><br>
+ <?php if (isset($selected["example"]) && trim($selected["example"]) !== ""): ?>
+ <?php if ($multipleExamples): ?>
+ <ul>
+ <?php foreach (explode(",", strip_tags($selected["example"])) as $example): ?>
+ <li><?= replaceKeyWords(trim($example)) ?></li>
+ <?php endforeach; ?>
+ </ul>
+ <?php else: ?>
+ <?= replaceKeyWords(strip_tags($selected["example"])) ?>
+ <?php endif; ?>
+ <?php else: ?>
+ <p><i>No example provided for this action. Enter edit mode to add an example to this action.</i></p>
+ <?php endif; ?>
+
+ <div style="margin-top:10px;"></div>
+
+ <b>Steps to reproduce in real life:</b><br>
+ <?php if (isset($selected["irl"]) && trim($selected["irl"]) !== ""): ?>
+ <?= replaceKeyWords(strip_tags($selected["irl"])) ?>
+ <?php else: ?>
+ <p><i>This action is not reproducible in real life.</i></p>
+ <?php endif; ?>
+
+ <hr>
+
+ <h4>Similar actions</h4>
+ <div class="row">
+ <?php
+
+ $names = [];
+ $currentName = $selected["name"];
+ $namesByDistance = [];
+
+ foreach ($data as $action) {
+ if ($action["name"] !== $currentName) $names[$action["name"]] = [
+ "id" => $action["id"],
+ "type" => $action["type"],
+ "ponies" => $action["ponies"]
+ ];
+ }
+
+ foreach ($names as $name => $data) {
+ if ($data["type"] === $selected["type"] || $selected["type"] !== "affectionate") {
+ $namesByDistance[] = [
+ "name" => $name,
+ "distance" => levenshtein($currentName, $name) + ((int)($data["type"] !== $selected["type"]) * 2),
+ "id" => $data["id"],
+ "type" => $data["type"],
+ "ponies" => $data["ponies"]
+ ];
+ }
+ }
+
+ uasort($namesByDistance, function ($a, $b) use ($selected) {
+ return $a["distance"] - $b["distance"];
+ });
+
+ foreach ($namesByDistance as $item) {
+ echo("<!-- " . $currentName . " <-> " . $item["name"] . " => " . $item["distance"] . " (artif: " . ((int)($item["type"] !== $selected["type"]) * 10) . ") -->");
+ }
+
+ $index = 0;
+ foreach ($namesByDistance as $item): if ($index < 3):
+ ?>
+ <div class="col-md-4" style="margin-bottom:20px;">
+ <a class="linked-card" href="/-/actions/<?= $item["id"] ?>"><div class="card">
+ <div class="card-body">
+ <h4 class="card-title"><?= $item["name"] ?></h4>
+ <p class="card-text">
+ <?php
+
+ $uniquePonies = [];
+
+ foreach ($item["ponies"] as $ponies) {
+ foreach ($ponies["members"] as $member) {
+ if (isset($uniquePonies[$member]) && !$uniquePonies[$member]) {
+ $uniquePonies[$member] = false;
+ } else {
+ $uniquePonies[$member] = $ponies["deprecated"];
+ }
+ }
+ }
+
+ foreach ($uniquePonies as $name => $deprecated): if (!$deprecated): ?>
+ <span data-bs-toggle="tooltip" title="<?= getMemberWithoutSystem($name)["display_name"] ?? getMemberWithoutSystem($name)["display_name"] ?>" style="display: inline-block;">
+ <img src="/assets/uploads/pt-<?= getMemberWithoutSystem($name)["name"] ?>.png" style="width:32px;">
+ </span>
+ <?php endif; endforeach; ?>
+ <?php foreach ($uniquePonies as $name => $deprecated): if ($deprecated): ?>
+ <span data-bs-toggle="tooltip" data-bs-html="true" title="<i><?= strip_tags(getMemberWithoutSystem($name)["display_name"] ?? getMemberWithoutSystem($name)["name"]) ?></i>" style="opacity:.5;display: inline-block;">
+ <img src="/assets/uploads/pt-<?= getMemberWithoutSystem($name)["name"] ?>.png" style="width:32px;">
+ </span>
+ <?php endif; endforeach; ?>
+ </p>
+ <?php if ($item["type"] === "affectionate"): ?>
+ <span style="" class="badge rounded-pill bg-primary">Affectionate</span>
+ <?php endif; ?>
+ <?php if ($item["type"] === "sexual"): ?>
+ <span style="" class="badge rounded-pill bg-danger">Sexual</span>
+ <?php endif; ?>
+ <?php if ($item["type"] === "mixed"): ?>
+ <span style="" class="badge rounded-pill bg-primary">Affectionate</span> <span style="" class="badge rounded-pill bg-danger">Sexual</span>
+ <?php endif; ?>
+ </div>
+ </div></a>
+ </div>
+ <?php $index++; endif; endforeach; ?>
+ </div>
+
+ <div class="modal fade" id="editor">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h4 class="modal-title">Edit action</h4>
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+
+ <div class="modal-body">
+ <form method="post" style="display:inline;">
+ <input type="text" placeholder="Action title" name="name" class="form-control" style="color:white;background:#111;border-color:#222;margin-bottom:10px;" value="<?= str_replace('"', '&quot;', $selected["name"]) ?>">
+ <select name="type" id="editor-type" class="form-select" style='color:white;background-color:#111;border-color:#222;background-image:url("data:image/svg+xml,%3csvg xmlns=&apos;http://www.w3.org/2000/svg&apos; viewBox=&apos;0 0 16 16&apos;%3e%3cpath fill=&apos;none&apos; stroke=&apos;%23ffffff&apos; stroke-linecap=&apos;round&apos; stroke-linejoin=&apos;round&apos; stroke-width=&apos;2&apos; d=&apos;M2 5l6 6 6-6&apos;/%3e%3c/svg%3e");margin-bottom:10px;' onchange="changeMixed();">
+ <option value="0" <?= $selected["type"] === "affectionate" ? "selected" : "" ?>>Affectionate</option>
+ <option value="1" <?= $selected["type"] === "sexual" ? "selected" : "" ?>>Sexual</option>
+ <option value="2" <?= $selected["type"] === "mixed" ? "selected" : "" ?>>Mixed</option>
+ </select>
+
+ <label style="margin-left:5px;">
+ <input <?= ($selected["consent"] ?? false) ? "checked" : "" ?> type="checkbox" name="consent">
+ Requires consent
+ </label><br>
+
+ <label style="margin-left:5px;">
+ <input <?= ($selected["verified"] ?? false) ? "checked" : "" ?> <?= $_PROFILE['login'] !== "raindrops" ? "disabled" : "" ?> type="checkbox" name="verified">
+ Mark as verified
+ </label><br>
+
+ <label style="margin-left:5px;">
+ <input <?= ($selected["untested"] ?? false) ? "checked" : "" ?> type="checkbox" name="untested">
+ Mark as untested
+ </label>
+
+ <hr>
+
+ <textarea name="description" class="form-control" style="resize: none;color:white;background:#111;border-color:#222;" placeholder="Description"><?= strip_tags($selected["description"] ?? "") ?></textarea>
+
+ <hr>
+
+ <input type="text" placeholder="Keywords (comma-separated)" name="keywords" class="form-control" style="color:white;background:#111;border-color:#222;margin-bottom:10px;" value="<?= str_replace('"', '&quot;', implode(",", $selected["keywords"] ?? [])) ?>">
+ <input type="text" placeholder="Action example" name="example" class="form-control" style="color:white;background:#111;border-color:#222;margin-bottom:10px;" value="<?= str_replace('"', '&quot;', $selected["example"] ?? "") ?>">
+ <input type="text" placeholder="IRL steps" name="irl" class="form-control" style="color:white;background:#111;border-color:#222;" value="<?= str_replace('"', '&quot;', $selected["irl"] ?? "") ?>">
+
+ <hr>
+
+ <p>Select the groups of ponies who can do this action:</p>
+ <?php
+
+ $members = scoreOrderGlobal();
+ $relations = [];
+
+ foreach ($members as $member) {
+ foreach ([
+ ...array_map(function ($i) {
+ $r = [
+ "name" => $i
+ ];
+ $r["type"] = "marefriends";
+ return $r;
+ }, $member["_metadata"]["marefriends"] ?? []),
+ ...array_map(function ($i) {
+ $r = [
+ "name" => $i
+ ];
+ $r["type"] = "sisters";
+ return $r;
+ }, $member["_metadata"]["sisters"] ?? []),
+ ...array_map(function ($i) {
+ $r = [
+ "name" => $i
+ ];
+ $r["type"] = "caretaking";
+ return $r;
+ }, $member["_metadata"]["caretakers"] ?? [])
+ ] as $rel) {
+ $id = $rel["name"];
+ $otherMember = getSystemMember(explode("/", $id)[0], explode("/", $id)[1]);
+
+ $parts = [
+ $member["id"],
+ $otherMember["id"]
+ ];
+
+ asort($parts);
+
+ $relations[implode("-", $parts)] = [
+ "name" => getMiniName($member["display_name"] ?? $member["name"]) . " and " . getMiniName($otherMember["display_name"] ?? $otherMember["name"]),
+ "type" => $rel["type"],
+ "images" => [
+ file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-$member[name].png") ? "/assets/uploads/pt-$member[name].png" : "/assets/uploads/pt.png",
+ file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-$otherMember[name].png") ? "/assets/uploads/pt-$otherMember[name].png" : "/assets/uploads/pt.png",
+ ]
+ ];
+ }
+ }
+
+ $inFileRelations = [];
+ $inFileDeprecations = [];
+ $inFileSexual = [];
+
+ foreach ($selected["ponies"] as $ponies) {
+ $inFileRelations[] = $ponies["members"][0] . "-" . $ponies["members"][1];
+
+ if (isset($ponies["deprecated"]) && $ponies["deprecated"]) $inFileDeprecations[] = $ponies["members"][0] . "-" . $ponies["members"][1];
+ if (isset($ponies["sexual"]) && $ponies["sexual"]) $inFileSexual[] = $ponies["members"][0] . "-" . $ponies["members"][1];
+ }
+
+ foreach ($relations as $id => $relation):
+ ?>
+ <label style="display:block;" class="creator-relation <?= $selected["type"] === "mixed" ? "mixed" : "" ?> <?= in_array($id, $inFileRelations) ? "checked" : "" ?>">
+ <input <?= in_array($id, $inFileRelations) ? "checked" : "" ?> name="relations[<?= $id ?>][member]" style="display:none;" type="checkbox" class="checkbox-input">
+
+ <img style="vertical-align: middle;width:24px;height:24px;" src="<?= $relation["images"][0] ?>">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="<?= $relation["images"][1] ?>">
+ <span style="vertical-align: middle;"><?= $relation["name"] ?></span>
+
+ <span style="float:right;margin-top:3px;" class="badge rounded-pill bg-<?= match ($relation["type"]) {
+ "marefriends" => "danger",
+ "sisters" => "success",
+ "caretaking" => "primary"
+ } ?>"><?= match ($relation["type"]) {
+ "marefriends" => "Marefriends",
+ "sisters" => "Sisters",
+ "caretaking" => "Caretaker"
+ } ?></span>
+
+ <label class="deprecated" style="display:none;margin-left: 20px;margin-top: 5px;">
+ <input <?= in_array($id, $inFileDeprecations) ? "checked" : "" ?> name="relations[<?= $id ?>][deprecated]" type="checkbox">
+ Mark as deprecated
+ </label>
+ <label class="sexual" style="display:none;margin-left: 20px;margin-top: 5px;">
+ <input <?= in_array($id, $inFileSexual) ? "checked" : "" ?> name="relations[<?= $id ?>][sexual]" type="checkbox">
+ Mark as sexual
+ </label>
+ </label>
+ <?php endforeach; ?>
+
+ <br>
+ <input type="hidden" name="updateAction">
+ <input type="hidden" name="action" value="<?= $selected["id"] ?>">
+ <input type="submit" class="btn btn-primary" value="Save">
+ </form>
+ <form method="post" style="display:inline;">
+ <input type="hidden" name="deleteAction">
+ <input type="hidden" name="action" value="<?= $selected["id"] ?>">
+ <input type="submit" class="btn btn-danger" value="Delete">
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <?php else: ?>
+
+ <h2>Actions database</h2>
+ <p><?= count($data) ?> actions (<?= count(array_filter($data, function ($i) {
+ return $i["type"] === "affectionate" || $i["type"] === "mixed";
+ })) ?> affectionate, <?= count(array_filter($data, function ($i) {
+ return $i["type"] === "sexual" || $i["type"] === "mixed";
+ })) ?> sexual, <?= count(array_filter($data, function ($i) {
+ return isset($i["untested"]) && $i["untested"];
+ })) ?> untested, <?= count(array_filter($data, function ($i) {
+ return (isset($i["description"]) && trim($i["description"]) === "") || !isset($i["description"]);
+ })) ?> incomplete)</p>
+
+ <p>TODO: add ponies for all actions (+ keywords)</p>
+
+ <input type="text" placeholder="Search for an action..." class="form-control" style="margin-bottom:15px;color:white;background:#111;border-color:#222;" onchange="search();" onkeydown="search();" onkeyup="search();" id="search">
+
+ <div id="list">
+ <div class="list-group">
+ <?php
+
+ $init = [];
+ foreach ($data as $value) {
+ $init[$value["name"]] = $value;
+ }
+
+ ksort($init);
+
+ $sorted = array_values($init);
+ uasort($sorted, function ($a, $b) {
+ $uniquePonies1 = [];
+
+ foreach ($a["ponies"] as $ponies) {
+ foreach ($ponies["members"] as $member) {
+ $uniquePonies1[$member] = true;
+ }
+ }
+
+ $uniquePonies2 = [];
+
+ foreach ($b["ponies"] as $ponies) {
+ foreach ($ponies["members"] as $member) {
+ $uniquePonies2[$member] = true;
+ }
+ }
+
+ return count($uniquePonies2) - count($uniquePonies1);
+ });
+
+ foreach ($sorted as $item): ?>
+ <a href="/-/actions/<?= $item["id"] ?>" id="action-<?= $item["id"] ?>" style="display:grid;grid-template-columns: 1fr 1fr 0.2fr;" class="list-group-item list-group-item-action action-listing">
+ <div>
+ <span class="<?= trim($item["description"]) === "" ? "text-danger" : "" ?>"><?= $item["name"] ?></span>
+ <?php if (!isset($item["verified"])): ?><span class="badge bg-warning rounded-pill text-black">Unverified</span><?php endif; ?>
+ <?php if (isset($item["untested"])): ?><span class="badge bg-info rounded-pill">Untested</span><?php endif; ?>
+ </div>
+ <div>
+ <?php
+
+ $uniquePonies = [];
+ $longPonyList = false;
+
+ foreach ($item["ponies"] as $ponies) {
+ foreach ($ponies["members"] as $member) {
+ if (isset($uniquePonies[$member]) && !$uniquePonies[$member]) {
+ $uniquePonies[$member] = false;
+ } else {
+ $uniquePonies[$member] = $ponies["deprecated"];
+ }
+ }
+ }
+
+ if (count($uniquePonies) > 6) {
+ $longPonyList = true;
+ }
+
+ $index = 1;
+ foreach ($uniquePonies as $name => $deprecated): if (!$deprecated): ?>
+ <!-- <?= $index ?> -->
+ <span title="<?= getMemberWithoutSystem($name)["display_name"] ?? getMemberWithoutSystem($name)["display_name"] ?>" style="display: inline-block;<?= $longPonyList && $index % 2 === 0 ? "position:absolute;margin-left:-12px;z-index:" . (999 - $index) . ";" : "position:relative;z-index:" . (999 - $index) . ";" ?>">
+ <img src="/assets/uploads/pt-<?= getMemberWithoutSystem($name)["name"] ?>.png" style="width:24px;">
+ </span>
+ <?php $index++; endif; endforeach; ?>
+ <?php foreach ($uniquePonies as $name => $deprecated): if ($deprecated): ?>
+ <span title="<?= strip_tags(getMemberWithoutSystem($name)["display_name"] ?? getMemberWithoutSystem($name)["name"]) ?> (deprecated)" style="opacity:.5;display: inline-block;<?= $longPonyList && $index % 2 === 0 ? "position:absolute;margin-left:-12px;z-index:" . (999 - $index) . ";" : "position:relative;z-index:" . (999 - $index) . ";" ?>">
+ <img src="/assets/uploads/pt-<?= getMemberWithoutSystem($name)["name"] ?>.png" style="width:24px;">
+ </span>
+ <?php $index++; endif; endforeach; ?>
+ </div>
+ <div style="text-align: right;">
+ <?php if ($item["type"] === "affectionate"): ?>
+ <span style="" class="badge rounded-pill bg-primary">Affectionate</span>
+ <?php endif; ?>
+ <?php if ($item["type"] === "sexual"): ?>
+ <span style="" class="badge rounded-pill bg-danger">Sexual</span>
+ <?php endif; ?>
+ <?php if ($item["type"] === "mixed"): ?>
+ <span style="" class="badge rounded-pill bg-success">Mixed</span>
+ <?php endif; ?>
+ </div>
+ </a>
+ <?php endforeach; ?>
+ </div>
+ </div>
+
+ <div id="search-results" class="list-group"></div>
+
+ <div id="page-content">
+ <hr>
+ <p>Not finding what you are looking for? <a onclick="event.target.blur(); document.getElementById('creator-title').focus();" href="#" data-bs-toggle="modal" data-bs-target="#creator">Create an action.</a></p>
+ </div>
+
+ <script>
+ window.actions = JSON.parse(atob(`<?= base64_encode(json_encode(array_values(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json"), true)))) ?>`));
+ </script>
+
+ <div class="modal fade" id="creator">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h4 class="modal-title">Create a new action</h4>
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+
+ <div class="modal-body">
+ <form method="post">
+ <input id="creator-title" type="text" placeholder="Action title" name="name" class="form-control" style="color:white;background:#111;border-color:#222;margin-bottom:10px;">
+ <select name="type" class="form-select" style='color:white;background-color:#111;border-color:#222;background-image:url("data:image/svg+xml,%3csvg xmlns=&apos;http://www.w3.org/2000/svg&apos; viewBox=&apos;0 0 16 16&apos;%3e%3cpath fill=&apos;none&apos; stroke=&apos;%23ffffff&apos; stroke-linecap=&apos;round&apos; stroke-linejoin=&apos;round&apos; stroke-width=&apos;2&apos; d=&apos;M2 5l6 6 6-6&apos;/%3e%3c/svg%3e");margin-bottom:10px;'>
+ <option value="0" selected>Affectionate</option>
+ <option value="1">Sexual</option>
+ <option value="2">Mixed</option>
+ </select>
+
+ <div class="alert alert-secondary" style="display:none;">
+ <p>The following actions might be the same as the one you are trying to add:</p>
+ <ul>
+ <li>Action 1</li>
+ <li>Action 2</li>
+ <li>Action 3</li>
+ </ul>
+ </div>
+
+ <label style="margin-left:5px;">
+ <input type="checkbox" name="consent">
+ Requires consent
+ </label>
+
+ <hr>
+
+ <p>Select the groups of ponies who can do this action:</p>
+ <?php
+
+ $members = scoreOrderGlobal();
+ $relations = [];
+
+ foreach ($members as $member) {
+ foreach ([
+ ...array_map(function ($i) {
+ $r = [
+ "name" => $i
+ ];
+ $r["type"] = "marefriends";
+ return $r;
+ }, $member["_metadata"]["marefriends"] ?? []),
+ ...array_map(function ($i) {
+ $r = [
+ "name" => $i
+ ];
+ $r["type"] = "sisters";
+ return $r;
+ }, $member["_metadata"]["sisters"] ?? []),
+ ...array_map(function ($i) {
+ $r = [
+ "name" => $i
+ ];
+ $r["type"] = "caretaking";
+ return $r;
+ }, $member["_metadata"]["caretakers"] ?? [])
+ ] as $rel) {
+ $id = $rel["name"];
+ $otherMember = getSystemMember(explode("/", $id)[0], explode("/", $id)[1]);
+
+ $parts = [
+ $member["id"],
+ $otherMember["id"]
+ ];
+
+ asort($parts);
+
+ $relations[implode("-", $parts)] = [
+ "name" => getMiniName($member["display_name"] ?? $member["name"]) . " and " . getMiniName($otherMember["display_name"] ?? $otherMember["name"]),
+ "type" => $rel["type"],
+ "images" => [
+ file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-$member[name].png") ? "/assets/uploads/pt-$member[name].png" : "/assets/uploads/pt.png",
+ file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-$otherMember[name].png") ? "/assets/uploads/pt-$otherMember[name].png" : "/assets/uploads/pt.png",
+ ]
+ ];
+ }
+ }
+
+ foreach ($relations as $id => $relation):
+ ?>
+ <label style="display:block;" class="creator-relation">
+ <input name="relations[<?= $id ?>]" style="display:none;" type="checkbox" class="checkbox-input">
+
+ <img style="vertical-align: middle;width:24px;height:24px;" src="<?= $relation["images"][0] ?>">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="<?= $relation["images"][1] ?>">
+ <span style="vertical-align: middle;"><?= $relation["name"] ?></span>
+
+ <span style="float:right;margin-top:3px;" class="badge rounded-pill bg-<?= match ($relation["type"]) {
+ "marefriends" => "danger",
+ "sisters" => "success",
+ "caretaking" => "primary"
+ } ?>"><?= match ($relation["type"]) {
+ "marefriends" => "Marefriends",
+ "sisters" => "Sisters",
+ "caretaking" => "Caretaker"
+ } ?></span>
+ </label>
+ <?php endforeach; ?>
+
+ <p style="margin-top:10px;">You can add additional data (description, example, how to do the action IRL) after creating the action.</p>
+ <input type="hidden" name="createAction">
+ <input type="submit" class="btn btn-primary" value="Create">
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <?php endif; ?>
+ </div>
+</div>
+
+<!--suppress JSUnresolvedFunction -->
+<script>
+ Array.from(document.getElementsByClassName("checkbox-input")).forEach((el) => {
+ el.onchange = () => {
+ let parent = el.parentElement;
+
+ if (el.checked) {
+ parent.classList.add("checked");
+ } else {
+ parent.classList.remove("checked");
+ }
+ }
+ });
+
+ const fuse = new Fuse(window.actions, {
+ includeScore: true,
+ keys: [
+ {
+ name: 'name',
+ weight: 1
+ },
+ {
+ name: 'description',
+ weight: 1
+ },
+ {
+ name: 'example',
+ weight: 0.7
+ },
+ {
+ name: 'irl',
+ weight: 0.5
+ }
+ ]
+ })
+
+ function search() {
+ let query = document.getElementById("search").value;
+ let results = fuse.search(query).map((i) => {
+ return {
+ id: i.item.id,
+ score: i.score
+ };
+ });
+
+ let unfiltered = results;
+
+ results = results.filter((i) => {
+ return i.score < 0.7;
+ });
+
+ console.log("Before:", unfiltered, "After:", results);
+
+ document.getElementById("list").style.display = "none";
+ document.getElementById("search-results").style.display = "block";
+ document.getElementById("search-results").innerHTML = "";
+
+ for (let result of results) {
+ document.getElementById("search-results").innerHTML += document.getElementById("action-" + result.id).outerHTML;
+ }
+
+ console.log(results);
+
+ if (query.trim() === "") {
+ document.getElementById("list").style.display = "block";
+ document.getElementById("search-results").style.display = "none";
+ }
+ }
+
+ function changeMixed() {
+ let value = document.getElementById("editor-type").value;
+ console.log(value);
+
+ if (value === "2") {
+ Array.from(document.getElementsByClassName("creator-relation")).forEach((el) => {
+ el.classList.add("mixed");
+ });
+ } else {
+ Array.from(document.getElementsByClassName("creator-relation")).forEach((el) => {
+ el.classList.remove("mixed");
+ });
+ }
+ }
+</script>
+
+<style>
+ .modal-header {
+ border-bottom: 1px solid #353738;
+ }
+
+ .modal-content {
+ border: 1px solid rgba(255, 255, 255, .2);
+ background-color: #111;
+ }
+
+ .btn-close {
+ filter: invert(1);
+ }
+
+ .creator-relation {
+ border-radius: 10px;
+ padding: 5px 10px;
+ opacity: .5;
+ }
+
+ .creator-relation.checked {
+ background-color: rgba(255, 255, 255, .1);
+ opacity: 1;
+ }
+
+ .creator-relation:hover {
+ background-color: rgba(255, 255, 255, .1);
+ }
+
+ .creator-relation.checked:hover {
+ background-color: rgba(255, 255, 255, .25) !important;
+ }
+
+ .creator-relation.checked .deprecated {
+ display: block !important;
+ }
+
+ .creator-relation.mixed.checked .sexual {
+ display: block !important;
+ }
+
+ .list-group-item {
+ color: #fff;
+ background-color: #222;
+ border: 1px solid rgba(255, 255, 255, .125);
+ }
+
+ .list-group-item.disabled {
+ color: #fff;
+ background-color: #222;
+ border-color: rgba(255, 255, 255, .125);
+ opacity: .75;
+ }
+
+ .list-group-item:hover {
+ background-color: #252525;
+ color: #ddd;
+ }
+
+ .list-group-item:active, .list-group-item:focus {
+ background-color: #272727;
+ color: #bbb;
+ }
+
+ @media (max-width: 991px) {
+ .action-listing {
+ grid-template-columns: 1fr !important;
+ text-align: center !important;
+ }
+
+ .action-listing > * {
+ margin-bottom: 10px;
+ text-align: center !important;
+ }
+
+ .action-listing > *:nth-last-child(1) {
+ margin-bottom: 0 !important;
+ }
+
+ .action-listing img {
+ width: 32px !important;
+ }
+ }
+
+ .toys-list-inner {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ background-color: #111111;
+ box-sizing: border-box;
+ grid-gap: 1px;
+ }
+
+ .toys-list-inner > * {
+ text-decoration: none !important;
+ box-shadow: 0 0 0 1px #2f2f2f;
+ padding: 5px 10px;
+ }
+
+ .toys-list-inner > *:nth-child(1) {
+ border-top-left-radius: 5px;
+ }
+
+ .toys-list-inner > *:nth-child(4) {
+ border-top-right-radius: 5px;
+ }
+
+ .toys-list-inner > *:nth-last-child(4) {
+ border-bottom-left-radius: 5px;
+ }
+
+ .toys-list-inner > *:nth-last-child(1) {
+ border-bottom-right-radius: 5px;
+ }
+</style>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?>
diff --git a/pages/bitset.php b/pages/bitset.php
index 11031e2..8c63bc5 100644
--- a/pages/bitset.php
+++ b/pages/bitset.php
@@ -1,7 +1,7 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
-if (!$isLoggedIn) header("Location: /login") and die();
+if (!$isLoggedIn) header("Location: /-/login") and die();
$title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
@@ -10,7 +10,7 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include
<br>
<div class="container">
<div id="page-content">
- <h2>Bitset Calculator</h2>
+ <h2>Bitset calculator</h2>
</div>
<div style="display:grid; grid-template-columns: repeat(48, 1fr);">
<div data-bs-toggle="tooltip" title="Value reserved for future use" id="binary-bit-1" class="font-monospace tooltip-nohelp text-muted" style="text-align: center;cursor: pointer;">0</div>
@@ -27,8 +27,8 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include
<div data-bs-toggle="tooltip" title="Value reserved for future use" id="binary-bit-12" class="font-monospace tooltip-nohelp text-muted" style="text-align: center;cursor: pointer;">0</div>
<div data-bs-toggle="tooltip" title="Value reserved for future use" id="binary-bit-13" class="font-monospace tooltip-nohelp text-muted" style="text-align: center;cursor: pointer;">0</div>
<div data-bs-toggle="tooltip" title="Value reserved for future use" id="binary-bit-14" class="font-monospace tooltip-nohelp text-muted" style="text-align: center;cursor: pointer;">0</div>
- <div data-bs-toggle="tooltip" title="Value reserved for future use" id="binary-bit-15" class="font-monospace tooltip-nohelp text-muted" style="text-align: center;cursor: pointer;">0</div>
- <div data-bs-toggle="tooltip" title="Value reserved for future use" id="binary-bit-16" class="font-monospace tooltip-nohelp text-muted" style="text-align: center;cursor: pointer;">0</div>
+ <div data-bs-toggle="tooltip" title="Fronts less frequently" id="binary-bit-15" class="font-monospace tooltip-nohelp" style="color: #198754;text-align: center;cursor: pointer;">0</div>
+ <div data-bs-toggle="tooltip" title="Non verbal in real life" id="binary-bit-16" class="font-monospace tooltip-nohelp" style="color: #20c997;text-align: center;cursor: pointer;">0</div>
<div data-bs-toggle="tooltip" title="Eatable food" id="binary-bit-17" class="font-monospace tooltip-nohelp" style="color: #d63384;text-align: center;cursor: pointer;">0</div>
<div data-bs-toggle="tooltip" title="Eatable food" id="binary-bit-18" class="font-monospace tooltip-nohelp" style="color: #d63384;text-align: center;cursor: pointer;">0</div>
<div data-bs-toggle="tooltip" title="Ability to use magic" id="binary-bit-19" class="font-monospace tooltip-nohelp" style="color: #fd7e14;text-align: center;cursor: pointer;">0</div>
@@ -108,6 +108,8 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include
let magic = parseInt(binString.substring(2 + 16, 5 + 16), 2);
let sensitivity = parseInt(binString.substring(5 + 16, 8 + 16), 2);
let ageSpells = binString.substring(31 + 16, 32 + 16) !== "0";
+ let nonverbal = binString.substring(15, 16) !== "0";
+ let lessFrequent = binString.substring(14, 15) !== "0";
document.getElementById("value-0").value = sharedMemory;
document.getElementById("value-1").value = little;
@@ -125,6 +127,8 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include
document.getElementById("value-13").value = sensitivity;
document.getElementById("value-14").value = species3;
document.getElementById("value-15").checked = ageSpells;
+ document.getElementById("value-16").checked = nonverbal;
+ document.getElementById("value-17").checked = lessFrequent;
}
}
@@ -171,6 +175,8 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include
let species2 = binString.substring(21 + 16, 25 + 16);
let species3 = binString.substring(25 + 16, 29 + 16);
let food = parseInt(binString.substring(16, 2 + 16), 2);
+ let nonverbal = binString.substring(15, 16) !== "0";
+ let lessFrequent = binString.substring(14, 15) !== "0";
let magic = parseInt(binString.substring(2 + 16, 5 + 16), 2);
let sensitivity = parseInt(binString.substring(5 + 16, 8 + 16), 2);
@@ -190,6 +196,8 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include
document.getElementById("value-10").checked = plush;
document.getElementById("value-14").value = species3;
document.getElementById("value-15").checked = ageSpells;
+ document.getElementById("value-16").checked = nonverbal;
+ document.getElementById("value-17").checked = lessFrequent;
calculateOutput();
@@ -213,6 +221,8 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include
let val13 = document.getElementById("value-13").value;
let val14 = document.getElementById("value-14").value;
let val15 = document.getElementById("value-15").checked;
+ let val16 = document.getElementById("value-16").checked;
+ let val17 = document.getElementById("value-17").checked;
let val0bin = parseInt(val0).toString(2);
val0bin = val0bin.length === 1 ? "0" + val0bin : val0bin;
@@ -241,8 +251,10 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include
let val9bin = val9 ? "1" : "0";
let val10bin = val10 ? "1" : "0";
let val15bin = val15 ? "1" : "0";
+ let val16bin = val16 ? "1" : "0";
+ let val17bin = val17 ? "1" : "0";
- let bin = "0000000000000000" + val11bin + val12bin + val13bin + val0bin + val4bin + val1bin + val5bin + val6bin + val7bin + val8bin + val2bin + val3bin + val14bin + val9bin + val10bin + val15bin;
+ let bin = "00000000000000" + val17bin + val16bin + val11bin + val12bin + val13bin + val0bin + val4bin + val1bin + val5bin + val6bin + val7bin + val8bin + val2bin + val3bin + val14bin + val9bin + val10bin + val15bin;
console.log(bin, parseInt(bin, 2));
@@ -364,7 +376,15 @@ $title = "Bitset calculator"; require_once $_SERVER['DOCUMENT_ROOT'] . '/include
<label style="margin-bottom:5px;">
<input type="checkbox" id="value-15" onchange="updateFromSelection();">
Affected by age spells
- </label><img alt="" src="/assets/icons/visibility-private.svg" style="filter:invert(1);width:24px;margin-left:5px;margin-top:-5px;" title="This information will remain private" data-bs-toggle="tooltip">
+ </label><img alt="" src="/assets/icons/visibility-private.svg" style="filter:invert(1);width:24px;margin-left:5px;margin-top:-5px;" title="This information will remain private" data-bs-toggle="tooltip"><br>
+ <label style="margin-bottom:5px;">
+ <input type="checkbox" id="value-16" onchange="updateFromSelection();">
+ Non verbal in real life
+ </label><img alt="" src="/assets/icons/visibility-public.svg" style="filter:invert(1);width:24px;margin-left:5px;margin-top:-5px;" title="This information will be shown publicly" data-bs-toggle="tooltip"><br>
+ <label style="margin-bottom:5px;">
+ <input type="checkbox" id="value-17" onchange="updateFromSelection();">
+ Fronts less frequently
+ </label><img alt="" src="/assets/icons/visibility-public.svg" style="filter:invert(1);width:24px;margin-left:5px;margin-top:-5px;" title="This information will be shown publicly" data-bs-toggle="tooltip">
</p>
</div>
diff --git a/pages/dashboard.php b/pages/dashboard.php
new file mode 100644
index 0000000..622cd20
--- /dev/null
+++ b/pages/dashboard.php
@@ -0,0 +1,299 @@
+<?php
+
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
+if (!$isLoggedIn) header("Location: /-/login") and die();
+
+$title = "Dashboard"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php'; global $_PROFILE;
+use om\IcalParser;
+
+$poniesHavingSex = [];
+
+$actions = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json"), true);
+
+foreach ($actions as $action) {
+ if ($action["type"] !== "sexual") continue;
+
+ foreach ($action["ponies"] as $ponies) {
+ $id = implode("", $ponies["members"]);
+
+ $member = getMemberWithoutSystem($ponies["members"][0]);
+ $otherMember = getMemberWithoutSystem($ponies["members"][1]);
+
+ $parts = [
+ $member["id"],
+ $otherMember["id"]
+ ];
+
+ asort($parts);
+ $poniesHavingSex[] = $parts[0];
+ $poniesHavingSex[] = $parts[1];
+ }
+}
+
+$fronter = array_map(function ($i) {
+ return $i["id"];
+}, ($_PROFILE["login"] === "raindrops" ? json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd-fronters.json"), true) : json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc-fronters.json"), true))["members"])[0];
+
+$viewingPonyHasSex = in_array($fronter, $poniesHavingSex) || isset($_GET['toys']);
+
+?>
+
+<br>
+<div class="container">
+ <div id="page-content">
+ <h3 id="date" style="text-align: center;margin-bottom:20px;">Date</h3>
+ <script>
+ function _Date() {
+ let d = new Date();
+ let m = d.getMonth();
+ let w = d.getDay();
+
+ switch (m) {
+ case 0: m = "Jan"; break;
+ case 1: m = "Feb"; break;
+ case 2: m = "Mar"; break;
+ case 3: m = "Apr"; break;
+ case 4: m = "May"; break;
+ case 5: m = "Jun"; break;
+ case 6: m = "Jul"; break;
+ case 7: m = "Aug"; break;
+ case 8: m = "Sep"; break;
+ case 9: m = "Oct"; break;
+ case 10: m = "Nov"; break;
+ case 11: m = "Dec"; break;
+ }
+
+ switch (w) {
+ case 0: w = "Sun"; break;
+ case 1: w = "Mon"; break;
+ case 2: w = "Tue"; break;
+ case 3: w = "Wed"; break;
+ case 4: w = "Thu"; break;
+ case 5: w = "Fri"; break;
+ case 6: w = "Sat"; break;
+ }
+
+ function fixed(number) {
+ if (number < 10) {
+ return "0" + number;
+ } else {
+ return number.toString();
+ }
+ }
+
+ document.getElementById("date").innerText = `${w} ${d.getDate()} ${m} ${d.getHours()}:${fixed(d.getMinutes())}`;
+ }
+
+ setInterval(_Date);
+ </script>
+
+ <div class="peh-row" style="<?php if (!$viewingPonyHasSex): ?>grid-template-columns: repeat(6, 1fr) !important;<?php endif; ?>">
+ <div class="column">
+ <div class="card">
+ <a href="/-/actions" class="card-body" style="text-align:center;color: white !important; text-decoration: none !important;">
+ <img src="/assets/icons/actions.svg" style="width:32px;height:32px;filter:invert(1);"><br>
+ <h6 class="app-name">Actions</h6>
+ </a>
+ </div>
+ </div>
+ <div class="column">
+ <div class="card">
+ <a href="/-/rules" class="card-body" style="text-align:center;color: white !important; text-decoration: none !important;">
+ <img src="/assets/icons/rules.svg" style="width:32px;height:32px;filter:invert(1);"><br>
+ <h6 class="app-name">Rules</h6>
+ </a>
+ </div>
+ </div>
+ <div class="column">
+ <div class="card">
+ <a href="/-/nicknames" class="card-body" style="text-align:center;color: white !important; text-decoration: none !important;">
+ <img src="/assets/icons/nicknames.svg" style="width:32px;height:32px;filter:invert(1);"><br>
+ <h6 class="app-name">Nicknames</h6>
+ </a>
+ </div>
+ </div>
+ <div class="column">
+ <div class="card">
+ <a href="/-/together" class="card-body" style="text-align:center;color: white !important; text-decoration: none !important;">
+ <img src="/assets/icons/together.svg" style="width:32px;height:32px;filter:invert(1);"><br>
+ <h6 class="app-name">Watch Together</h6>
+ </a>
+ </div>
+ </div>
+ <div class="column">
+ <div class="card">
+ <a href="/-/splitting" class="card-body" style="text-align:center;color: white !important; text-decoration: none !important;">
+ <img src="/assets/icons/form.svg" style="width:32px;height:32px;filter:invert(1);"><br>
+ <h6 class="app-name">Splits</h6>
+ </a>
+ </div>
+ </div>
+ <div class="column">
+ <div class="card">
+ <a href="/-/bitset" class="card-body" style="text-align:center;color: white !important; text-decoration: none !important;">
+ <img src="/assets/icons/bitset.svg" style="width:32px;height:32px;filter:invert(1);"><br>
+ <h6 class="app-name">Bitset</h6>
+ </a>
+ </div>
+ </div>
+ <?php if ($viewingPonyHasSex): ?>
+ <div class="column">
+ <div class="card">
+ <a href="/-/toys" class="card-body" style="text-align:center;color: white !important; text-decoration: none !important;">
+ <img src="/assets/icons/toys.svg" style="width:32px;height:32px;filter:invert(1);"><br>
+ <h6 class="app-name">Toys</h6>
+ </a>
+ </div>
+ </div>
+ <?php endif; ?>
+ </div>
+
+ <hr>
+ <h4>Next fronters <span class="small"><a href="/-/fronting">(edit)</a></span></h4>
+ <?php
+
+ $scheduleCloudburstToday = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc-planner.json"), true)[date('Y-m-d')] ?? [];
+ $scheduleRaindropsToday = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd-planner.json"), true)[date('Y-m-d')] ?? [];
+
+ $scheduleCloudburstTomorrow = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc-planner.json"), true)[date('Y-m-d', time() + 86400)] ?? [];
+ $scheduleRaindropsTomorrow = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd-planner.json"), true)[date('Y-m-d', time() + 86400)] ?? [];
+
+ $today1 = $_PROFILE["login"] === "raindrops" ? $scheduleRaindropsToday : $scheduleCloudburstToday;
+ $today2 = $_PROFILE["login"] === "raindrops" ? $scheduleCloudburstToday : $scheduleRaindropsToday;
+ $tomorrow1 = $_PROFILE["login"] === "raindrops" ? $scheduleRaindropsTomorrow : $scheduleCloudburstTomorrow;
+ $tomorrow2 = $_PROFILE["login"] === "raindrops" ? $scheduleCloudburstTomorrow : $scheduleRaindropsTomorrow;
+
+ ?>
+
+ <h5>Fronters today</h5>
+ <ul>
+ <?php foreach ($today1 as $index => $id): $member = getSystemMember($_PROFILE["login"] === "raindrops" ? "gdapd" : "ynmuc", $id); ?>
+ <li>
+ <img alt="" src="/assets/uploads/pt-<?= $member["name"] ?>.png" style="width:24px; height: 24px; vertical-align: middle;"> <b style="vertical-align: middle;"><?= getMiniName($member["display_name"] ?? $member["name"]) ?></b>
+ <?php if (isset($today2[$index])): $member2 = getSystemMember($_PROFILE["login"] === "raindrops" ? "ynmuc" : "gdapd", $today2[$index]); ?><span style="vertical-align: middle;">with</span> <img alt="" src="/assets/uploads/pt-<?= $member2["name"] ?>.png" style="width:24px; height: 24px; vertical-align: middle;"> <span style="vertical-align: middle;"><?= getMiniName($member2["display_name"] ?? $member2["name"]) ?></span><?php endif; ?>
+ </li>
+ <?php endforeach; ?>
+ </ul>
+
+ <h5>Fronters tomorrow</h5>
+ <ul>
+ <?php foreach ($tomorrow1 as $index => $id): $member = getSystemMember($_PROFILE["login"] === "raindrops" ? "gdapd" : "ynmuc", $id); ?>
+ <li>
+ <img alt="" src="/assets/uploads/pt-<?= $member["name"] ?>.png" style="width:24px; height: 24px; vertical-align: middle;"> <b style="vertical-align: middle;"><?= getMiniName($member["display_name"] ?? $member["name"]) ?></b>
+ <?php if (isset($tomorrow2[$index])): $member2 = getSystemMember($_PROFILE["login"] === "raindrops" ? "ynmuc" : "gdapd", $tomorrow2[$index]); ?><span style="vertical-align: middle;">with</span> <img alt="" src="/assets/uploads/pt-<?= $member2["name"] ?>.png" style="width:24px; height: 24px; vertical-align: middle;"> <span style="vertical-align: middle;"><?= getMiniName($member2["display_name"] ?? $member2["name"]) ?></span><?php endif; ?>
+ </li>
+ <?php endforeach; ?>
+ </ul>
+
+ <hr>
+ <h4>Next events</h4>
+ <?php
+
+ $cal = new IcalParser();
+ $results = $cal->parseFile($_SERVER['DOCUMENT_ROOT'] . "/includes/data/calendar.ics");
+ $events = [];
+
+ foreach ($cal->getEvents()->sorted() as $event) {
+ $events[] = [
+ "id" => $event['UID'],
+ "date" => [
+ "created" => $event['CREATED']->format('c'),
+ "modified" => $event['LAST-MODIFIED']->format('c'),
+ "start" => $event['DTSTART']->format('c'),
+ "end" => $event['DTEND']->format('c'),
+ "duration" => strtotime($event['DTEND']->format('c')) - strtotime($event['DTSTART']->format('c')),
+ "full_day" => strtotime($event['DTEND']->format('c')) - strtotime($event['DTSTART']->format('c')) >= 86400,
+ "days" => (
+ strtotime($event['DTEND']->format('c')) - strtotime($event['DTSTART']->format('c')) >= 86400 ?
+ round((strtotime($event['DTEND']->format('c')) - strtotime($event['DTSTART']->format('c'))) / 86400) :
+ null
+ )
+ ],
+ "name" => $event['SUMMARY'],
+ "description" => $event['DESCRIPTION'],
+ ];
+ }
+
+ $events = array_values(array_filter($events, function ($i) {
+ return (
+ strtotime($i["date"]["end"]) > time() &&
+ strtotime($i["date"]["start"]) < time() + 2629800
+ );
+ }));
+
+ ?>
+ <ul>
+ <?php foreach ($events as $event): ?>
+ <li>
+ <?php if ($event["date"]["full_day"]): ?>
+ <b>
+ <?php if (strtotime($event["date"]["start"]) < time()): ?>
+ Started <?= timeAgo($event["date"]["start"]) ?>, ends <?= timeIn($event["date"]["end"]) ?>
+ <?php else: ?>
+ <?= ucfirst(relativeDate($event["date"]["start"], false)) ?>
+ <?php endif; ?>
+ ·
+ </b>
+ <?php else: ?>
+ <b>
+ <?= ucfirst(relativeDate($event["date"]["start"])) ?>
+ ·
+ </b>
+ <?php endif; ?>
+ for <?= duration($event["date"]["duration"]) ?>
+ ·
+ <?= $event["name"] ?>
+ </li>
+ <?php endforeach; ?>
+ </ul>
+ </div>
+</div>
+
+<style>
+ .peh-row {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ }
+
+ .column .card {
+ border-right: none !important;
+ border-radius: 0 !important;
+ }
+
+ .column:nth-child(1) .card {
+ border-bottom-left-radius: 0.25rem !important;
+ border-top-left-radius: 0.25rem !important;
+ }
+
+ .column:nth-last-child(1) .card {
+ border-right: 1px solid rgba(255, 255, 255, .125) !important;
+ border-bottom-right-radius: 0.25rem !important;
+ border-top-right-radius: 0.25rem !important;
+ }
+
+ @media (max-width: 1199px) {
+ .app-name {
+ display: none;
+ }
+ }
+
+ @media (max-width: 991px) {
+ .column {
+ padding-right: calc(var(--bs-gutter-x) * .25);
+ padding-left: calc(var(--bs-gutter-x) * .25);
+ }
+ }
+
+ @media (max-width: 576px) {
+ .card-body {
+ padding: 0.5rem 0.5rem !important;
+ }
+
+ .column {
+ padding-right: calc(var(--bs-gutter-x) * .175) !important;
+ padding-left: calc(var(--bs-gutter-x) * .175) !important;
+ }
+ }
+</style>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?>
diff --git a/pages/debug.php b/pages/debug.php
new file mode 100644
index 0000000..338a896
--- /dev/null
+++ b/pages/debug.php
@@ -0,0 +1,91 @@
+<?php $title = "Data updater debugging"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+
+function itemToName(string $item): string {
+ if ($item === "restore") return "Failure protection";
+ if ($item === "backups") return "Encrypted off-site backups";
+ if ($item === "calendar") return "Google Calendar integration";
+
+ if (str_starts_with($item, "system-")) {
+ $system = explode("-", $item)[2];
+ $type = explode("-", $item)[1];
+ $systemName = $system === "gdapd" ? "Raindrops System" : "Cloudburst System";
+
+ return match ($type) {
+ "general" => "General info about $systemName",
+ "members" => "Members in the $systemName",
+ "fronters" => "Current fronter(s) in the $systemName",
+ "switches" => "Switch history from the $systemName",
+ default => "$type in $systemName",
+ };
+ }
+
+ if (str_starts_with($item, "images-")) {
+ $system = explode("-", $item)[1];
+ $id = explode("-", $item)[2];
+ $systemName = $system === "gdapd" ? "Raindrops" : "Cloudburst";
+ $member = getSystemMember($system, $id) ?? [ "name" => $id, "display_name" => $id, "id" => $id ];
+
+ if ($member["name"] === "unknown") {
+ return "Unknown (" . $systemName . ")'s images";
+ } else {
+ return getMiniName($member["display_name"] ?? $member["name"]) . "'s images";
+ }
+ }
+
+ return "<code>$item</code>";
+}
+
+?>
+
+<br>
+<div class="container">
+ <?php $data = json_decode(file_get_contents($_SERVER["DOCUMENT_ROOT"] . "/includes/data/refresh.json"), true); ?>
+
+ <div id="page-content">
+ <h2>Data updater debugging</h2>
+ <p>This page provides debugging information to troubleshoot unexpectedly long update times or reported failures.</p>
+
+ <h4>General information</h4>
+ <ul>
+ <li><b>Update date:</b> <?php $dt = DateTime::createFromFormat('U.u', $data["timestamp"]); echo($dt->format("l j F Y, G:i:s.u T")); ?></li>
+ <li><b>Total duration:</b> <?= round($data["duration"] * 1000) ?> ms</li>
+ <li><b>Longest operation:</b> <?= itemToName(array_search(max(array_values($data["times"])), $data["times"])) ?? "-" ?> (<?= round(max(array_values($data["times"])) * 1000) ?> ms, <?= round((max(array_values($data["times"])) / $data["duration"]) * 100, 2) ?>%)</li>
+ </ul>
+
+ <h4>Processing times</h4>
+ <div>
+ <?php foreach ($data["times"] as $item => $time): ?><span class="element tooltip-nohelp" title="<b><?= itemToName($item) ?></b><br>(<?= round($time * 1000) ?> ms)" data-bs-toggle="tooltip" data-bs-html="true" style="opacity:.75;display:inline-block;background:#<?= substr(md5($item), 0, 6) ?>;height:8px;margin:4px 0;width:<?= ($time / $data["duration"]) * 100 ?>%"></span><?php endforeach; ?>
+ </div>
+ <ul>
+ <?php foreach ($data["times"] as $item => $time): ?>
+ <li><b><?= itemToName($item) ?>:</b> <?= round($time * 1000) ?> ms</li>
+ <?php endforeach; ?>
+ </ul>
+
+ <h4>Reported failures</h4>
+ <?php if (count($data["restored"]) < 1): ?>
+ <p><i>The data updater has not reported any update failure in the last run.</i></p>
+ <?php else: ?>
+ <p>The following files have failed to update:</p>
+ <ul>
+ <?php foreach ($data["restored"] as $item): ?>
+ <li><code><?= $item ?></code></li>
+ <?php endforeach; ?>
+ </ul>
+ <?php endif; ?>
+ </div>
+</div>
+
+<style>
+ .element:hover {
+ margin: 0 !important;
+ height: 16px !important;
+ opacity: 1 !important;
+ }
+
+ .element {
+ transition: margin 200ms, height 200ms, opacity 200ms;
+ }
+</style>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?>
diff --git a/pages/edit.php b/pages/edit.php
index 74105e2..ae007be 100644
--- a/pages/edit.php
+++ b/pages/edit.php
@@ -14,7 +14,7 @@ function getSubsystemByID(string $id) {
}
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
-if (!$isLoggedIn) header("Location: /login") and die();
+if (!$isLoggedIn) header("Location: /-/login") and die();
if (!isset($_GET['_']) || trim($_GET['_']) === "") header("Location: /?error=Invalid request") and die();
diff --git a/pages/emergency.php b/pages/emergency.php
index 0405bca..ba2a35e 100644
--- a/pages/emergency.php
+++ b/pages/emergency.php
@@ -1,7 +1,7 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
-if (!$isLoggedIn) header("Location: /login") and die();
+if (!$isLoggedIn) header("Location: /-/login") and die();
$emergencyHeader = true;
$title = "Emergency alert"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
diff --git a/pages/fronting.php b/pages/fronting.php
index eae0b61..261cf03 100644
--- a/pages/fronting.php
+++ b/pages/fronting.php
@@ -1,4 +1,4 @@
-<?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn; if (!$isLoggedIn) header("Location: /login") and die(); $title = "Front planner"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php'; ?>
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn; if (!$isLoggedIn) header("Location: /-/login") and die(); $title = "Front planner"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php'; ?>
<br>
<div class="container">
diff --git a/pages/nicknames.php b/pages/nicknames.php
new file mode 100644
index 0000000..050c275
--- /dev/null
+++ b/pages/nicknames.php
@@ -0,0 +1,117 @@
+<?php
+
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
+if (!$isLoggedIn) header("Location: /-/login") and die();
+
+$title = "Relations nicknames"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+
+$members = scoreOrderGlobal();
+$relations = [];
+
+foreach ($members as $member) {
+ foreach ([
+ ...array_map(function ($i) {
+ $r = [
+ "name" => $i
+ ];
+ $r["type"] = "marefriends";
+ return $r;
+ }, $member["_metadata"]["marefriends"] ?? []),
+ ...array_map(function ($i) {
+ $r = [
+ "name" => $i
+ ];
+ $r["type"] = "sisters";
+ return $r;
+ }, $member["_metadata"]["sisters"] ?? []),
+ ...array_map(function ($i) {
+ $r = [
+ "name" => $i
+ ];
+ $r["type"] = "caretaking";
+ return $r;
+ }, $member["_metadata"]["caretakers"] ?? [])
+ ] as $rel) {
+ $id = $rel["name"];
+ $otherMember = getSystemMember(explode("/", $id)[0], explode("/", $id)[1]);
+
+ $parts = [
+ $member["id"],
+ $otherMember["id"]
+ ];
+
+ asort($parts);
+
+ $relations[implode("-", $parts)] = [
+ "id" => implode("", $parts),
+ "name" => getMiniName($member["display_name"] ?? $member["name"]) . " and " . getMiniName($otherMember["display_name"] ?? $otherMember["name"]),
+ "type" => $rel["type"],
+ "images" => [
+ file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-$member[name].png") ? "/assets/uploads/pt-$member[name].png" : "/assets/uploads/pt.png",
+ file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-$otherMember[name].png") ? "/assets/uploads/pt-$otherMember[name].png" : "/assets/uploads/pt.png",
+ ]
+ ];
+ }
+}
+
+$nicknames = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/nicknames.json"), true);
+
+?>
+
+<br>
+<div class="container">
+ <div id="page-content">
+ <h2>Relations nicknames</h2>
+ <?php foreach ($relations as $relation): ?>
+ <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 3fr;">
+ <a class="relation-intro" title="<?= $relation["id"] ?>" data-bs-toggle="tooltip" 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;">
+ <span style="vertical-align: middle;"><img src="<?= $relation["images"][0] ?>" style="width:24px;"><img src="<?= $relation["images"][1] ?>" style="width:24px;"></span> <span style="vertical-align: middle;"><?= $relation["name"] ?></span>
+ </a>
+ <div class="relation-item relation-item-marefriends" style="text-align:left;margin-left:10px;padding:0 20px;">
+ <?php if (isset($nicknames[$relation["id"]])): ?>
+ "<?= implode('", "', $nicknames[$relation["id"]]) ?>"
+ <?php else: ?>
+ <span class="text-muted">No nickname for this relation</span>
+ <?php endif; ?>
+ </div>
+ </div>
+ <?php endforeach; ?>
+ </div>
+</div>
+
+<style>
+ .relation-intro {
+ opacity: 1 !important;
+ }
+
+ @media (max-width: 991px) {
+ .relation {
+ grid-template-columns: 1fr !important;
+ }
+
+ .relation-intro {
+ text-align: center;
+ border-bottom-left-radius: 0 !important;
+ border-top-right-radius: 10px;
+ border-right: none !important;
+ border-bottom: 1px solid rgba(255, 255, 255, .1);
+ }
+
+ .relation-item-marefriends {
+ margin-top: 20px !important;
+ text-align: center !important;
+ }
+
+ .relation-item {
+ margin-top: 10px;
+ margin-left: 0 !important;
+ padding: 10px 0 !important;
+ }
+ }
+
+ .relation-item {
+ text-align: center;
+ }
+</style>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?>
diff --git a/pages/parser.php b/pages/parser.php
index cb07179..b49fd00 100644
--- a/pages/parser.php
+++ b/pages/parser.php
@@ -3,7 +3,7 @@
<br>
<div class="container">
<div id="page-content">
- <h2>Message Parser</h2>
+ <h2>Message parser</h2>
<p>Enter a message here, and we will tell you which system member this message came from.</p>
<div style="display:grid;grid-template-columns:1fr 2fr;grid-gap:10px;">
diff --git a/pages/pleasure.php b/pages/pleasure.php
index db63f5a..295e597 100644
--- a/pages/pleasure.php
+++ b/pages/pleasure.php
@@ -1,7 +1,7 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
-if (!$isLoggedIn) header("Location: /login") and die();
+if (!$isLoggedIn) header("Location: /-/login") and die();
$emergencyHeader = true;
$title = "Pleasure alert"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
diff --git a/pages/prefix.php b/pages/prefix.php
index 73e9c8a..cba68b2 100644
--- a/pages/prefix.php
+++ b/pages/prefix.php
@@ -3,7 +3,7 @@
<br>
<div class="container">
<div id="page-content">
- <h2>Prefix Generator</h2>
+ <h2>Prefix generator</h2>
<p>This prefix generator will take into account the prefixes from the existing members to try to generate new original prefixes for a potential new member.</p>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;grid-gap:10px;">
diff --git a/pages/rules.php b/pages/rules.php
new file mode 100644
index 0000000..b1759dc
--- /dev/null
+++ b/pages/rules.php
@@ -0,0 +1,254 @@
+<?php
+
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
+if (!$isLoggedIn) header("Location: /-/login") and die();
+
+if (isset($_POST["updateRules"])) {
+ header("Content-Type: text/plain");
+
+ if (!isset($_POST['payload'])) {
+ header("Location: /-/rules");
+ die();
+ }
+
+ foreach ($_POST['payload'] as $index => $rule) {
+ if (!isset($rule["name"]) || !isset($rule["content"]) && !is_numeric($index)) {
+ header("Location: /-/rules");
+ die();
+ }
+
+ if (trim($rule["name"]) === "") {
+ unset($_POST["payload"][$index]);
+ continue;
+ }
+
+ if (!isset($rule["approved"])) $rule["approved"] = [];
+ if (isset($rule["approved"][0])) $rule["approved"][0] = true; else $rule["approved"][0] = false;
+ if (isset($rule["approved"][1])) $rule["approved"][1] = true; else $rule["approved"][1] = false;
+
+ $_POST["payload"][$index] = $rule;
+ }
+
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/rules.json", utf8_encode(json_encode($_POST["payload"])));
+
+ header("Location: /-/rules");
+ die();
+}
+
+$title = "Systems rules"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+
+?>
+
+<br>
+<div class="container">
+ <div id="page-content">
+ <h2>Systems rules</h2>
+ <p>Click on a rule to view additional details. <a onclick="event.target.blur();" href="#" data-bs-toggle="modal" data-bs-target="#editor">Edit rules</a></p>
+
+ <?php
+
+ $rules = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/rules.json"), true);
+
+ $protectorCloudburst = array_values(array_filter(scoreOrderGlobal(), function ($i) {
+ return $i["_system"] === "ynmuc" && $i["_metadata"]["protector"];
+ }))[0];
+ $protectorRaindrops = array_values(array_filter(scoreOrderGlobal(), function ($i) {
+ return $i["_system"] === "gdapd" && $i["_metadata"]["protector"];
+ }))[0];
+
+ $pcName = getMiniName($protectorCloudburst["display_name"] ?? $protectorCloudburst["name"]);
+ $prName = getMiniName($protectorRaindrops["display_name"] ?? $protectorRaindrops["name"]);
+
+ ?>
+
+ <ul class="list-group">
+ <?php $index = 1; foreach ($rules as $rule): ?>
+ <li class="list-group-item rule-outer">
+ <details>
+ <summary class="rule">
+ <b><?= $index ?>. <?= strip_tags($rule["name"]) ?></b>
+ <?php if (in_array(false, $rule["approved"])): ?>
+ <span class="badge bg-warning text-black rounded-pill">Unapproved</span>
+ <?php endif; ?>
+ </summary>
+ <?php if (in_array(false, $rule["approved"])): ?>
+ <div style="margin-top:10px;" class="alert alert-warning">
+ <b>This rule has not yet been approved.</b> All rules need to be approved by the protectors from both systems. This rule is still missing approval from <?php
+
+ if ($rule["approved"][0] === false) {
+ if ($rule["approved"][1] === false) {
+ echo($pcName . " and " . $prName);
+ } else {
+ echo($pcName);
+ }
+ } else if ($rule["approved"][1] === false) {
+ echo($prName);
+ }
+
+ ?>.
+ </div>
+ <?php endif; ?>
+ <div <?= !in_array(false, $rule["approved"]) ? 'style="margin-top:10px;"' : '' ?> class="list-group-item">
+ <?= strip_tags($rule["content"]) ?>
+ </div>
+ </details>
+ </li>
+ <?php $index++; endforeach; ?>
+ </ul>
+ </div>
+</div>
+
+<div class="modal fade" id="editor">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h4 class="modal-title">Rules editor</h4>
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+
+ <div class="modal-body">
+ <p>Rules with an empty name are automatically deleted. Buttons to add new rules and save the changes are at the bottom of the list.</p>
+ <hr>
+ <form method="post">
+ <div id="rules-editor">
+ <?php $index = 1; foreach ($rules as $rule): ?>
+ <div <?= $index === 1 ? 'id="default-rule"' : '' ?>>
+ <p><b <?= $index === 1 ? 'id="default-rule--number"' : '' ?>>Rule #<?= $index ?>:</b></p>
+ <input <?= $index === 1 ? 'id="default-rule--name"' : '' ?> type="text" placeholder="Rule name" class="form-control" style="margin-bottom:15px;color:white;background:#111;border-color:#222;" name="payload[<?= $index - 1 ?>][name]" value="<?= str_replace('"', "&quot;", strip_tags($rule["name"])) ?>">
+
+ <textarea <?= $index === 1 ? 'id="default-rule--content"' : '' ?> name="payload[<?= $index - 1 ?>][content]" class="form-control" style="resize: none;color:white;background:#111;border-color:#222;" placeholder="Rule details"><?= strip_tags($rule["content"] ?? "") ?></textarea>
+
+ <label style="margin-top:10px;margin-left:5px;">
+ <input <?= $index === 1 ? 'id="default-rule--approval-1"' : '' ?> <?= ($rule["approved"][0] ?? false) ? "checked" : "" ?> type="checkbox" name="payload[<?= $index - 1 ?>][approved][0]">
+ Approved by <?= $pcName ?>
+ </label><br>
+ <label style="margin-left:5px;">
+ <input <?= $index === 1 ? 'id="default-rule--approval-2"' : '' ?> <?= ($rule["approved"][1] ?? false) ? "checked" : "" ?> type="checkbox" name="payload[<?= $index - 1 ?>][approved][1]">
+ Approved by <?= $prName ?>
+ </label><br>
+
+ <hr>
+ </div>
+ <?php $index++; endforeach; ?>
+ </div>
+ <input type="submit" value="Save" class="btn btn-primary">
+ <a onclick="editorNewRule();" class="btn btn-secondary">New rule</a>
+ <input type="hidden" name="updateRules">
+ </form>
+ </div>
+ </div>
+ </div>
+</div>
+
+<script>
+ window.numberOfRules = <?= count($rules) ?>;
+
+ Array.from(document.getElementsByClassName("rule-outer")).forEach((el) => {
+ let details = el.children[0];
+
+ el.onclick = () => {
+ Array.from(document.getElementsByClassName("rule-outer")).forEach((sel) => {
+ if (el === sel) return;
+ sel.children[0].open = false;
+ sel.classList.remove("open");
+ });
+
+ details.open = !details.open;
+
+ if (details.open) {
+ el.classList.add("open");
+ } else {
+ el.classList.remove("open");
+ }
+ }
+ })
+
+ function editorNewRule() {
+ let id = Math.random(36).toString().split(".")[1];
+
+ document.getElementById("default-rule").id = "added-" + id + "-1";
+ document.getElementById("default-rule--number").id = "added-" + id + "-2";
+ document.getElementById("default-rule--name").id = "added-" + id + "-3";
+ document.getElementById("default-rule--content").id = "added-" + id + "-4";
+ document.getElementById("default-rule--approval-1").id = "added-" + id + "-5";
+ document.getElementById("default-rule--approval-2").id = "added-" + id + "-6";
+
+ let child = document.createElement("div");
+ child.id = "temp-" + id;
+ window.numberOfRules++;
+
+ document.getElementById("rules-editor").appendChild(child);
+ document.getElementById("temp-" + id).outerHTML = document.getElementById("added-" + id + "-1").outerHTML;
+
+ document.getElementById("added-" + id + "-1").id = "default-rule";
+ document.getElementById("added-" + id + "-2").id = "default-rule--number";
+ document.getElementById("added-" + id + "-3").id = "default-rule--name";
+ document.getElementById("added-" + id + "-4").id = "default-rule--content";
+ document.getElementById("added-" + id + "-5").id = "default-rule--approval-1";
+ document.getElementById("added-" + id + "-6").id = "default-rule--approval-2";
+
+ document.getElementById("added-" + id + "-3").name = "payload[" + (numberOfRules - 1) + "][name]";
+ document.getElementById("added-" + id + "-4").name = "payload[" + (numberOfRules - 1) + "][content]";
+ document.getElementById("added-" + id + "-5").name = "payload[" + (numberOfRules - 1) + "][approved][0]";
+ document.getElementById("added-" + id + "-6").name = "payload[" + (numberOfRules - 1) + "][approved][1]";
+
+ document.getElementById("added-" + id + "-5").checked = false;
+ document.getElementById("added-" + id + "-6").checked = false;
+ document.getElementById("added-" + id + "-3").value = "";
+ document.getElementById("added-" + id + "-4").value = "";
+ document.getElementById("added-" + id + "-2").innerText = "Rule #" + numberOfRules + ":";
+ }
+</script>
+
+<style>
+ .list-group-item {
+ color: #fff;
+ background-color: #222;
+ border: 1px solid rgba(255, 255, 255, .125);
+ }
+
+ .list-group-item.disabled {
+ color: #fff;
+ background-color: #222;
+ border-color: rgba(255, 255, 255, .125);
+ opacity: .75;
+ }
+
+ .rule-outer:hover {
+ background-color: #252525;
+ color: #ddd;
+ }
+
+ .rule-outer:active, .rule-outer:focus {
+ background-color: #272727;
+ color: #bbb;
+ }
+
+ .rule-outer.open {
+ background-color: #333;
+ }
+
+ .rule {
+ list-style: none;
+ pointer-events: none;
+ }
+
+ .rule-outer {
+ cursor: pointer;
+ }
+
+ .modal-header {
+ border-bottom: 1px solid #353738;
+ }
+
+ .modal-content {
+ border: 1px solid rgba(255, 255, 255, .2);
+ background-color: #111;
+ }
+
+ .btn-close {
+ filter: invert(1);
+ }
+</style>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?>
diff --git a/pages/score.php b/pages/score.php
index 96a9d17..043a695 100644
--- a/pages/score.php
+++ b/pages/score.php
@@ -1,7 +1,7 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
-if (!$isLoggedIn) header("Location: /login") and die();
+if (!$isLoggedIn) header("Location: /-/login") and die();
$title = "Score system testing"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
@@ -10,7 +10,7 @@ $title = "Score system testing"; require_once $_SERVER['DOCUMENT_ROOT'] . '/incl
<br>
<div class="container">
<div id="page-content">
- <h2>Score System Testing</h2>
+ <h2>Score system testing</h2>
<h4>Raindrops System (<code><?php
diff --git a/pages/splitting.php b/pages/splitting.php
index fab97f1..7b5b1f5 100644
--- a/pages/splitting.php
+++ b/pages/splitting.php
@@ -1,7 +1,7 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
-if (!$isLoggedIn) header("Location: /login") and die();
+if (!$isLoggedIn) header("Location: /-/login") and die();
$title = "Members by splitting date"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
diff --git a/pages/together.php b/pages/together.php
index 644a6c0..4e39736 100644
--- a/pages/together.php
+++ b/pages/together.php
@@ -1,7 +1,7 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
-if (!$isLoggedIn) header("Location: /login") and die();
+if (!$isLoggedIn) header("Location: /-/login") and die();
$title = "Watch Together"; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
diff --git a/pages/toys.php b/pages/toys.php
new file mode 100644
index 0000000..e4e5def
--- /dev/null
+++ b/pages/toys.php
@@ -0,0 +1,915 @@
+<?php
+
+require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
+if (!$isLoggedIn) header("Location: /-/login") and die();
+
+if (isset($_POST['deleteAction'])) {
+ $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json"), true);
+
+ $selected = null;
+ $selectedIndex = -1;
+ $id = $_POST['action'];
+
+ foreach ($data as $index => $item) {
+ if ($item["id"] === $id) {
+ $selectedIndex = $index;
+ $selected = $item;
+ break;
+ }
+ }
+
+ if ($selected === null) {
+ header("Location: /-/toys");
+ die();
+ }
+
+ unset($data[$selectedIndex]);
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json", utf8_encode(json_encode($data)));
+ header("Location: /-/toys/?d&id=" . $id);
+ die();
+}
+
+if (isset($_POST['updateAction'])) {
+ $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json"), true);
+
+ $selected = null;
+ $selectedIndex = -1;
+ $id = $_POST['action'];
+
+ foreach ($data as $index => $item) {
+ if ($item["id"] === $id) {
+ $selectedIndex = $index;
+ $selected = $item;
+ break;
+ }
+ }
+
+ if ($selected === null) {
+ header("Location: /-/toys");
+ die();
+ }
+
+ if (isset($_POST["sexual"])) {
+ $selected["sexual"] = true;
+ } else {
+ $selected["sexual"] = false;
+ }
+
+ if (isset($_POST["name"])) $selected["name"] = strip_tags(trim($_POST["name"]));
+ if (isset($_POST["usage"])) $selected["usage"] = strip_tags(trim($_POST["usage"]));
+ if (isset($_POST["irl"])) $selected["irl"] = strip_tags(trim($_POST["irl"]));
+ if (isset($_POST["keywords"])) $selected["keywords"] = array_map(function ($i) {
+ return trim($i);
+ }, explode(",", strip_tags(trim($_POST["keywords"]))));
+ if (isset($_POST["description"])) $selected["description"] = strip_tags(trim($_POST["description"]));
+ if (isset($_POST["water"])) $selected["water"] = match ($_POST["water"]) {
+ "0" => "out",
+ "1" => "in",
+ "2" => "both",
+ "3" => "playground"
+ };
+
+ if (isset($_POST["relations"])) {
+ $ponies = [];
+
+ foreach ($_POST["relations"] as $relation => $d) {
+ $ponies[] = [
+ "members" => explode("-", $relation),
+ "deprecated" => isset($d["deprecated"]),
+ "sexual" => isset($d["sexual"])
+ ];
+ }
+
+ $selected["ponies"] = $ponies;
+ }
+
+ global $_PROFILE;
+ if ($_PROFILE['login'] === "raindrops" && isset($_POST["verified"])) {
+ $selected["verified"] = true;
+ } else {
+ unset($selected["verified"]);
+ }
+
+ if (isset($_POST["untested"])) {
+ $selected["untested"] = true;
+ } else {
+ unset($selected["untested"]);
+ }
+
+ $data[$selectedIndex] = $selected;
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json", utf8_encode(json_encode($data)));
+
+ header("Location: /-/toys/" . $id);
+ die();
+}
+
+if (isset($_POST['createAction'])) {
+ require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/random.php";
+ $data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json"), true);
+
+ if (!isset($_POST["name"]) || !isset($_POST["water"])) {
+ header("Location: /-/toys");
+ die();
+ }
+ if (trim($_POST["name"]) === "" || !is_numeric($_POST["water"])) {
+ header("Location: /-/toys");
+ die();
+ }
+
+ $water = match ($_POST["water"]) {
+ "0" => "out",
+ "1" => "in",
+ "2" => "both",
+ "3" => "playground"
+ };
+ $name = strip_tags(trim($_POST["name"]));
+ $id = random();
+
+ $ponies = [];
+
+ if (isset($_POST["relations"])) {
+ foreach ($_POST["relations"] as $relation => $_) {
+ $ponies[] = [
+ "members" => explode("-", $relation),
+ "deprecated" => false
+ ];
+ }
+ }
+
+ if (isset($_POST["sexual"])) {
+ $sexual = true;
+ } else {
+ $sexual = false;
+ }
+
+ $data[] = [
+ "id" => $id,
+ "name" => $name,
+ "water" => $water,
+ "description" => null,
+ "ponies" => $ponies,
+ "usage" => null,
+ "irl" => null,
+ "keywords" => [],
+ "sexual" => $sexual
+ ];
+
+ file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json", utf8_encode(json_encode($data)));
+ header("Location: /-/toys/" . $id);
+ die();
+}
+
+global $pagename;
+$parts = explode("/", $pagename);
+$data = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json"), true);
+
+$selected = null;
+$title = "Toys database";
+
+if (isset($parts[1])) {
+ $id = $parts[1];
+
+ foreach ($data as $item) {
+ if ($item["id"] === $id) {
+ $selected = $item;
+ break;
+ }
+ }
+
+ if ($selected === null) {
+ header("Location: /-/toys/?nf&id=" . $id);
+ die();
+ } else {
+ $title = $selected["name"] . " · Toys database";
+ }
+}
+
+require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/keywords.php';
+
+if (!file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json")) file_put_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json", "[]");
+
+global $_PROFILE;
+
+?>
+
+<script src="/assets/editor/fuse.js"></script>
+
+<br>
+<div class="container">
+ <div id="<?= isset($parts[1]) ? "page-content" : "" ?>">
+ <?php if (isset($_GET['nf'])): ?>
+ <div class="alert alert-danger alert-dismissible">
+ <button onclick='window.history.pushState({"html":null,"pageTitle":document.title},"", "/-/toys/");' type="button" class="btn-close" data-bs-dismiss="alert" style="filter: none !important;"></button>
+ <b>Error: </b> The requested toy (<code><?= strip_tags($_GET['id'] ?? "-") ?></code>) was not found, it may have been deleted or has never existed.
+ </div>
+ <?php endif; ?>
+
+ <?php if (isset($_GET['d'])): ?>
+ <div class="alert alert-success alert-dismissible">
+ <button onclick='window.history.pushState({"html":null,"pageTitle":document.title},"", "/-/toys");' type="button" class="btn-close" data-bs-dismiss="alert" style="filter: none !important;"></button>
+ <b>Success: </b> The toy with ID <code><?= strip_tags($_GET['id'] ?? "-") ?></code> has been successfully deleted.
+ </div>
+ <?php endif; ?>
+
+ <?php if (isset($parts[1])): ?>
+
+ <h2>
+ <span style="vertical-align: middle;"><?= $selected["name"] ?></span>
+ <a href="/-/toys" class="small btn btn-outline-light" style="float:right;margin-top:5px;vertical-align:middle;opacity:1 !important; color:white;">Back</a>
+ </h2>
+ <p>
+ <a onclick="event.target.blur();" data-bs-toggle="modal" data-bs-target="#editor" href="#">Edit</a> ·
+ <?php if ($item["sexual"]): ?>
+ <span style="" class="badge rounded-pill bg-danger">Sexual</span>
+ <?php else: ?>
+ <span style="" class="badge rounded-pill bg-success">Pleasurable</span>
+ <?php endif; ?>
+ <?php if (!isset($item["verified"])): ?><span class="badge bg-warning rounded-pill text-black">Unverified</span><?php endif; ?>
+ <?php if (isset($item["untested"])): ?><span class="badge bg-info rounded-pill">Untested</span><?php endif; ?>
+ <?php if ($item["water"] === "in"): ?>
+ <span style="" class="badge rounded-pill bg-primary">Underwater</span>
+ <?php elseif ($item["water"] === "out"): ?>
+ <span style="background-color:#d63384;" class="badge rounded-pill">Outside of water</span>
+ <?php elseif ($item["water"] === "playground"): ?>
+ <span class="badge rounded-pill" style="background-color:#20c997;">In playground</span>
+ <?php else: ?>
+ <span style="" class="badge rounded-pill bg-primary">Underwater</span>
+ <span style="background-color:#d63384;" class="badge rounded-pill">Outside of water</span>
+ <?php endif; ?>
+ </p>
+
+ <?php if (isset($selected["description"]) && trim($selected["description"]) !== ""): ?>
+ <?= replaceKeyWords(str_replace("\n", "<br>", strip_tags($selected["description"]))); ?>
+ <?php else: ?>
+ <p><i>No description provided for this toy. Enter edit mode to add a description to this toy.</i></p>
+ <?php endif; ?>
+
+ <hr>
+ <b>Can be used by:</b>
+ <?php
+
+ $hasMultipleTypes = false;
+ $seenType = "";
+
+ foreach ($selected["ponies"] as $relation) {
+ if (isset($relation["sexual"]) && $relation["sexual"]) {
+ $type = "sexual";
+ } else {
+ $type = "affectionate";
+ }
+
+ if ($type !== $seenType && $seenType !== "") {
+ $hasMultipleTypes = true;
+ }
+
+ if (trim($seenType) === "") {
+ $seenType = $type;
+ }
+ }
+
+ ?>
+ <ul>
+ <?php foreach ($selected["ponies"] as $relation): if (!$relation["deprecated"]):
+
+ $member1 = getMemberWithoutSystem($relation["members"][0]);
+ $member2 = getMemberWithoutSystem($relation["members"][1]);
+
+ ?>
+ <li>
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member1["name"] ?>.png">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member2["name"] ?>.png">
+
+ <span style="vertical-align: middle;"><?= getMiniName($member1["display_name"] ?? $member1["name"]) ?> and <?= getMiniName($member2["display_name"] ?? $member2["name"]) ?></span>
+ </li>
+ <?php endif; endforeach; ?>
+ <?php foreach ($selected["ponies"] as $relation): if ($relation["deprecated"]):
+
+ $member1 = getMemberWithoutSystem($relation["members"][0]);
+ $member2 = getMemberWithoutSystem($relation["members"][1]);
+
+ ?>
+ <li>
+ <span style="opacity:.5;">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member1["name"] ?>.png">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="/assets/uploads/pt-<?= $member2["name"] ?>.png">
+
+ <span style="vertical-align: middle;"><?= getMiniName($member1["display_name"] ?? $member1["name"]) ?> and <?= getMiniName($member2["display_name"] ?? $member2["name"]) ?></span>
+ </span>
+ <span class="badge bg-danger rounded-pill">Deprecated</span>
+ </li>
+ <?php endif; endforeach; ?>
+ </ul>
+
+ <div style="margin-top:10px;"></div>
+
+ <b>Usage:</b><br>
+ <?php if (isset($selected["usage"]) && trim($selected["usage"]) !== ""): ?>
+ <?php
+
+ $lines = explode("\n", strip_tags($selected["usage"]));
+
+ if (count($lines) > 1) echo("<ul>");
+
+ foreach ($lines as $line) {
+ if (count($lines) > 1) echo("<li>");
+
+ $parts = explode(":", $line);
+
+ if (count($parts) > 1 && strlen($parts[0]) < 30) {
+ $p0 = $parts[0]; array_shift($parts);
+ echo(replaceKeyWords("<b>" . $p0 . ":</b>" . implode(":", $parts)));
+ } else {
+ echo(replaceKeyWords(implode(":", $parts)));
+ }
+
+ if (count($lines) > 1) echo("</li>");
+ }
+
+ if (count($lines) > 1) echo("</ul>");
+
+ ?>
+ <?php else: ?>
+ <p><i>No usage provided for this toy. Enter edit mode to add usage information to this toy.</i></p>
+ <?php endif; ?>
+
+ <div style="margin-top:10px;"></div>
+
+ <b>Instructions to craft in real life:</b><br>
+ <?php if (isset($selected["irl"]) && trim($selected["irl"]) !== ""): ?>
+ <?= replaceKeyWords(strip_tags($selected["irl"])) ?>
+ <?php else: ?>
+ <p><i>This toy is not craftable in real life.</i></p>
+ <?php endif; ?>
+
+ <hr>
+
+ <h4>Similar toys</h4>
+ <div class="row">
+ <?php
+
+ $names = [];
+ $currentName = $selected["name"];
+ $namesByDistance = [];
+
+ foreach ($data as $action) {
+ if ($action["name"] !== $currentName) $names[$action["name"]] = [
+ "id" => $action["id"],
+ "water" => $action["water"],
+ "ponies" => $action["ponies"]
+ ];
+ }
+
+ foreach ($names as $name => $data) {
+ $namesByDistance[] = [
+ "name" => $name,
+ "distance" => levenshtein($currentName, $name) + ((int)($data["type"] !== $selected["type"]) * 2),
+ "id" => $data["id"],
+ "water" => $data["water"],
+ "ponies" => $data["ponies"]
+ ];
+ }
+
+ uasort($namesByDistance, function ($a, $b) use ($selected) {
+ return $a["distance"] - $b["distance"];
+ });
+
+ foreach ($namesByDistance as $item) {
+ echo("<!-- " . $currentName . " <-> " . $item["name"] . " => " . $item["distance"] . " (artif: " . ((int)($item["water"] !== $selected["water"]) * 10) . ") -->");
+ }
+
+ $index = 0;
+ foreach ($namesByDistance as $item): if ($index < 3):
+ ?>
+ <div class="col-md-4" style="margin-bottom:20px;">
+ <a class="linked-card" href="/-/actions/<?= $item["id"] ?>"><div class="card">
+ <div class="card-body">
+ <h4 class="card-title"><?= $item["name"] ?></h4>
+ <p class="card-text">
+ <?php
+
+ $uniquePonies = [];
+
+ foreach ($item["ponies"] as $ponies) {
+ foreach ($ponies["members"] as $member) {
+ if (isset($uniquePonies[$member]) && !$uniquePonies[$member]) {
+ $uniquePonies[$member] = false;
+ } else {
+ $uniquePonies[$member] = $ponies["deprecated"];
+ }
+ }
+ }
+
+ foreach ($uniquePonies as $name => $deprecated): if (!$deprecated): ?>
+ <span data-bs-toggle="tooltip" title="<?= getMemberWithoutSystem($name)["display_name"] ?? getMemberWithoutSystem($name)["display_name"] ?>" style="display: inline-block;">
+ <img src="/assets/uploads/pt-<?= getMemberWithoutSystem($name)["name"] ?>.png" style="width:32px;">
+ </span>
+ <?php endif; endforeach; ?>
+ <?php foreach ($uniquePonies as $name => $deprecated): if ($deprecated): ?>
+ <span data-bs-toggle="tooltip" data-bs-html="true" title="<i><?= strip_tags(getMemberWithoutSystem($name)["display_name"] ?? getMemberWithoutSystem($name)["name"]) ?></i>" style="opacity:.5;display: inline-block;">
+ <img src="/assets/uploads/pt-<?= getMemberWithoutSystem($name)["name"] ?>.png" style="width:32px;">
+ </span>
+ <?php endif; endforeach; ?>
+ </p>
+ <?php if ($item["sexual"]): ?>
+ <span style="" class="badge rounded-pill bg-danger">Sexual</span>
+ <?php else: ?>
+ <span style="" class="badge rounded-pill bg-success">Pleasurable</span>
+ <?php endif; ?>
+ <?php if ($item["water"] === "in"): ?>
+ <span style="" class="badge rounded-pill bg-primary">Underwater</span>
+ <?php elseif ($item["water"] === "out"): ?>
+ <span style="background-color:#d63384;" class="badge rounded-pill">Outside of water</span>
+ <?php elseif ($item["water"] === "playground"): ?>
+ <span class="badge rounded-pill" style="background-color:#20c997;">In playground</span>
+ <?php else: ?>
+ <span style="" class="badge rounded-pill bg-primary">Underwater</span>
+ <span style="background-color:#d63384;" class="badge rounded-pill">Outside of water</span>
+ <?php endif; ?>
+ </div>
+ </div></a>
+ </div>
+ <?php $index++; endif; endforeach; ?>
+ </div>
+
+ <div class="modal fade" id="editor">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h4 class="modal-title">Edit toy</h4>
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+
+ <div class="modal-body">
+ <form method="post" style="display:inline;">
+ <input type="text" placeholder="Toy name" name="name" class="form-control" style="color:white;background:#111;border-color:#222;margin-bottom:10px;" value="<?= str_replace('"', '&quot;', $selected["name"]) ?>">
+ <select name="water" class="form-select" style='color:white;background-color:#111;border-color:#222;background-image:url("data:image/svg+xml,%3csvg xmlns=&apos;http://www.w3.org/2000/svg&apos; viewBox=&apos;0 0 16 16&apos;%3e%3cpath fill=&apos;none&apos; stroke=&apos;%23ffffff&apos; stroke-linecap=&apos;round&apos; stroke-linejoin=&apos;round&apos; stroke-width=&apos;2&apos; d=&apos;M2 5l6 6 6-6&apos;/%3e%3c/svg%3e");margin-bottom:10px;'>
+ <option value="0" <?= $selected["water"] === "out" ? "selected" : "" ?>>Usable outside of the water</option>
+ <option value="1" <?= $selected["water"] === "in" ? "selected" : "" ?>>Usable inside of the water</option>
+ <option value="2" <?= $selected["water"] === "both" ? "selected" : "" ?>>Usable both inside and outside</option>
+ <option value="3" <?= $selected["water"] === "playground" ? "selected" : "" ?>>Usable in a playground</option>
+ </select>
+
+ <label style="margin-left:5px;">
+ <input <?= ($selected["sexual"] ?? false) ? "checked" : "" ?> type="checkbox" name="sexual">
+ Sexual toy
+ </label><br>
+
+ <label style="margin-left:5px;">
+ <input <?= ($selected["verified"] ?? false) ? "checked" : "" ?> <?= $_PROFILE['login'] !== "raindrops" ? "disabled" : "" ?> type="checkbox" name="verified">
+ Mark as verified
+ </label><br>
+
+ <label style="margin-left:5px;">
+ <input <?= ($selected["untested"] ?? false) ? "checked" : "" ?> type="checkbox" name="untested">
+ Mark as untested
+ </label>
+
+ <hr>
+
+ <textarea name="description" class="form-control" style="resize: none;color:white;background:#111;border-color:#222;margin-bottom:10px;" placeholder="Description"><?= strip_tags($selected["description"] ?? "") ?></textarea>
+
+ <textarea placeholder="Toy usage" name="usage" class="form-control" style="resize: none;color:white;background:#111;border-color:#222;margin-bottom:10px;"><?= strip_tags($selected["usage"] ?? "") ?></textarea>
+
+ <hr>
+
+ <input type="text" placeholder="Keywords (comma-separated)" name="keywords" class="form-control" style="color:white;background:#111;border-color:#222;margin-bottom:10px;" value="<?= str_replace('"', '&quot;', implode(",", $selected["keywords"] ?? [])) ?>">
+
+ <input type="text" placeholder="Instructions to craft IRL" name="irl" class="form-control" style="color:white;background:#111;border-color:#222;" value="<?= str_replace('"', '&quot;', $selected["irl"] ?? "") ?>">
+
+ <hr>
+
+ <p>Select the groups of ponies who can use this toy:</p>
+ <?php
+
+ $relations = [];
+ $actions = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json"), true);
+
+ foreach ($actions as $action) {
+ if ($action["type"] !== "sexual") continue;
+
+ foreach ($action["ponies"] as $ponies) {
+ $id = implode("", $ponies["members"]);
+
+ $member = getMemberWithoutSystem($ponies["members"][0]);
+ $otherMember = getMemberWithoutSystem($ponies["members"][1]);
+
+ $parts = [
+ $member["id"],
+ $otherMember["id"]
+ ];
+
+ asort($parts);
+
+ $relations[implode("-", $parts)] = [
+ "name" => getMiniName($member["display_name"] ?? $member["name"]) . " and " . getMiniName($otherMember["display_name"] ?? $otherMember["name"]),
+ "images" => [
+ file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-$member[name].png") ? "/assets/uploads/pt-$member[name].png" : "/assets/uploads/pt.png",
+ file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-$otherMember[name].png") ? "/assets/uploads/pt-$otherMember[name].png" : "/assets/uploads/pt.png",
+ ]
+ ];
+ }
+ }
+
+ $inFileRelations = [];
+ $inFileDeprecations = [];
+
+ foreach ($selected["ponies"] as $ponies) {
+ $inFileRelations[] = $ponies["members"][0] . "-" . $ponies["members"][1];
+
+ if (isset($ponies["deprecated"]) && $ponies["deprecated"]) $inFileDeprecations[] = $ponies["members"][0] . "-" . $ponies["members"][1];
+ }
+
+ foreach ($relations as $id => $relation):
+ ?>
+ <label style="display:block;" class="creator-relation <?= $selected["type"] === "mixed" ? "mixed" : "" ?> <?= in_array($id, $inFileRelations) ? "checked" : "" ?>">
+ <input <?= in_array($id, $inFileRelations) ? "checked" : "" ?> name="relations[<?= $id ?>][member]" style="display:none;" type="checkbox" class="checkbox-input">
+
+ <img style="vertical-align: middle;width:24px;height:24px;" src="<?= $relation["images"][0] ?>">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="<?= $relation["images"][1] ?>">
+ <span style="vertical-align: middle;"><?= $relation["name"] ?></span>
+
+ <label class="deprecated" style="display:none;margin-left: 20px;margin-top: 5px;">
+ <input <?= in_array($id, $inFileDeprecations) ? "checked" : "" ?> name="relations[<?= $id ?>][deprecated]" type="checkbox">
+ Mark as deprecated
+ </label>
+ </label>
+ <?php endforeach; ?>
+
+ <br>
+ <input type="hidden" name="updateAction">
+ <input type="hidden" name="action" value="<?= $selected["id"] ?>">
+ <input type="submit" class="btn btn-primary" value="Save">
+ </form>
+ <form method="post" style="display:inline;">
+ <input type="hidden" name="deleteAction">
+ <input type="hidden" name="action" value="<?= $selected["id"] ?>">
+ <input type="submit" class="btn btn-danger" value="Delete">
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <?php else: ?>
+
+ <h2>Toys database</h2>
+ <p><?= count($data) ?> toys (<?= count(array_filter($data, function ($i) {
+ return $i["sexual"] === false;
+ })) ?> non-sexual, <?= count(array_filter($data, function ($i) {
+ return $i["sexual"] === true;
+ })) ?> sexual, <?= count(array_filter($data, function ($i) {
+ return isset($i["untested"]) && $i["untested"];
+ })) ?> untested, <?= count(array_filter($data, function ($i) {
+ return (isset($i["description"]) && trim($i["description"]) === "") || !isset($i["description"]);
+ })) ?> incomplete)</p>
+
+ <input type="text" placeholder="Search for a toy..." class="form-control" style="margin-bottom:15px;color:white;background:#111;border-color:#222;" onchange="search();" onkeydown="search();" onkeyup="search();" id="search">
+
+ <div id="list">
+ <div class="list-group">
+ <?php
+
+ $init = [];
+ foreach ($data as $value) {
+ $init[$value["name"]] = $value;
+ }
+
+ ksort($init);
+
+ $sorted = array_values($init);
+ uasort($sorted, function ($a, $b) {
+ $uniquePonies1 = [];
+
+ foreach ($a["ponies"] as $ponies) {
+ foreach ($ponies["members"] as $member) {
+ $uniquePonies1[$member] = true;
+ }
+ }
+
+ $uniquePonies2 = [];
+
+ foreach ($b["ponies"] as $ponies) {
+ foreach ($ponies["members"] as $member) {
+ $uniquePonies2[$member] = true;
+ }
+ }
+
+ return count($uniquePonies2) - count($uniquePonies1);
+ });
+
+ foreach ($sorted as $item): ?>
+ <a href="/-/toys/<?= $item["id"] ?>" id="action-<?= $item["id"] ?>" style="display:grid;grid-template-columns: 1fr 1fr 0.2fr;" class="list-group-item list-group-item-action action-listing">
+ <div>
+ <span class="<?= trim($item["description"]) === "" ? "text-danger" : "" ?>"><?= $item["name"] ?></span>
+ <?php if (!isset($item["verified"])): ?><span class="badge bg-warning rounded-pill text-black">Unverified</span><?php endif; ?>
+ <?php if (isset($item["untested"])): ?><span class="badge bg-info rounded-pill">Untested</span><?php endif; ?>
+ </div>
+ <div>
+ <?php
+
+ $uniquePonies = [];
+ $longPonyList = false;
+
+ foreach ($item["ponies"] as $ponies) {
+ foreach ($ponies["members"] as $member) {
+ if (isset($uniquePonies[$member]) && !$uniquePonies[$member]) {
+ $uniquePonies[$member] = false;
+ } else {
+ $uniquePonies[$member] = $ponies["deprecated"];
+ }
+ }
+ }
+
+ if (count($uniquePonies) > 6) {
+ $longPonyList = true;
+ }
+
+ $index = 1;
+ foreach ($uniquePonies as $name => $deprecated): if (!$deprecated): ?>
+ <!-- <?= $index ?> -->
+ <span title="<?= getMemberWithoutSystem($name)["display_name"] ?? getMemberWithoutSystem($name)["display_name"] ?>" style="display: inline-block;<?= $longPonyList && $index % 2 === 0 ? "position:absolute;margin-left:-12px;z-index:" . (999 - $index) . ";" : "position:relative;z-index:" . (999 - $index) . ";" ?>">
+ <img src="/assets/uploads/pt-<?= getMemberWithoutSystem($name)["name"] ?>.png" style="width:24px;">
+ </span>
+ <?php $index++; endif; endforeach; ?>
+ <?php foreach ($uniquePonies as $name => $deprecated): if ($deprecated): ?>
+ <span title="<?= strip_tags(getMemberWithoutSystem($name)["display_name"] ?? getMemberWithoutSystem($name)["name"]) ?> (deprecated)" style="opacity:.5;display: inline-block;<?= $longPonyList && $index % 2 === 0 ? "position:absolute;margin-left:-12px;z-index:" . (999 - $index) . ";" : "position:relative;z-index:" . (999 - $index) . ";" ?>">
+ <img src="/assets/uploads/pt-<?= getMemberWithoutSystem($name)["name"] ?>.png" style="width:24px;">
+ </span>
+ <?php $index++; endif; endforeach; ?>
+ </div>
+ <div style="text-align: right;">
+ <?php if ($item["sexual"]): ?>
+ <span style="" class="badge rounded-pill bg-danger">Sexual</span>
+ <?php else: ?>
+ <span style="" class="badge rounded-pill bg-success">Pleasurable</span>
+ <?php endif; ?>
+ </div>
+ </a>
+ <?php endforeach; ?>
+ </div>
+ </div>
+
+ <div id="search-results" class="list-group"></div>
+
+ <div id="page-content">
+ <hr>
+ <p>Not finding what you are looking for? <a onclick="event.target.blur(); document.getElementById('creator-title').focus();" href="#" data-bs-toggle="modal" data-bs-target="#creator">Add a toy.</a></p>
+ </div>
+
+ <script>
+ window.actions = JSON.parse(atob(`<?= base64_encode(json_encode(array_values(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/toys.json"), true)))) ?>`));
+ </script>
+
+ <div class="modal fade" id="creator">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h4 class="modal-title">Add a new toy</h4>
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+ </div>
+
+ <div class="modal-body">
+ <form method="post">
+ <input id="creator-title" type="text" placeholder="Toy name" name="name" class="form-control" style="color:white;background:#111;border-color:#222;margin-bottom:10px;">
+ <select name="water" class="form-select" style='color:white;background-color:#111;border-color:#222;background-image:url("data:image/svg+xml,%3csvg xmlns=&apos;http://www.w3.org/2000/svg&apos; viewBox=&apos;0 0 16 16&apos;%3e%3cpath fill=&apos;none&apos; stroke=&apos;%23ffffff&apos; stroke-linecap=&apos;round&apos; stroke-linejoin=&apos;round&apos; stroke-width=&apos;2&apos; d=&apos;M2 5l6 6 6-6&apos;/%3e%3c/svg%3e");margin-bottom:10px;'>
+ <option value="0" selected>Usable outside of the water</option>
+ <option value="1">Usable inside of the water</option>
+ <option value="2">Usable both inside and outside</option>
+ <option value="3">Usable in a playground</option>
+ </select>
+
+ <label style="margin-left:5px;">
+ <input type="checkbox" name="sexual">
+ Sexual toy
+ </label>
+
+ <hr>
+
+ <p>Select the groups of ponies who can use this toy:</p>
+ <?php
+
+ $relations = [];
+ $actions = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/actions.json"), true);
+
+ foreach ($actions as $action) {
+ if ($action["type"] !== "sexual") continue;
+
+ foreach ($action["ponies"] as $ponies) {
+ $id = implode("", $ponies["members"]);
+
+ $member = getMemberWithoutSystem($ponies["members"][0]);
+ $otherMember = getMemberWithoutSystem($ponies["members"][1]);
+
+ $parts = [
+ $member["id"],
+ $otherMember["id"]
+ ];
+
+ asort($parts);
+
+ $relations[implode("-", $parts)] = [
+ "name" => getMiniName($member["display_name"] ?? $member["name"]) . " and " . getMiniName($otherMember["display_name"] ?? $otherMember["name"]),
+ "images" => [
+ file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-$member[name].png") ? "/assets/uploads/pt-$member[name].png" : "/assets/uploads/pt.png",
+ file_exists($_SERVER['DOCUMENT_ROOT'] . "/assets/uploads/pt-$otherMember[name].png") ? "/assets/uploads/pt-$otherMember[name].png" : "/assets/uploads/pt.png",
+ ]
+ ];
+ }
+ }
+
+ foreach ($relations as $id => $relation):
+ ?>
+ <label style="display:block;" class="creator-relation">
+ <input name="relations[<?= $id ?>]" style="display:none;" type="checkbox" class="checkbox-input">
+
+ <img style="vertical-align: middle;width:24px;height:24px;" src="<?= $relation["images"][0] ?>">
+ <img style="vertical-align: middle;width:24px;height:24px;" src="<?= $relation["images"][1] ?>">
+ <span style="vertical-align: middle;"><?= $relation["name"] ?></span>
+ </label>
+ <?php endforeach; ?>
+
+ <p style="margin-top:10px;">You can add additional data (description, how to use) after adding the toy.</p>
+ <input type="hidden" name="createAction">
+ <input type="submit" class="btn btn-primary" value="Add">
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <?php endif; ?>
+ </div>
+</div>
+
+<!--suppress JSUnresolvedFunction -->
+<script>
+ Array.from(document.getElementsByClassName("checkbox-input")).forEach((el) => {
+ el.onchange = () => {
+ let parent = el.parentElement;
+
+ if (el.checked) {
+ parent.classList.add("checked");
+ } else {
+ parent.classList.remove("checked");
+ }
+ }
+ });
+
+ const fuse = new Fuse(window.actions, {
+ includeScore: true,
+ keys: [
+ {
+ name: 'name',
+ weight: 1
+ },
+ {
+ name: 'description',
+ weight: 1
+ },
+ {
+ name: 'example',
+ weight: 0.7
+ },
+ {
+ name: 'irl',
+ weight: 0.5
+ }
+ ]
+ })
+
+ function search() {
+ let query = document.getElementById("search").value;
+ let results = fuse.search(query).map((i) => {
+ return {
+ id: i.item.id,
+ score: i.score
+ };
+ });
+
+ let unfiltered = results;
+
+ results = results.filter((i) => {
+ return i.score < 0.7;
+ });
+
+ console.log("Before:", unfiltered, "After:", results);
+
+ document.getElementById("list").style.display = "none";
+ document.getElementById("search-results").style.display = "block";
+ document.getElementById("search-results").innerHTML = "";
+
+ for (let result of results) {
+ document.getElementById("search-results").innerHTML += document.getElementById("action-" + result.id).outerHTML;
+ }
+
+ console.log(results);
+
+ if (query.trim() === "") {
+ document.getElementById("list").style.display = "block";
+ document.getElementById("search-results").style.display = "none";
+ }
+ }
+
+ function changeMixed() {
+ let value = document.getElementById("editor-type").value;
+ console.log(value);
+
+ if (value === "2") {
+ Array.from(document.getElementsByClassName("creator-relation")).forEach((el) => {
+ el.classList.add("mixed");
+ });
+ } else {
+ Array.from(document.getElementsByClassName("creator-relation")).forEach((el) => {
+ el.classList.remove("mixed");
+ });
+ }
+ }
+</script>
+
+<style>
+ .modal-header {
+ border-bottom: 1px solid #353738;
+ }
+
+ .modal-content {
+ border: 1px solid rgba(255, 255, 255, .2);
+ background-color: #111;
+ }
+
+ .btn-close {
+ filter: invert(1);
+ }
+
+ .creator-relation {
+ border-radius: 10px;
+ padding: 5px 10px;
+ opacity: .5;
+ }
+
+ .creator-relation.checked {
+ background-color: rgba(255, 255, 255, .1);
+ opacity: 1;
+ }
+
+ .creator-relation:hover {
+ background-color: rgba(255, 255, 255, .1);
+ }
+
+ .creator-relation.checked:hover {
+ background-color: rgba(255, 255, 255, .25) !important;
+ }
+
+ .creator-relation.checked .deprecated {
+ display: block !important;
+ }
+
+ .creator-relation.mixed.checked .sexual {
+ display: block !important;
+ }
+
+ .list-group-item {
+ color: #fff;
+ background-color: #222;
+ border: 1px solid rgba(255, 255, 255, .125);
+ }
+
+ .list-group-item.disabled {
+ color: #fff;
+ background-color: #222;
+ border-color: rgba(255, 255, 255, .125);
+ opacity: .75;
+ }
+
+ .list-group-item:hover {
+ background-color: #252525;
+ color: #ddd;
+ }
+
+ .list-group-item:active, .list-group-item:focus {
+ background-color: #272727;
+ color: #bbb;
+ }
+
+ @media (max-width: 991px) {
+ .action-listing {
+ grid-template-columns: 1fr !important;
+ text-align: center !important;
+ }
+
+ .action-listing > * {
+ margin-bottom: 10px;
+ text-align: center !important;
+ }
+
+ .action-listing > *:nth-last-child(1) {
+ margin-bottom: 0 !important;
+ }
+
+ .action-listing img {
+ width: 32px !important;
+ }
+ }
+</style>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; ?>
diff --git a/pages/travelling.php b/pages/travelling.php
index affe92e..c40ebab 100644
--- a/pages/travelling.php
+++ b/pages/travelling.php
@@ -1,7 +1,7 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/session.php"; global $isLoggedIn;
-if (!$isLoggedIn) header("Location: /login") and die();
+if (!$isLoggedIn) header("Location: /-/login") and die();
$travelling = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/travelling.json"), true);
$app = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/app.json"), true);
@@ -116,7 +116,7 @@ global $travelling;
<br>
<div class="container">
<div>
- <h2>System Travelling</h2>
+ <h2>System travelling</h2>
<?php foreach (scoreOrderGlobal() as $member): ?>
<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 2fr max-content;">
<a 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;display:flex;align-items:center;text-decoration: none;" href="/<?= $member["name"] ?>">