Compare commits

..

49 commits
next ... main

Author SHA1 Message Date
34cd588fa9 robots.txt 2024-03-20 20:55:16 -05:00
3dc5f6574b no that's stupid. if not null 2024-03-19 22:00:33 -05:00
5df7ceebf3 or true 2024-03-19 21:59:08 -05:00
309d357d0b what ??? it was using the odl one ??? 2024-03-19 21:55:49 -05:00
7b7f6cab6a Improve bracket styling 2024-03-19 21:49:13 -05:00
ba06ec32c0 fuck again 2024-03-19 20:38:17 -05:00
b28555b7e4 eebles 2024-03-19 20:31:57 -05:00
a8a22773aa fuck 2024-03-19 20:28:32 -05:00
98c4cd1a03 Add manual mode, fix some bugs 2024-03-19 12:30:37 -05:00
967f6bc1e7 Fix some stuff about Fediverse Madness
Add pagination
2024-03-19 10:08:44 -05:00
a21bbd9a0a add offset 2024-03-18 23:18:17 -05:00
54b1b6236c add commandline tasks 2024-03-18 23:17:45 -05:00
40a6050329 oops 2024-03-18 21:43:35 -05:00
ec27da906b fix delegation and stuff 2024-03-18 21:39:13 -05:00
3b05a957da fix this 2024-03-18 21:26:02 -05:00
e0f1a59d86 Fix some things (thanks besties.house) 2024-03-18 20:02:49 -05:00
c6918b659b ugh 2024-03-18 19:24:00 -05:00
faaf3d1b13 more abtmtr.link bullshit 2024-03-18 19:02:11 -05:00
a081c6a44b oops 2024-03-09 10:58:34 -06:00
e43c470147 swearing 2024-03-08 11:47:19 -06:00
35e367a1e7 more projects 2024-02-29 00:25:03 -06:00
9cd8a604ff THE CELESTIFICATION OF ABTMTR.LINK 2024-02-29 00:07:29 -06:00
4241c69887 too tired to update favicon anymore 2024-02-23 20:45:20 -06:00
8fc147b75f new favicon 2024-02-23 20:39:40 -06:00
d21e4c3007 shut up shut up shut up sit up sit up sit up 2024-02-13 20:40:02 -06:00
6565431867 She responds to the spam emails 2024-02-12 21:02:26 -06:00
ec1f2a934d add favicon back 2024-02-12 14:24:23 -06:00
c60ff08abf v7 2024-02-12 13:46:21 -06:00
5a55a4843e rules 2024-02-02 12:25:30 -06:00
b5115b8a64 you stupid BITCH 2024-02-02 10:20:25 -06:00
6d36b0fd88 fix 2024-01-30 20:33:58 -06:00
dfbf1c6d64 change bg :3 2024-01-30 11:23:45 -06:00
e813ec4b39 crt 2024-01-24 22:49:29 -06:00
c7883652b0 add Owen Zimmerman badge 2024-01-24 09:05:51 -06:00
8aa3d5531d oops 2024-01-24 07:39:28 -06:00
36cab4cba2 add uBlock button 2024-01-24 07:38:59 -06:00
a37e1a52e4 Decrease speed of buttons 2024-01-23 20:59:15 -06:00
385551a510 Add acidicAlchemist 2024-01-23 20:50:26 -06:00
20c00dd33d Fix 88x31s a little 2024-01-23 20:37:29 -06:00
96020c395a check if font is bad 2024-01-23 20:15:40 -06:00
100ec476e1 Oops 2024-01-23 20:14:37 -06:00
2099648300 fix stuff 2024-01-23 17:12:09 -06:00
d2d5cf7161 Loader structuring. 2024-01-23 16:12:05 -06:00
92c481343b Clean up site... 2024-01-23 16:10:54 -06:00
3b43e6bc6b Rebrand 2024-01-23 15:04:38 -06:00
accf418bbf Change favicon 2024-01-23 11:23:20 -06:00
b8697b0a6e oops lol 2024-01-23 08:54:38 -06:00
8707ae9e39 I lied to your face and you show love
Why do you reward me with your love?
2024-01-22 10:06:54 -06:00
db77ca5ccd Update name stuff and favicon 2024-01-19 10:28:16 -06:00
55 changed files with 1962 additions and 482 deletions

4
.gitignore vendored
View file

@ -4,4 +4,6 @@ output/
.vercel
# the world isn't ready yet
views/projects/item/wavetapper
views/projects/item/wavetapper
views/projects/item/dice_2
views/projects/item/text

14
embeds/attr.html Normal file
View file

@ -0,0 +1,14 @@
<p id="attr">
Image attribution:
<a href="https://exok.com/games/celeste/" target="_blank">Celeste</a>
</p>
<style>
#attr {
display: inline-block;
position: fixed;
left: 0;
bottom: 0;
color: white !important;
text-shadow: 0 0.1em 0.5em black;
}
</style>

View file

@ -19,7 +19,7 @@
>
<div>
<h1>Loading...</h1>
<h2>Please wait.</h2>
<p>Please wait.</p>
<p>If this page does not load, try enabling JavaScript.</p>
<small>(This site won't work well without it.)</small>
<p>Or <a href="/">go back home</a>.</p>

View file

@ -1,11 +1,9 @@
<section class="nav" role="navigation" aria-label="Site navigation">
<p>
<a href="/">Home</a> <a href="/branding/">Branding</a>
<a href="/site/">Site Information</a>
</p>
<p>
<a href="/">Home</a>
<a href="https://blog.abtmtr.link/mtr/quick-q-and-a">FAQ</a>
<a href="/projects/">Projects</a>
<a href="/domain/">Domain</a>
<a href="/links/">Links</a>
<!-- <a href="/whois/">Who are we?</a> -->
</p>
</section>

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
views/assets/bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

View file

@ -1,127 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Branding - abtmtr.link</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles/normal.css" />
<link rel="stylesheet" href="/styles/style.css" />
<style>
:root {
--base-color: 0, 0%;
}
.chip {
background-color: var(--mainColor);
display: inline-block;
width: 12em;
aspect-ratio: 1/1;
padding: 1em;
vertical-align: top;
margin: 0.5em;
}
</style>
</head>
<body>
<header>
<section>
<h1>Branding</h1>
</section>
<$ nav.html $>
<section id="accessibility" hidden></section>
</header>
<main>
<section>
<p>
I don't really have strict branding guidelines, but I do have a few
important rules if you would like to refer to me in a professional or
formal context.
</p>
</section>
<section>
<h2>Name</h2>
<p>
If you would like to refer to me online, you'll usually be able to use
my username <b>MeowcaTheoRange</b>. If that's too long, you can always
shorten it to <strong>MTR</strong>.
</p>
<p>
Please make sure to keep the styling. It's <b>MeowcaTheoRange</b>, not<!-- -->
<strong>MeowcatHeOrange</strong>, <strong>MeowcaTheOrange</strong>,
nor <strong>Meowca Theo Range</strong>.
</p>
<p class="hv">
The styling <strong>meowcatheorange</strong> is OK if necessary.
</p>
<p>
If you would like to refer to me in a more professional context,
<a href="https://pronouns.cc/@MeowcaTheoRange" target="_blank"
>check my pronouns.cc page for my preferred names.</a
>
</p>
</section>
<section id="characters" style="display: none">
<h2>Characters</h2>
<p>
If you would like to use a character to represent me, I'd prefer you
use <b class="characterName"></b>
<small>(<span class="pronouns"></span>)</small>.
</p>
<img class="characterImage" />
</section>
<section>
<h2>Colors</h2>
<h3>Main Colors</h3>
<p>
If you would like to use colors to refer to me, whether that be the
primary color on a card, or the color of my name, I recommend you use
these colors:
</p>
<div style="--mainColor: #00c0ff; color: #000000" class="chip">
<b>Iszac Blue</b><br /><small>Primary</small>
<p>#00c0ff</p>
</div>
<div style="--mainColor: #ff4000; color: #000000" class="chip">
<b>Rocco Orange</b><br /><small>Primary Negative</small>
<p>#ff4000</p>
</div>
<p>
<b>Iszac Blue</b> is literally <strong>Rocco Orange</strong> but
inverted. This color is named after <strong>Iszac</strong>.<br />If
you were to use any of these colors to represnt me as a
<strong>person</strong>, use this one.
</p>
<p>
<b>Rocco Orange</b> is my favourite color orange, but now more red.
The name comes from my character <strong>Rocco</strong>.<br />If you
were to use any of these colors to represnt me as an
<strong>entity</strong>, use this one whenever possible.
</p>
<h3>Sub Colors</h3>
<p>
If you need filler colors for something that refers to me, feel free
to use these extra colors:
</p>
<div style="--mainColor: #8000ff; color: #000000" class="chip">
<b>Grape Soda</b><br /><small>Secondary</small>
<p>#8000ff</p>
</div>
<div style="--mainColor: #80ff00; color: #000000" class="chip">
<b>Avalonian Waste</b><br /><small>Secondary Negative</small>
<p>#80ff00</p>
</div>
<p>
<b>Grape Soda</b> represents my love for grape soda.<br />My favourite
is Fanta Grape. :]
</p>
<p>
<b>Avalonian Waste</b> is also literally
<strong>Grape Soda</strong> but inverted. This represents some old
lore that I'm still screwing with.
</p>
</section>
</main>
<section id="accessibility" hidden></section>
<script src="/scripts/accessibility.js"></script>
</body>
</html>

View file

@ -1,22 +0,0 @@
const characters = document.getElementById("characters");
const CURRENT_CHARACTER = "meowcatheorange/iszac";
fetch(`https://beta.trollcall.xyz/api/troll/${CURRENT_CHARACTER}`)
.then((x) => x.json())
.then((char) => {
var name = char.name
.map((string) => {
string = string[0].toUpperCase() + string.slice(1);
return string;
})
.join(" ");
characters.querySelector(
".characterName"
).innerHTML = `<a href="https://beta.trollcall.xyz/troll/${CURRENT_CHARACTER}" target="_blank">${name}</a>`;
characters.querySelector(".characterImage").src = char.images[0];
characters.querySelector(".pronouns").innerHTML = char.pronouns
.map((pronoun) => pronoun[0])
.join("/");
characters.style.display = null;
});

View file

@ -1,14 +0,0 @@
const data_get = document.getElementById("data_get");
fetch("https://blog.abtmtr.link/api/collections/paste/posts/domain")
.then((x) => x.json())
.then((paste) => {
const data = JSON.parse(paste.data.body);
data_get.innerHTML = data.reduce(
(html, subdomain) =>
html +
`<h1><a href="${subdomain.url}" target="_blank">${subdomain.name}</a></h1>
${subdomain.description.map((x) => `<p>${x}</p>`).join("")}
<img src="${subdomain.src}" alt="${subdomain.name}" title="${subdomain.name}" />`,
""
);
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View file

@ -7,62 +7,96 @@
<link rel="stylesheet" href="/styles/normal.css" />
<link rel="stylesheet" href="/styles/windows.css" />
<link rel="stylesheet" href="/styles/style.css" />
<link rel="stylesheet" href="./styles/buttonroll.css" />
<style>
:root {
--base-color: 195, 100%;
.header {
width: 100%;
aspect-ratio: 3 / 1;
background: url("/assets/bg.jpg");
background-position: center;
background-size: cover;
background-attachment: fixed;
text-align: center;
color: white;
text-shadow: 0 0.1em 0.5em black;
box-shadow: inset 0 0 1em var(--accent-color);
font-size: 1.5em;
border-radius: 2rem;
overflow: hidden;
display: grid;
align-items: center;
justify-content: center;
margin: 0.5em 0;
margin-bottom: 1em;
user-select: none;
}
.header h1 {
line-height: 1em;
margin-bottom: 0.35em;
border: none;
padding: 0;
}
.horizontal {
display: grid;
grid-auto-flow: column;
gap: 1em;
}
@keyframes wave {
0% {
transform: rotate(0deg);
}
20% {
transform: rotate(-15deg);
}
40% {
transform: rotate(15deg);
}
60% {
transform: rotate(-15deg);
}
80% {
transform: rotate(15deg);
}
100% {
transform: rotate(0deg);
}
}
.waving {
transform-origin: 60% 60%;
cursor: none;
}
.waving:hover {
animation: infinite wave 0.5s;
}
</style>
</head>
<body>
<header>
<section>
<h1>ABTMTR.LINK</h1>
</section>
<h1><small><small>welcome to</small></small> ABTMTR.LINK</h1>
<$ nav.html $>
<section id="accessibility" hidden></section>
<div id="buttonroll" hidden></div>
</header>
<main>
<section>
<h2>Welcome! 👋</h2>
<section id="data_whoami">
<p>
I'm...
<small
><a href="https://pronouns.cc/@MeowcaTheoRange" target="_blank"
>not loading</a
>.</small
>
</p>
</section>
<p>
I'm a web developer, Fediverse enthusiast, and compulsory Minnesotan.
</p>
<p>I run this domain and all of the services on it!</p>
<p id="no-fetchy" style="display: none">
For me, it is <b id="time">12:00 PM</b> on a
<b id="weekday">Sunday</b>. <b id="timezone">(UTC-0)</b><br />
<small
>data locally fetched from
<a href="https://pronouns.cc/@MeowcaTheoRange" target="_blank"
>pronouns.cc</a
></small
>
</p>
<h1>
Hey!
<span class="rs waving">👋</span>
</h1>
<p>Welcome to abtmtr.link.</p>
<p>abtmtr.link is a domain for a suite of services, ranging from <b>micro-blogging</b> to <b>web search</b>.</p>
<p>This site was once a little less personal. That should be over for now.</p>
<p>Check out our <a href="/projects/">projects</a>!</p>
</section>
<section id="data_get">
<h1>Getting domain indexes...</h1>
</section>
<section id="status"></section>
<section id="about"></section>
<button id="buttonflags" disabled>Cool Flags</button>
<section id="fields"></section>
</main>
<div id="WindowHolder"></div>
<script src="/scripts/windows.js"></script>
<script src="/scripts/accessibility.js"></script>
<script src="./scripts/data_get_whoami.js"></script>
<script src="/projects/item/hex/scripts/index.js"></script>
<script>
window.manager = new WindowManager(
document.getElementById("WindowHolder")
);
</script>
<script src="./scripts/roll_buttons.js"></script>
<script src="./scripts/data_get_domain.js"></script>
</body>
</html>

View file

@ -6,18 +6,14 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles/normal.css" />
<link rel="stylesheet" href="/styles/style.css" />
<style>
:root {
--base-color: 270, 100%;
}
</style>
<style></style>
</head>
<body>
<$ loader.html $>
<header>
<section>
<h1>Links</h1>
<p>Where else you can find me.</p>
<p>Where else you can find traces of abtmtr.link.</p>
</section>
<$ nav.html $>
<section id="accessibility" hidden></section>

View file

@ -1,5 +1,5 @@
const data_get = document.getElementById("data_get");
fetch("https://pronouns.cc/api/v1/users/MeowcaTheoRange")
fetch_ask("https://pronouns.cc/api/v1/users/clhd8desmggedm3q1t3g")
.then((x) => x.json())
.then((user) => {
data_get.innerHTML = user.links.reduce(

View file

@ -7,18 +7,14 @@
<link rel="stylesheet" href="/styles/normal.css" />
<link rel="stylesheet" href="/styles/windows.css" />
<link rel="stylesheet" href="/styles/style.css" />
<style>
:root {
--base-color: 15, 100%;
}
</style>
<style></style>
</head>
<body>
<$ loader.html $>
<header>
<section>
<h1>Projects</h1>
<p>Various projects I've made.</p>
<p>Various projects.</p>
</section>
<$ nav.html $>
<section id="accessibility" hidden></section>

View file

@ -8,14 +8,16 @@ function reqData(start) {
if (resp != prevSha) {
window.sessionStorage.setItem("commitsha", resp);
if (start) return;
document.querySelector("#updatesha").innerHTML = `from job v. ${prevSha} to job v. ${resp}`;
document.querySelector(
"#updatesha"
).innerHTML = `from job v. ${prevSha} to job v. ${resp}`;
document.querySelector("#updatedialog").classList.add("open");
}
};
fetch("https://ClockCheckGithub.meowcatheorange.repl.co")
.then(x => x.text())
.then(y => donedata(y));
fetch_ask("https://ClockCheckGithub.meowcatheorange.repl.co")
.then((x) => x.text())
.then((y) => donedata(y));
}
setInterval(reqData, 60000);

View file

@ -0,0 +1,175 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Fediverse Madness - abtmtr.link</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles/normal.css" />
<link rel="stylesheet" href="/styles/myapp.css" />
<link rel="stylesheet" href="./style.css" />
<style>
:root {
--base-color: 60, 40%;
}
</style>
</head>
<body>
<header>
<section>
<h1>Fediverse Madness</h1>
<p>Competitive bracket-based comparisons of Fediverse users.</p>
<p>Requires JavaScript.</p>
</section>
<section id="accessibility" hidden></section>
</header>
<main>
<section id="whoami" hidden>
<h3>Who are you?</h3>
<span class="userinputbox">
<span>@</span>
<input type="text" name="user" id="user" class="userinstance" />
<span>@</span>
<input type="text" name="instance" id="instance" class="userinstance" />
</span>
<br />
<hr />
<button id="submitwhoami_followers">Play (Followers)</button>
<button id="submitwhoami_following">Play (Following)</button>
<button id="submitwhoami_manual">Play (Manual)</button>
<p id="errorwhoami"></p>
</section>
<section id="followers" hidden>
<h3>Followers list</h3>
<p id="loadingfollowers">Retrieving followers... Please wait.</p>
<table id="listfollowers" hidden>
<tr id="listfollowersvalues">
<th>S.</th>
<th>Av.</th>
<th>User</th>
<th>Handle</th>
</tr>
</table>
<div>
<button id="selectallfollowers">Select all</button>
<button id="selectnofollowers">Select none</button>
<button id="selectrandomfollowers">Select random</button>
<button id="deselectrandomfollowers">Deselect random</button>
</div>
<div>
<button id="morefollowers">Load more</button>
<button id="savehandlesfollowers">Save selection</button>
<button id="loadhandlesfollowers">Load selection</button>
</div>
<button id="submitfollowers" disabled>Done</button>
</section>
<section id="following" hidden>
<h3>Following list</h3>
<p id="loadingfollowing">Retrieving following... Please wait.</p>
<table id="listfollowing" hidden>
<tr id="listfollowingvalues">
<th>S.</th>
<th>Av.</th>
<th>User</th>
<th>Handle</th>
</tr>
</table>
<div>
<button id="selectallfollowing">Select all</button>
<button id="selectnofollowing">Select none</button>
<button id="selectrandomfollowing">Select random</button>
<button id="deselectrandomfollowing">Deselect random</button>
</div>
<div>
<button id="morefollowing">Load more</button>
<button id="savehandlesfollowing">Save selection</button>
<button id="loadhandlesfollowing">Load selection</button>
</div>
<button id="submitfollowing" disabled>Done</button>
</section>
<section id="manual" hidden>
<h3>Manual list</h3>
<p id="loadingmanual"></p>
<table id="listmanual" hidden>
<tr id="listmanualvalues">
<th>S.</th>
<th>Av.</th>
<th>User</th>
<th>Handle</th>
</tr>
</table>
<div>
<button id="selectallmanual">Select all</button>
<button id="selectnomanual">Select none</button>
<button id="selectrandommanual">Select random</button>
<button id="deselectrandommanual">Deselect random</button>
</div>
<div>
<span class="userinputbox">
<span>@</span>
<input type="text" name="user" id="manualuser" class="userinstance" />
<span>@</span>
<input type="text" name="instance" id="manualinstance" class="userinstance" />
</span>
<button id="moremanual">Fetch</button>
</div>
<button id="submitmanual" disabled>Done</button>
</section>
<section id="game" hidden>
<h3>Fight!</h3>
<p id="gameBracketLevel">Bracket Level ...</p>
<p id="gameBracketFight">Round ...</p>
<div class="gridUsersVert">
<div class="gridUser" id="gameUserOne">
<img src="" id="gameUserOneImage" width="128" height="128" />
<div>
<h3 id="gameUserOneName"></h3>
<p id="gameUserOneId"></p>
<!-- <p id="gameUserOneNote"></p>
<table id="gameUserOneFields">
</table> -->
</div>
</div>
<div class="gridUser" id="gameUserTwo">
<img src="" id="gameUserTwoImage" width="128" height="128" />
<div>
<h3 id="gameUserTwoName"></h3>
<p id="gameUserTwoId"></p>
<!-- <p id="gameUserTwoNote"></p>
<table id="gameUserTwoFields">
</table> -->
</div>
</div>
</div>
<hr />
<button id="gameSubmitLeft" disabled>Please wait...</button>
<button id="gameSubmitRight" disabled>Please wait...</button>
</section>
<section id="winner" hidden>
<h3>Winner</h3>
<p id="winnermessage">Congratulations, ...</p>
<div class="gridUser" id="winnerUser">
<img src="" id="winnerUserImage" width="128" height="128" />
<div>
<h3 id="winnerUserName"></h3>
<p id="winnerUserId"></p>
<!-- <p id="winnerUserNote"></p>
<table id="winnerUserFields">
</table> -->
</div>
</div>
<h3>Bracket</h3>
<div class="flexUsersVert" id="winnerusers">
</div>
<hr />
<button id="winnerPlayAgain">Play Again</button>
</section>
</main>
<section id="accessibility" hidden></section>
<script src="/scripts/accessibility.js"></script>
<script>
let game = null;
</script>
<script src="./script.js"></script>
<script src="./scripts/game.js"></script>
</body>
</html>

View file

@ -0,0 +1,735 @@
function escapeHtml(unsafe)
{
return unsafe
.replace(/<\/?script\/?>/g, "")
.replace(/<\/?img\/?>/g, "")
.replace(/<\/?iframe\/?>/g, "")
.replace(/<\/?xml\/?>/g, "")
.replace(/<\/?audio\/?>/g, "")
.replace(/<\/?video\/?>/g, "")
.replace(/<\/?object\/?>/g, "");
}
const n=x=>null;
const el_id_user = document.querySelector("#user");
const el_id_instance = document.querySelector("#instance");
const el_id_whoami = document.querySelector("#whoami");
const el_id_submitwhoamiFollowers = document.querySelector("#submitwhoami_followers");
const el_id_submitwhoamiFollowing = document.querySelector("#submitwhoami_following");
const el_id_submitwhoamiManual = document.querySelector("#submitwhoami_manual");
const el_id_errorwhoami = document.querySelector("#errorwhoami");
let gamemodeFollowers = "";
el_id_user.addEventListener("input", (e) => {
console.log(e);
// User types @ Chrome autofill
if (e.data === "@" || e.data === undefined) {
el_id_user.value =
el_id_user.value.replace(/@/gim, "");
el_id_instance.focus();
}
})
async function verify() {
const username = el_id_user.value.replace(/[^a-z0-9_]/gim, "");
const instance = el_id_instance.value.replace(/@/gim, "");
el_id_user.value = username;
el_id_instance.value = instance;
if (username.length < 1) return null;
if (instance.length < 1) return null;
// webfinger domain delegation
const domain = await fetch(`https://${instance}/.well-known/webfinger?resource=acct:${username}@${instance}`).catch(n);
if (domain == null || !domain.ok) return null;
const domain_json = (await domain.json());
const domain_url = new URL(domain_json.links.find(x=>x.type=="application/activity+json").href).hostname;
const user_req = await fetch(`https://${domain_url}/api/v1/accounts/lookup?acct=${username}`).catch(n);
if (user_req == null || !domain.ok) return null;
const user_json = await user_req.json();
window.localStorage.setItem("fediversemadness_game", JSON.stringify({ username, instance }));
return {
USER_ID: user_json.id,
INSTANCE: domain_url
};
}
async function lookupUser() {
const lsGame = JSON.parse(window.localStorage.getItem("fediversemadness_game"));
if (lsGame != null) {
el_id_user.value = lsGame.username;
el_id_instance.value = lsGame.instance;
}
}
lookupUser();
async function doVerif() {
el_id_submitwhoamiFollowers.disabled = true;
el_id_submitwhoamiFollowing.disabled = true;
el_id_submitwhoamiManual.disabled = true;
game = await verify();
el_id_submitwhoamiFollowers.disabled = false;
el_id_submitwhoamiFollowing.disabled = false;
el_id_submitwhoamiManual.disabled = false;
}
el_id_submitwhoamiFollowers.addEventListener("click", async (e) => {
el_id_errorwhoami.innerHTML = "";
await doVerif();
if (game == null) {
el_id_errorwhoami.innerHTML = "Invalid user!";
return false;
}
gamemodeFollowers = "followers";
startFollowers();
})
el_id_submitwhoamiFollowing.addEventListener("click", async (e) => {
el_id_errorwhoami.innerHTML = "";
await doVerif();
if (game == null) {
el_id_errorwhoami.innerHTML = "Invalid user!";
return false;
}
gamemodeFollowers = "following";
startFollowing();
})
el_id_submitwhoamiManual.addEventListener("click", async (e) => {
el_id_errorwhoami.innerHTML = "";
await doVerif();
if (game == null) {
el_id_errorwhoami.innerHTML = "Invalid user!";
return false;
}
gamemodeFollowers = "manual";
startManual();
})
// Followers
const el_id_followers = document.querySelector("#followers");
const el_id_loadingfollowers = document.querySelector("#loadingfollowers");
const el_id_listfollowers = document.querySelector("#listfollowers");
const el_id_listfollowersvalues = document.querySelector("#listfollowersvalues");
const el_id_selectallfollowers = document.querySelector("#selectallfollowers");
const el_id_selectrandomfollowers = document.querySelector("#selectrandomfollowers");
const el_id_deselectrandomfollowers = document.querySelector("#deselectrandomfollowers");
const el_id_selectnofollowers = document.querySelector("#selectnofollowers");
const el_id_morefollowers = document.querySelector("#morefollowers");
const el_id_submitfollowers = document.querySelector("#submitfollowers");
const el_id_savehandlesfollowers = document.querySelector("#savehandlesfollowers");
const el_id_loadhandlesfollowers = document.querySelector("#loadhandlesfollowers");
let selectboxes = [];
let userList = [];
let lastId = "";
let selectedUsers = [];
function generateFollowersMap() {
const checks = Array.from(el_id_listfollowers.querySelectorAll(".follower_checkbox"));
let codeMap = {};
checks.forEach((checkbox) => {
const handle = checkbox.parentElement.parentElement.querySelector(".follower_checkname");
codeMap[handle.innerHTML] = checkbox.checked;
});
return codeMap;
}
function readFollowersMap(m) {
if (m == null) return;
const handles = Array.from(el_id_listfollowers.querySelectorAll(".follower_checkname"));
handles.forEach((handle) => {
const checkbox = handle.parentElement.querySelector(".follower_checkbox");
if (m[handle.innerHTML] != null)
checkbox.checked = m[handle.innerHTML];
});
}
function renderNameHTML(name, user) {
return `<span>${escapeHtml(name).replace(/:([a-z0-9_-]+?):/gim, (m, p1) => {
const emoji = user.emojis.find(x => x.shortcode == p1);
if (emoji == null) return null;
return `<img src="${emoji.url}" width="16" height="16" title=":${emoji.shortcode}:" />`;
})}</span>`;
}
function startFollowers(reload = true) {
goToState("followers");
if (reload) {
selectboxes = [];
userList = [];
lastId = "";
selectedUsers = [];
el_id_listfollowers.innerHTML = "";
}
getFollowers(!reload);
}
async function getFollowers(dontLoadNew = false) {
el_id_submitfollowers.disabled = true;
el_id_morefollowers.disabled = true;
let res;
let out;
if (!dontLoadNew) {
el_id_loadingfollowers.innerHTML = `Retrieving followers... Please wait.`;
res = await fetch(`https://${game.INSTANCE}/api/v1/accounts/${game.USER_ID}/followers${lastId.length > 0 ? `?max_id=${lastId}` : ""}`);
out = await res.json();
}
el_id_submitfollowers.disabled = false;
el_id_morefollowers.disabled = false;
if (dontLoadNew) return false;
if (out.length < 1) {
el_id_loadingfollowers.innerHTML = `No followers found.`;
return false;
}
el_id_listfollowers.hidden = false;
out.forEach((cuser) => {
const element = document.createElement("tr");
element.innerHTML = `<td><input type="checkbox" class="follower_checkbox" checked onchange="checkSelectedAmtFollowers()" /></td>
<td><img src="${cuser.avatar}" width="32" height="32" /></td>
<td class="follower_namelabel">
<a href="${cuser.url}" target="_blank">
${renderNameHTML(cuser.display_name, cuser)}
</a>
</td>
<td class="follower_namelabel follower_checkname">@${cuser.fqn}</td>`;
el_id_listfollowers.appendChild(element);
});
userList.push(...out.map(user => ({
fqn: user.fqn || user.acct,
avatar: user.avatar,
bot: user.bot,
created_at: user.created_at,
display_name: user.display_name,
emojis: user.emojis,
fields: user.fields,
id: user.id,
note: user.note,
url: user.url
})));
lastId = userList.at(-1).id;
selectboxes = Array.from(el_id_listfollowers.querySelectorAll(".follower_checkbox"));
checkSelectedAmtFollowers();
}
el_id_selectallfollowers.addEventListener("click", () => {
selectboxes.forEach(x => x.checked = true);
checkSelectedAmtFollowers();
})
el_id_selectrandomfollowers.addEventListener("click", () => {
const unselectedPick = selectboxes.filter(x => !x.checked);
const selected = unselectedPick[Math.floor(Math.random() * (unselectedPick.length - 1))];
if (selected == null) return;
selected.checked = true;
checkSelectedAmtFollowers();
})
el_id_deselectrandomfollowers.addEventListener("click", () => {
const selectedPick = selectboxes.filter(x => x.checked);
const selected = selectedPick[Math.floor(Math.random() * (selectedPick.length - 1))];
if (selected == null) return;
selected.checked = false;
checkSelectedAmtFollowers();
})
el_id_selectnofollowers.addEventListener("click", () => {
selectboxes.forEach(x => x.checked = false);
checkSelectedAmtFollowers();
})
el_id_savehandlesfollowers.addEventListener("click", () => window.localStorage.setItem("fediversemadness_followerssel", JSON.stringify(generateFollowersMap())));
el_id_loadhandlesfollowers.addEventListener("click", () => readFollowersMap(JSON.parse(window.localStorage.getItem("fediversemadness_followerssel"))));
el_id_morefollowers.addEventListener("click", () => getFollowers());
el_id_submitfollowers.addEventListener("click", () => {
selectedUsers = [];
selectboxes.forEach(({checked}, i) => {
if (checked) selectedUsers.push(userList.at(i));
});
if (selectedUsers.length < 2) {
el_id_loadingfollowers.innerHTML = `Selected user count is less than 2! (${selectboxes.filter(x => x.checked).length} selected)`;
return;
}
sortSelectedUsers();
})
function checkSelectedAmtFollowers() {
el_id_loadingfollowers.innerHTML = `${selectboxes.filter(x => x.checked).length} selected`;
}
// Following
const el_id_following = document.querySelector("#following");
const el_id_loadingfollowing = document.querySelector("#loadingfollowing");
const el_id_listfollowing = document.querySelector("#listfollowing");
const el_id_listfollowingvalues = document.querySelector("#listfollowingvalues");
const el_id_selectallfollowing = document.querySelector("#selectallfollowing");
const el_id_selectrandomfollowing = document.querySelector("#selectrandomfollowing");
const el_id_deselectrandomfollowing = document.querySelector("#deselectrandomfollowing");
const el_id_selectnofollowing = document.querySelector("#selectnofollowing");
const el_id_morefollowing = document.querySelector("#morefollowing");
const el_id_submitfollowing = document.querySelector("#submitfollowing");
const el_id_savehandlesfollowing = document.querySelector("#savehandlesfollowing");
const el_id_loadhandlesfollowing = document.querySelector("#loadhandlesfollowing");
function generateFollowingMap() {
const checks = Array.from(el_id_listfollowing.querySelectorAll(".following_checkbox"));
let codeMap = {};
checks.forEach((checkbox) => {
const handle = checkbox.parentElement.parentElement.querySelector(".following_checkname");
codeMap[handle.innerHTML] = checkbox.checked;
});
return codeMap;
}
function readFollowingMap(m) {
if (m == null) return;
const handles = Array.from(el_id_listfollowing.querySelectorAll(".following_checkname"));
handles.forEach((handle) => {
const checkbox = handle.parentElement.querySelector(".following_checkbox");
if (m[handle.innerHTML] != null)
checkbox.checked = m[handle.innerHTML];
});
}
function startFollowing(reload = true) {
goToState("following");
if (reload) {
selectboxes = [];
userList = [];
lastId = "";
selectedUsers = [];
el_id_listfollowing.innerHTML = "";
}
getFollowing(!reload);
}
async function getFollowing(dontLoadNew = false) {
el_id_submitfollowing.disabled = true;
el_id_morefollowing.disabled = true;
let res;
let out;
if (!dontLoadNew) {
el_id_loadingfollowing.innerHTML = `Retrieving following... Please wait.`;
res = await fetch(`https://${game.INSTANCE}/api/v1/accounts/${game.USER_ID}/following${lastId.length > 0 ? `?max_id=${lastId}` : ""}`);
out = await res.json();
}
el_id_submitfollowing.disabled = false;
el_id_morefollowing.disabled = false;
if (dontLoadNew) return false;
if (out.length < 1) {
el_id_loadingfollowing.innerHTML = `No following found.`;
return false;
}
el_id_listfollowing.hidden = false;
out.forEach((cuser) => {
const element = document.createElement("tr");
element.innerHTML = `<td><input type="checkbox" class="following_checkbox" checked onchange="checkSelectedAmtFollowing()" /></td>
<td><img src="${cuser.avatar}" width="32" height="32" /></td>
<td class="following_namelabel">
<a href="${cuser.url}" target="_blank">
${renderNameHTML(cuser.display_name, cuser)}
</a>
</td>
<td class="following_namelabel following_checkname">@${cuser.fqn}</td>`;
el_id_listfollowing.appendChild(element);
});
userList.push(...out.map(user => ({
fqn: user.fqn,
avatar: user.avatar,
bot: user.bot,
created_at: user.created_at,
display_name: user.display_name,
emojis: user.emojis,
fields: user.fields,
id: user.id,
note: user.note,
url: user.url
})));
lastId = userList.at(-1).id;
selectboxes = Array.from(el_id_listfollowing.querySelectorAll(".following_checkbox"));
checkSelectedAmtFollowing();
}
el_id_selectallfollowing.addEventListener("click", () => {
selectboxes.forEach(x => x.checked = true);
checkSelectedAmtFollowing();
})
el_id_selectrandomfollowing.addEventListener("click", () => {
const unselectedPick = selectboxes.filter(x => !x.checked);
const selected = unselectedPick[Math.floor(Math.random() * (unselectedPick.length - 1))];
if (selected == null) return;
selected.checked = true;
checkSelectedAmtFollowing();
})
el_id_deselectrandomfollowing.addEventListener("click", () => {
const selectedPick = selectboxes.filter(x => x.checked);
const selected = selectedPick[Math.floor(Math.random() * (selectedPick.length - 1))];
if (selected == null) return;
selected.checked = false;
checkSelectedAmtFollowing();
})
el_id_selectnofollowing.addEventListener("click", () => {
selectboxes.forEach(x => x.checked = false);
checkSelectedAmtFollowing();
})
el_id_savehandlesfollowing.addEventListener("click", () => window.localStorage.setItem("fediversemadness_followingsel", JSON.stringify(generateFollowingMap())));
el_id_loadhandlesfollowing.addEventListener("click", () => readFollowingMap(JSON.parse(window.localStorage.getItem("fediversemadness_followingsel"))));
el_id_morefollowing.addEventListener("click", () => getFollowing());
el_id_submitfollowing.addEventListener("click", () => {
selectedUsers = [];
selectboxes.forEach(({checked}, i) => {
if (checked) selectedUsers.push(userList.at(i));
});
if (selectedUsers.length < 2) {
el_id_loadingfollowing.innerHTML = `Selected user count is less than 2! (${selectboxes.filter(x => x.checked).length} selected)`;
return;
}
sortSelectedUsers();
})
function checkSelectedAmtFollowing() {
el_id_loadingfollowing.innerHTML = `${selectboxes.filter(x => x.checked).length} selected`;
}
// Manual
const el_id_manual = document.querySelector("#manual");
const el_id_loadingmanual = document.querySelector("#loadingmanual");
const el_id_listmanual = document.querySelector("#listmanual");
const el_id_listmanualvalues = document.querySelector("#listmanualvalues");
const el_id_selectallmanual = document.querySelector("#selectallmanual");
const el_id_selectrandommanual = document.querySelector("#selectrandommanual");
const el_id_deselectrandommanual = document.querySelector("#deselectrandommanual");
const el_id_selectnomanual = document.querySelector("#selectnomanual");
const el_id_manualuser = document.querySelector("#manualuser");
const el_id_manualinstance = document.querySelector("#manualinstance");
const el_id_moremanual = document.querySelector("#moremanual");
const el_id_submitmanual = document.querySelector("#submitmanual");
function startManual(reload = true) {
goToState("manual");
if (reload) {
selectboxes = [];
userList = [];
lastId = "";
selectedUsers = [];
el_id_listmanual.innerHTML = "";
}
checkSelectedAmtManual();
}
async function addUserToManual() {
const username = el_id_manualuser.value.replace(/[^a-z0-9_]/gim, "");
const instance = el_id_manualinstance.value.replace(/@/gim, "");
el_id_manualuser.value = username;
el_id_manualinstance.value = instance;
if (username.length < 1) return null;
if (instance.length < 1) return null;
el_id_submitmanual.disabled = true;
el_id_moremanual.disabled = true;
el_id_loadingmanual.innerHTML = `Finding user...`;
let res = await fetch(`https://${game.INSTANCE}/api/v1/accounts/lookup?acct=${username}@${instance}`);
let out = await res.json();
el_id_submitmanual.disabled = false;
el_id_moremanual.disabled = false;
if (out.length < 1) {
el_id_loadingmanual.innerHTML = `No manual found.`;
return false;
}
el_id_listmanual.hidden = false;
const element = document.createElement("tr");
element.innerHTML = `<td><input type="checkbox" class="manual_checkbox" checked onchange="checkSelectedAmtManual()" /></td>
<td><img src="${out.avatar}" width="32" height="32" /></td>
<td class="manual_namelabel">
<a href="${out.url}" target="_blank">
${renderNameHTML(out.display_name, out)}
</a>
</td>
<td class="manual_namelabel manual_checkname">@${out.fqn}</td>`;
el_id_listmanual.appendChild(element);
userList.push({
fqn: out.fqn,
avatar: out.avatar,
bot: out.bot,
created_at: out.created_at,
display_name: out.display_name,
emojis: out.emojis,
fields: out.fields,
id: out.id,
note: out.note,
url: out.url
});
selectboxes = Array.from(el_id_listmanual.querySelectorAll(".manual_checkbox"));
checkSelectedAmtManual();
}
el_id_selectallmanual.addEventListener("click", () => {
selectboxes.forEach(x => x.checked = true);
checkSelectedAmtManual();
})
el_id_selectrandommanual.addEventListener("click", () => {
const unselectedPick = selectboxes.filter(x => !x.checked);
const selected = unselectedPick[Math.floor(Math.random() * (unselectedPick.length - 1))];
if (selected == null) return;
selected.checked = true;
checkSelectedAmtManual();
})
el_id_deselectrandommanual.addEventListener("click", () => {
const selectedPick = selectboxes.filter(x => x.checked);
const selected = selectedPick[Math.floor(Math.random() * (selectedPick.length - 1))];
if (selected == null) return;
selected.checked = false;
checkSelectedAmtManual();
})
el_id_selectnomanual.addEventListener("click", () => {
selectboxes.forEach(x => x.checked = false);
checkSelectedAmtManual();
})
el_id_moremanual.addEventListener("click", () => addUserToManual());
el_id_submitmanual.addEventListener("click", () => {
selectedUsers = [];
selectboxes.forEach(({checked}, i) => {
if (checked) selectedUsers.push(userList.at(i));
});
if (selectedUsers.length < 2) {
el_id_loadingmanual.innerHTML = `Selected user count is less than 2! (${selectboxes.filter(x => x.checked).length} selected)`;
return;
}
sortSelectedUsers();
})
function checkSelectedAmtManual() {
el_id_loadingmanual.innerHTML = `${selectboxes.filter(x => x.checked).length} selected`;
}
// Brackets
function shuf(array) {
var count = array.length,
randomnumber,
temp;
while (count) {
randomnumber = Math.random() * count-- | 0;
temp = array[count];
array[count] = array[randomnumber];
array[randomnumber] = temp
}
return array;
}
function chunk(arr, size) {
return Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
arr.slice(i * size, i * size + size)
);
}
const el_id_game = document.querySelector("#game");
const el_id_gameUserOne = document.querySelector("#gameUserOne");
const el_id_gameUserOneImage = document.querySelector("#gameUserOneImage");
const el_id_gameUserOneName = document.querySelector("#gameUserOneName");
const el_id_gameUserOneId = document.querySelector("#gameUserOneId");
// const el_id_gameUserOneNote = document.querySelector("#gameUserOneNote");
// const el_id_gameUserOneFields = document.querySelector("#gameUserOneFields");
const el_id_gameUserTwo = document.querySelector("#gameUserTwo");
const el_id_gameUserTwoImage = document.querySelector("#gameUserTwoImage");
const el_id_gameUserTwoName = document.querySelector("#gameUserTwoName");
const el_id_gameUserTwoId = document.querySelector("#gameUserTwoId");
// const el_id_gameUserTwoNote = document.querySelector("#gameUserTwoNote");
// const el_id_gameUserTwoFields = document.querySelector("#gameUserTwoFields");
const el_id_gameSubmitLeft = document.querySelector("#gameSubmitLeft");
const el_id_gameSubmitRight = document.querySelector("#gameSubmitRight");
const el_id_gameBracketLevel = document.querySelector("#gameBracketLevel");
const el_id_gameBracketFight = document.querySelector("#gameBracketFight");
let state = [];
let curDepth = 0;
let curFight = 0;
function sortSelectedUsers() {
goToState("game");
state = [];
curDepth = 0;
curFight = 0;
state.push(chunk(shuf(selectedUsers), 2));
state.push([]);
prepareGameStage();
}
function prepareGameStage() {
el_id_gameSubmitLeft.innerHTML = "Please wait...";
el_id_gameSubmitRight.innerHTML = "Please wait...";
el_id_gameUserOneImage.src = "";
el_id_gameUserTwoImage.src = "";
el_id_gameBracketLevel.innerHTML = "Bracket Level " + (curDepth + 1);
el_id_gameBracketFight.innerHTML = "Round " + (curFight + 1);
const curSubStage = state[curDepth][curFight];
el_id_gameUserOneImage.src = curSubStage[0].avatar;
el_id_gameUserOneName.innerHTML = `<a href="${curSubStage[0].url}" target="_blank">${renderNameHTML(escapeHtml(curSubStage[0].display_name), curSubStage[0])}</a>`;
el_id_gameUserOneId.innerHTML = "@" + curSubStage[0].fqn;
// el_id_gameUserOneNote.innerHTML = escapeHtml(curSubStage[0].note);
// el_id_gameUserOneFields.innerHTML = curSubStage[0].fields.reduce((pv, fields) => pv + `<tr><td>${escapeHtml(fields.name)}</td><td>${escapeHtml(fields.value)}</td></tr>`, "");
el_id_gameUserTwoImage.src = curSubStage[1].avatar;
el_id_gameUserTwoName.innerHTML = `<a href="${curSubStage[1].url}" target="_blank">${renderNameHTML(escapeHtml(curSubStage[1].display_name), curSubStage[1])}</a>`;
el_id_gameUserTwoId.innerHTML = "@" + curSubStage[1].fqn;
// el_id_gameUserTwoNote.innerHTML = escapeHtml(curSubStage[1].note);
// el_id_gameUserTwoFields.innerHTML = curSubStage[1].fields.reduce((pv, fields) => pv + `<tr><td>${escapeHtml(fields.name)}</td><td>${escapeHtml(fields.value)}</td></tr>`, "");
el_id_gameSubmitLeft.disabled = false;
el_id_gameSubmitRight.disabled = false;
el_id_gameSubmitLeft.innerHTML = "Pick " + "@" + curSubStage[0].fqn;
el_id_gameSubmitRight.innerHTML = "Pick " + "@" + curSubStage[1].fqn;
}
function addNewThingy(thingy) {
if (state[curDepth + 1] == null) state.push([]);
if (curFight % 2 == 0)
state[curDepth + 1][Math.floor(curFight / 2)] = [];
state[curDepth + 1][Math.floor(curFight / 2)].push(thingy);
}
el_id_gameSubmitLeft.addEventListener("click", () => {
addNewThingy(state[curDepth][curFight][0]);
nextStage();
})
el_id_gameSubmitRight.addEventListener("click", () => {
addNewThingy(state[curDepth][curFight][1]);
nextStage();
})
function nextStage() {
if (curFight >= state[curDepth].length - 1) {
curDepth++;
curFight = 0;
} else curFight++;
console.log(state, curFight, curDepth);
if (state[curDepth][curFight].length < 2) {
if (state[curDepth].length < 2) {
endGame();
return;
}
addNewThingy(state[curDepth][curFight][0]);
curDepth++;
curFight = 0;
}
prepareGameStage();
}
// End
const el_id_winner = document.querySelector("#winner");
const el_id_winnerUser = document.querySelector("#winnerUser");
const el_id_winnerUserImage = document.querySelector("#winnerUserImage");
const el_id_winnerUserName = document.querySelector("#winnerUserName");
const el_id_winnerUserId = document.querySelector("#winnerUserId");
// const el_id_winnerUserNote = document.querySelector("#winnerUserNote");
// const el_id_winnerUserFields = document.querySelector("#winnerUserFields");
const el_id_winnermessage = document.querySelector("#winnermessage");
const el_id_winnerusers = document.querySelector("#winnerusers");
const el_id_winnerPlayAgain = document.querySelector("#winnerPlayAgain");
function endGame() {
goToState("winner");
const winningPlayer = state[curDepth][curFight][0];
el_id_winnerUserImage.src = winningPlayer.avatar;
el_id_winnerUserName.innerHTML = `<a href="${winningPlayer.url}" target="_blank">${renderNameHTML(escapeHtml(winningPlayer.display_name), winningPlayer)}</a>`;
el_id_winnerUserId.innerHTML = "@" + winningPlayer.fqn;
// el_id_winnerUserNote.innerHTML = escapeHtml(winningPlayer.note);
// el_id_winnerUserFields.innerHTML = winningPlayer.fields.reduce((pv, fields) => pv + `<tr><td>${escapeHtml(fields.name)}</td><td>${escapeHtml(fields.value)}</td></tr>`, "");
el_id_winnermessage.innerHTML = `Congratulations, ${renderNameHTML(escapeHtml(winningPlayer.display_name), winningPlayer)}! You won the bracket!`;
el_id_winnerusers.innerHTML = state.reduce((pv, cs) => {
return pv + `<div class="flexUser">${cs.reduce((pvus, cus) => {
console.log(cus);
return pvus + `<div class="flexUserGroup"><div class="flexUserUser">
<img src="${cus[0].avatar}" width="64" height="64" />
<h3><a href="${cus[0].url}" target="_blank">${renderNameHTML(escapeHtml(cus[0].display_name), cus[0])}</a></h3>
<p>@${cus[0].fqn}</p>
</div>${cus[1] ? `<div class="flexUserUser">
<img src="${cus[1].avatar}" width="64" height="64" />
<h3><a href="${cus[1].url}" target="_blank">${renderNameHTML(escapeHtml(cus[1].display_name), cus[1])}</a></h3>
<p>@${cus[1].fqn}</p>
</div>` : ""}</div>`
}, "")}</div>`
}, "");
}
el_id_winnerPlayAgain.addEventListener("click", (x) => {
state = [];
selectedUsers = [];
curDepth = 0;
curFight = 0;
switch (gamemodeFollowers) {
case "followers":
startFollowers(false);
break;
case "following":
startFollowing(false);
break;
case "manual":
startManual(false);
break;
}
})
// Game state
const gameStates = new Map([
["whoami", {
state: el_id_whoami
}],
["followers", {
state: el_id_followers
}],
["following", {
state: el_id_following
}],
["manual", {
state: el_id_manual
}],
["game", {
state: el_id_game,
links: {
"winner": () => gamemodeFollowers
}
}],
["winner", {
state: el_id_winner
}]
]);
let prevState = "";
function goToState(state) {
window.location.hash = state;
const curState = gameStates.get(state);
if (curState.links != null)
if (curState.links[prevState])
return goToState(curState.links[prevState]());
gameStates.forEach(x => x.state.hidden = true);
curState.state.hidden = false;
prevState = state;
}
goToState("whoami");
addEventListener("popstate", () => goToState(window.location.hash.slice(1)));

View file

@ -0,0 +1,91 @@
.userinstance {
border: 0;
margin: 0;
padding: 0;
vertical-align: middle;
}
.userinputbox {
display: inline-block;
border: var(--border-width) var(--border-style) var(--border-color);
border-radius: var(--border-radius);
padding-inline: 0.25em;
padding-block: 0.25em;
white-space: nowrap;
}
/* .userinputbox * {
vertical-align: 0%;
} */
#listfollowers, #listfollowing {
display: inline-block;
position: relative;
width: 100%;
max-height: 31.25em;
overflow:auto;
}
#listfollowers #listfollowersvalues,
#listfollowing #listfollowingvalues {
position: sticky;
top: 0;
background-color: var(--background-color);
}
.followers_namelabel,
.following_namelabel {
max-width: 20em;
}
.gridUser {
display: grid;
grid-template-columns: 128px auto;
overflow: auto;
gap: 0.5em;
}
.gridUsersVert {
display: grid;
overflow: auto;
gap: 0.5em;
}
.flexUsersVert {
display: flex;
flex-direction: row;
align-items: stretch;
overflow: auto;
gap: 0.5em;
}
.flexUser {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: space-around;
gap: 0.5em;
}
.flexUserUser {
text-align: center;
width: 256px;
min-width: 256px;
height: 150px;
min-height: 150px;
box-sizing: content-box;
overflow: hidden;
word-wrap: break-word;
}
.flexUserGroup {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: space-around;
gap: 0.5em;
}
.flexUserUser * {
word-wrap: break-word;
}

View file

@ -5,12 +5,58 @@
"normalize": "#808080"
},
"items": [
{
"name": "Fediverse Madness",
"date": 1710804730000,
"description": [
"A March Madness style bracket-based competition for the entities on the Fediverse.",
"Compare your friends! Have a meltdown-sized crisis over who you like more!",
"This project will make you do it.",
"CONTENT WARNING // uncomfortable decisions"
],
"url": "/projects/item/fediverse-madness/",
"tags": [
"normalize"
]
},
{
"name": "LastFMDownloader",
"date": 1708382160000,
"description": [
"Downloader from Last.fm scrobble to MP3 file. Uploads to Nextcloud because it can.",
"Not piracy. I don't think so?"
],
"url": "https://git.abtmtr.link/MeowcaTheoRange/LastFMDownloader",
"hotlink": true
},
{
"name": "XKCDViewer",
"date": 1702875360000,
"description": [
"Unofficial XKCD Reader for Roku®."
],
"url": "https://git.abtmtr.link/MeowcaTheoRange/XKCDViewer",
"hotlink": true
},
{
"name": "Karkat Public License",
"date": 1704316500000,
"description": [
"The Karkat Public License"
],
"url": "https://git.abtmtr.link/MeowcaTheoRange/KarkatPublicLicense",
"hotlink": true
},
{
"name": "DiceApp",
"date": 1655589660000,
"description": ["An app. For managing virtual dice."],
"description": [
"An app. For managing virtual dice."
],
"url": "/projects/item/dice/",
"tags": ["old"]
"tags": [
"old"
]
},
{
"name": "FunnyClock²",
@ -19,16 +65,9 @@
"FunnyClock² is the successor of FunnyClock, an application made for my school to show when smartboards are idle."
],
"url": "/projects/item/clock/",
"tags": ["old"]
},
{
"name": "MTRUWSaDMfHTML5",
"date": 1701743828350,
"description": [
"MeowcaTheoRange's Unnecessary Windowing System and Desktop Manager for HTML5.<br />Works best outside of windowed mode."
],
"url": "/projects/item/wm/",
"tags": ["normalize", "new"]
"tags": [
"old"
]
},
{
"name": "Normalize",
@ -37,7 +76,9 @@
"Normalize is a simple CSS library made to provide good-looking yet simple HTML element styles."
],
"url": "/projects/item/normalize/",
"tags": ["normalize"]
"tags": [
"normalize"
]
},
{
"name": "JellyBean's Mid-Sim",
@ -46,8 +87,13 @@
"JellyBean's Mid-Sim, a 1K rhythm game made in the HTML5-JS game engine Kaboom."
],
"url": "/projects/item/midsim/",
"size": [700, 400],
"tags": ["old"]
"size": [
700,
400
],
"tags": [
"old"
]
},
{
"name": "WozSteamGen",
@ -57,7 +103,9 @@
"It's an API-less adaptation of Steam The Woz, running completely locally."
],
"url": "/projects/item/woz/",
"tags": ["normalize"]
"tags": [
"normalize"
]
},
{
"name": "HexFlagGen",
@ -67,7 +115,9 @@
"I dislike DRM and support trans rights."
],
"url": "/projects/item/hex/",
"tags": ["normalize"]
"tags": [
"normalize"
]
}
]
}
}

View file

@ -1,5 +1,6 @@
const data_get = document.getElementById("data_get");
fetch("./public/projects.json")
fetch_ask("./public/projects.json")
.then((x) => x.json())
.then((projects) => {
data_get.innerHTML = projects.items
@ -26,7 +27,7 @@ fetch("./public/projects.json")
(html, descfragment) => html + `<p>${descfragment}</p>`,
""
)}
<p>
${project.hotlink ? `` : `<p>
<button
onclick="window.manager.createWindow('${project.url}', false${
project.size?.[0] ? ", " + project.size[0] : ""
@ -34,7 +35,7 @@ fetch("./public/projects.json")
>
Open Window
</button>
</p>`,
</p>`}`,
""
);
});
});

96
views/robots.txt Normal file
View file

@ -0,0 +1,96 @@
# https://seirdy.one/robots.txt
User-agent: *
Disallow: /noindex/
Disallow: /misc/
# I opt out of online advertising so malware that injects ads on my site won't get paid.
# You should do the same. my ads.txt file contains a standard placeholder to forbid any
# compliant ad networks from paying for ad placement on my domain.
User-Agent: Adsbot
Disallow: /
Allow: /ads.txt
Allow: /app-ads.txt
## IP-violation scanners ##
# The next three are borrowed from https://www.videolan.org/robots.txt
# > This robot collects content from the Internet for the sole purpose of # helping educational institutions prevent plagiarism. [...] we compare student papers against the content we find on the Internet to see if we # can find similarities. (http://www.turnitin.com/robot/crawlerinfo.html)
# --> fuck off.
User-Agent: TurnitinBot
Disallow: /
# > NameProtect engages in crawling activity in search of a wide range of brand and other intellectual property violations that may be of interest to our clients. (http://www.nameprotect.com/botinfo.html)
# --> fuck off.
User-Agent: NPBot
Disallow: /
# iThenticate is a new service we have developed to combat the piracy of intellectual property and ensure the originality of written work for# publishers, non-profit agencies, corporations, and newspapers. (http://www.slysearch.com/)
# --> fuck off.
User-Agent: SlySearch
Disallow: /
# BLEXBot assists internet marketers to get information on the link structure of sites and their interlinking on the web, to avoid any technical and possible legal issues and improve overall online experience. (http://webmeup-crawler.com/)
# --> fuck off.
User-Agent: BLEXBot
Disallow: /
# Providing Intellectual Property professionals with superior brand protection services by artfully merging the latest technology with expert analysis. (https://www.checkmarknetwork.com/spider.html/)
# "The Internet is just way to big to effectively police alone." (ACTUAL quote)
# --> fuck off.
User-agent: CheckMarkNetwork/1.0 (+https://www.checkmarknetwork.com/spider.html)
Disallow: /
# Stop trademark violations and affiliate non-compliance in paid search. Automatically monitor your partner and affiliates online marketing to protect yourself from harmful brand violations and regulatory risks. We regularly crawl websites on behalf of our clients to ensure content compliance with brand and regulatory guidelines. (https://www.brandverity.com/why-is-brandverity-visiting-me)
# --> fuck off.
User-agent: BrandVerity/1.0
Disallow: /
## Misc. icky stuff ##
# Pipl assembles online identity information from multiple independent sources to create the most complete picture of a digital identity and connect it to real people and their offline identity records. When all the fragments of online identity data are collected, connected, and corroborated, the result is a more trustworthy identity.
# --> fuck off.
User-agent: PiplBot
Disallow: /
## Gen-AI data scrapers ##
# Eat shit, OpenAI.
User-agent: ChatGPT-User
Disallow: /
User-agent: GPTBot
Disallow: /
# Official way to opt-out of Google's generative AI training:
# <https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers>
User-agent: Google-Extended
Disallow: /
# There isn't any public documentation for this AFAICT.
# Reuters thinks this works so I might as well give it a shot.
User-agent: anthropic-ai
Disallow: /
User-agent: Claude-Web
Disallow: /
# FacebookBot crawls public web pages to improve language models for our speech recognition technology.
# <https://developers.facebook.com/docs/sharing/bot/?_fb_noscript=1>
User-Agent: FacebookBot
Disallow: /
# I'm not blocking CCBot for now. It publishes a free index for anyone to use.
# Googe used this to train the initial version of Bard (now called Gemini).
# I allow CCBot since its index is also used for upstart/hobbyist search engines
# like Alexandria and for genuinely useful academic work I personally like.
# I allow Owler for similar reasons:
# <https://openwebsearch.eu/owler/#owler-opt-out>
# <https://openwebsearch.eu/common-goals-with-common-crawl/>.
# Omgilibot/Omgili is similar to CCBot, except it sells the scrape results.
# I'm not familiar enough with Omgili to make a call here.
# In the long run, my embedded robots meta-tags and headers could cover gen-AI
# I don't block cohere-ai or Perplexitybot: they don't appear to actually scrape data for LLM training purposes. The crawling powers search engines with integrated pre-trained LLMs.
# TODO: investigate whether YouBot scrapes to train its own in-house LLM.
Sitemap: https://seirdy.one/sitemap.xml

View file

@ -13,18 +13,21 @@ var accBase;
var propagateStyles = propagateStyles ?? null;
const FONTS = [
["Lexend Deca"],
["Renogare"],
["OpenDyslexic"],
["Lexend Deca", ""],
["Renogare", "Renogare"],
["OpenDyslexic", "OpenDyslexic"],
// ["Mojangles", "Minecraft"],
// ["Nintendo DS", "NDS12"],
["System Default", `-apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto,
[
"System Default",
`-apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto,
Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;,
&quot;Segoe UI Symbol&quot;`],
&quot;Segoe UI Symbol&quot;`,
],
["Sans-serif", "sans-serif"],
["Serif", "serif"],
["Mono", "monospace"]
]
["Mono", "monospace"],
];
function createAccessibilityNodes() {
document.querySelector("#accessibility").innerHTML = `<details>
@ -52,7 +55,10 @@ function createAccessibilityNodes() {
</label><br />
<label for="acc-font">Page font:
<select id="acc-font">
${FONTS.map(([name, value]) => `<option value="${value ?? name}" selected>${name}</option>`)}
${FONTS.map(
([name, value]) =>
`<option value="${value}" selected>${name}</option>`
)}
</select>
</label><br />
<label for="acc-hue">Page hue:
@ -119,22 +125,42 @@ function createAccessibilityNodes() {
accHueOverride.addEventListener("change", function (e) {
window.localStorage.setItem("acc-hue-over", e.target.checked);
changeColor(accHue?.value, accSaturation?.value, e.target.checked, accSaturationOverride?.checked);
changeColor(
accHue?.value,
accSaturation?.value,
e.target.checked,
accSaturationOverride?.checked
);
});
accHue.addEventListener("input", function (e) {
window.localStorage.setItem("acc-hue", e.target.value);
changeColor(e.target.value, accSaturation?.value, accHueOverride?.checked, accSaturationOverride?.checked);
changeColor(
e.target.value,
accSaturation?.value,
accHueOverride?.checked,
accSaturationOverride?.checked
);
});
accSaturationOverride.addEventListener("change", function (e) {
window.localStorage.setItem("acc-sat-over", e.target.checked);
changeColor(accHue?.value, accSaturation?.value, accHueOverride?.checked, e.target.checked);
changeColor(
accHue?.value,
accSaturation?.value,
accHueOverride?.checked,
e.target.checked
);
});
accSaturation.addEventListener("input", function (e) {
window.localStorage.setItem("acc-sat", e.target.value);
changeColor(accHue?.value, e.target.value, accHueOverride?.checked, accSaturationOverride?.checked);
changeColor(
accHue?.value,
e.target.value,
accHueOverride?.checked,
accSaturationOverride?.checked
);
});
accBase.addEventListener("change", function (e) {
@ -143,6 +169,20 @@ function createAccessibilityNodes() {
});
}
// For metered conenctions
async function fetch_ask(...args) {
// return new Promise(async (resolve, reject) => {
// let canFetch = window.sessionStorage.getItem("canFetch");
// if (canFetch == null) {
// canFetch = confirm("may the site fetch data for this session?");
// window.sessionStorage.setItem("canFetch", canFetch);
// }
// if (canFetch === "true") resolve(await fetch(...args));
// else reject(null);
// });
return await fetch(...args);
}
window.addEventListener("DOMContentLoaded", (e) => initializeChanges(e, true));
// on localStorage change
@ -164,8 +204,8 @@ function initializeChanges(_, loading) {
width = window.localStorage.getItem("acc-width");
}
let font = window.localStorage.getItem("acc-font");
if (font == null) {
window.localStorage.setItem("acc-font", "Lexend Deca");
if (font == null || font == "Lexend Deca") {
window.localStorage.setItem("acc-font", "");
font = window.localStorage.getItem("acc-font");
}
let hueOver = window.localStorage.getItem("acc-hue-over");
@ -205,9 +245,11 @@ function initializeChanges(_, loading) {
if (width != null && accWidth != null) accWidth.value = width;
if (font != null && accFont != null) accFont.value = font;
if (hueOver != null && accHueOverride != null) accHueOverride.checked = hueOver === "true";
if (hueOver != null && accHueOverride != null)
accHueOverride.checked = hueOver === "true";
if (hue != null && accHue != null) accHue.value = hue;
if (satOver != null && accSaturationOverride != null) accSaturationOverride.checked = satOver === "true";
if (satOver != null && accSaturationOverride != null)
accSaturationOverride.checked = satOver === "true";
if (sat != null && accSaturation != null) accSaturation.value = sat;
if (base != null && accBase != null) accBase.checked = base === "true";
@ -223,26 +265,25 @@ function changeWidth(width) {
function changeFont(font) {
document.documentElement.style.setProperty("--font-family", font);
document.documentElement.style.setProperty("--header-font-family", font);
}
const baseColor = getComputedStyle(document.documentElement).getPropertyValue("--base-color").split(", ");
const baseColor = getComputedStyle(document.documentElement)
.getPropertyValue("--base-color")
.split(", ");
function changeColor(hue, sat, hueOver, satOver) {
if (accHue != null) accHue.disabled = !hueOver;
if (accSaturation != null) accSaturation.disabled = !satOver;
document.documentElement.style.setProperty("--base-color",
(hueOver ? hue : baseColor[0])
+ ", "
+ (satOver ? (sat + "%") : baseColor[1])
document.documentElement.style.setProperty(
"--base-color",
(hueOver ? hue : baseColor[0]) + ", " + (satOver ? sat + "%" : baseColor[1])
);
if (propagateStyles != null) propagateStyles(getComputedStyle(document.documentElement));
if (propagateStyles != null)
propagateStyles(getComputedStyle(document.documentElement));
}
function changeBase(base) {
if (base) document.documentElement.classList.add("base");
else document.documentElement.classList.remove("base");

View file

@ -0,0 +1,58 @@
const data_get = document.getElementById("data_get");
function check_uptime(event, url) {
const outElement = event.target;
const startTime = performance.now();
let status, time, timer;
outElement.disabled = true;
fetch_ask(url, { mode: "no-cors", cache: "no-cache" })
.then((res) => {
const endTime = performance.now();
time = ((endTime - startTime) / 1000).toFixed(2);
outElement.innerHTML = `Check Status (${time}s)`;
})
.catch((err) => {
const endTime = performance.now();
time = ((endTime - startTime) / 1000).toFixed(2);
outElement.innerHTML = `Check Status (${time}s - Error)`;
})
.finally(() => {
window.cancelAnimationFrame(timer);
outElement.disabled = false;
});
function timeCounter() {
const curTime = performance.now();
time = (curTime - startTime) / 1000;
if (time >= 5)
outElement.innerHTML = `Check Status (${time.toFixed(
2
)}s - Taking longer than usual)`;
else outElement.innerHTML = `Check Status (${time.toFixed(2)}s - Waiting)`;
timer = window.requestAnimationFrame(timeCounter);
}
timer = window.requestAnimationFrame(timeCounter);
}
fetch_ask("https://blog.abtmtr.link/api/collections/paste/posts/domain")
.then((x) => x.json())
.then((paste) => {
const data = JSON.parse(paste.data.body);
data_get.innerHTML = data.reduce((html, subdomain) => {
const id = `${subdomain.name.replaceAll(".", "")}-uptime`;
return (
html +
`<h2><a href="${subdomain.url}" target="_blank">${
subdomain.name
}</a></h2>
${subdomain.description.map((x) => `<p>${x}</p>`).join("")}
<img src="${subdomain.src}" alt="${subdomain.name}" title="${
subdomain.name
}" />
<p>
<button
onclick="check_uptime(event, '${subdomain.url}')"
>Check Status</button>
<span id="${id}"></span></p>`
);
}, "");
});

View file

@ -1,77 +0,0 @@
const data_whoami = document.getElementById("data_whoami");
const time = document.getElementById("time");
const weekday = document.getElementById("weekday");
const timezone = document.getElementById("timezone");
const nofetchy = document.getElementById("no-fetchy");
const about = document.getElementById("about");
const statusus = document.getElementById("status");
const buttonflags = document.getElementById("buttonflags");
const fields = document.getElementById("fields");
const URL_REGEXP =
/(([a-z0-9.-]*)(((:\/\/)([a-z0-9.-]*))((\/)([\w;,\/?:@&=+$\-_.!~*'()#]*))?)|(((mailto|tel):)([\w;,\/?:@&=+$\-_.!~*'()#]*)))/g;
fetch("https://pronouns.cc/api/v1/users/MeowcaTheoRange")
.then((x) => x.json())
.then((user) => {
data_whoami.innerHTML = `
<p>I'm <a href="https://pronouns.cc/@MeowcaTheoRange" target="_blank"><b>${
user.names[0].value
}</b></a>, also better known online as <b>${
user.name
}</b>. <small>(${user.pronouns
.filter((pronoun) => pronoun.status == "okay")
.map((pronoun) => pronoun.pronouns.split("/")[0])
.join("/")})</small></p>`;
about.innerHTML = `<h2>Bio</h2><p>MeowcaTheoRange<br />${user.bio
.replace(URL_REGEXP, (m) => `<a href="${m}" target="_blank">${m}</a>`)
.replaceAll("\n", "<br />")}</p>`;
const curTime = new Date();
timezone.innerHTML = `(UTC${user.utc_offset > 0 ? "+" : "-"}${Math.abs(
user.utc_offset / (60 * 60)
)})`;
buttonflags.onclick = () => {
user.flags.forEach((flag) => {
const props = flag.description.split(";");
initDocument(
props[0].split(","),
props[1],
props[2],
props[3],
flag.name
);
});
};
buttonflags.disabled = false;
function updateTime() {
curTime.setTime(Date.now());
time.innerHTML = curTime.toLocaleTimeString("en-us", {
timeZone: "America/Chicago",
});
weekday.innerHTML = curTime.toLocaleString("en-us", {
weekday: "long",
timeZone: "America/Chicago",
});
window.requestAnimationFrame(updateTime);
}
window.requestAnimationFrame(updateTime);
nofetchy.style.display = null;
fields.innerHTML = user.fields
.map(
(fieldset) => `
<section>
<h2>${fieldset.name}</h2>
<ul>${fieldset.entries
.map((entry) => `<li>${entry.value}</li>`)
.join("")}</ul>
</section>`
)
.join("");
});
fetch("https://local.abtmtr.link/api/v1/accounts/stat/statuses")
.then((x) => x.json())
.then((statuses) => {
console.log(statuses);
statusus.innerHTML = `<h2><a href="${statuses[0].url}" target="_blank">Current status</a></h2>${statuses[0].content}<p></p>`;
});

View file

@ -0,0 +1,141 @@
const buttonroll = document.getElementById("buttonroll");
const buttons = [
{
href: "https://jaiden.sh/",
img: "/assets/88x31/jaiden_sh.png",
alt: "jaiden.sh",
},
{
href: "https://owenzimmerman.com/",
img: "/assets/88x31/owenzimmerman_com.png",
alt: "owenzimmerman.com",
},
{
href: "https://acidicalchemist.neocities.org/",
img: "/assets/88x31/acidicalchemist_neocities_org.gif",
alt: "acidicalchemist.neocities.org",
},
{
href: "https://arimelody.me/",
img: "/assets/88x31/arimelody_me.gif",
alt: "arimelody.me",
},
{
href: "https://freeplay.floof.company/",
img: "/assets/88x31/freeplay_floof_company.png",
alt: "freeplay.floof.company",
},
{
href: "https://invoxiplaygames.uk/",
img: "/assets/88x31/invoxiplaygames_uk.png",
alt: "invoxiplaygames.uk",
},
{
href: "https://ioletsgo.gay/",
img: "/assets/88x31/ioletsgo_gay.gif",
alt: "ioletsgo.gay",
},
{
href: "https://mae.wtf/",
img: "/assets/88x31/mae_wtf.png",
alt: "mae.wtf",
},
{
href: "https://micro.pages.gay/",
img: "/assets/88x31/micro_pages_gay.png",
alt: "micro.pages.gay",
},
{
href: "https://sneexy.pages.gay/",
img: "/assets/88x31/sneexy_pages_gay.gif",
alt: "sneexy.pages.gay",
},
{
href: "https://whois.slipfox.xyz/",
img: "/assets/88x31/whois_slipfox_xyz.png",
alt: "whois.slipfox.xyz",
},
{
href: "https://moth.monster/",
img: "/assets/88x31/moth_monster.png",
alt: "moth.monster",
},
{
href: "https://translunar.academy/",
img: "/assets/88x31/translunar_academy.png",
alt: "translunar.academy",
},
{
href: "https://ultramarine-linux.org/",
img: "/assets/88x31/esoteric/gnu-linux.gif",
alt: "Made on GNU/Linux",
},
{
href: "https://abtmtr.link/projects/item/normalize",
img: "/assets/88x31/esoteric/html.gif",
alt: "<HTML> - Learn it today!",
},
{
href: "https://vivaldi.com",
img: "/assets/88x31/esoteric/vivaldi.gif",
alt: "I use Vivaldi",
},
{
href: "https://ublockorigin.com",
img: "/assets/88x31/esoteric/ublock.png",
alt: "uBlock Origin Now!",
},
{
href: "https://channelstore.roku.com/details/7da3fa0c2209746730df8a4e21e83b02",
img: "/assets/88x31/esoteric/xkcd.gif",
alt: "xkcd",
},
];
function initializeButtons() {
buttonroll.innerHTML += buttons.reduce(
(pv, cv) =>
pv +
`<a href="${cv.href}" target="_blank"><img alt="${cv.alt}" title="${cv.alt}" src="${cv.img}"></a>`,
""
);
}
buttonroll.hidden = false;
overflowButtons();
initializeButtons();
overflowButtons();
overflowButtons();
function overflowButtons() {
buttonroll.innerHTML += buttons.reduce(
(pv, cv) =>
pv +
`<a href="${cv.href}" target="_blank" class="overflow"><img alt="${cv.alt}" title="${cv.alt}" src="${cv.img}"></a>`,
""
);
}
let isHovering = false;
buttonroll.addEventListener("mouseenter", () => (isHovering = true));
buttonroll.addEventListener("mouseleave", () => (isHovering = false));
let curAnim;
let endTime = performance.now();
let unit = 88;
let gap = 8;
let scrollX = (unit + gap) * buttons.length * 2;
let speed = 25;
let max = (unit + gap) * buttons.length;
function scrollButtons(startTime) {
let deltaTime = (startTime - endTime) / 1000;
if (!isHovering) {
scrollX += speed * deltaTime;
} else scrollX = buttonroll.scrollLeft;
buttonroll.scrollLeft = (scrollX % max) + max;
endTime = startTime;
curAnim = window.requestAnimationFrame(scrollButtons);
}
curAnim = window.requestAnimationFrame(scrollButtons);

View file

@ -1,54 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Site Information - abtmtr.link</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles/normal.css" />
<link rel="stylesheet" href="/styles/style.css" />
<style>
:root {
--base-color: 125, 100%;
}
</style>
</head>
<body>
<header>
<section>
<h1>Site Information</h1>
</section>
<$ nav.html $>
<section id="accessibility" hidden></section>
</header>
<main>
<section>
<h2>What happened to the previous site?</h2>
<p>
The previous site kinda sucked. It depended on client-side React for
the most basic of things, and was just overall kind of a mess of TSX
everywhere.
</p>
<p>
This site is represented semi-statically, which means there's nothing
new being generated on the backend.<br />
This site does not use React either, so everything the frontend runs
is vanilla JavaScript.
</p>
<h2>
What's with the <b>accessibility controls</b> on every page of the
site?
</h2>
<p>
Just because. If you want to make the text bigger or get rid of the
colors, you have that panel.
</p>
<h2>What font are you using for this site?</h2>
<p>
<b>Lexend Deca</b>, or your <b>system's default sans-serif font</b>,
depending on if you're using Base style.
</p>
</section>
</main>
<script src="/scripts/accessibility.js"></script>
</body>
</html>

View file

@ -0,0 +1,34 @@
#buttonroll {
display: flex;
align-items: center;
overflow-x: scroll;
overflow-y: hidden;
gap: 8px;
max-width: 50em;
margin: auto;
padding: 8px 0;
}
#buttonroll a {
display: inline-block;
width: 88px;
height: 31px;
}
#buttonroll img {
display: inline-block;
width: 88px;
height: 31px;
max-width: none;
}
/* #buttonroll .overflow {
opacity: 0.5;
} */
#buttonroll::-webkit-scrollbar {
display: none;
}
#buttonroll {
-ms-overflow-style: none;
scrollbar-width: none;
}

53
views/styles/crt.css Normal file
View file

@ -0,0 +1,53 @@
body {
background-color: black;
margin: 0;
padding: 0;
box-sizing: border-box;
overflow: hidden;
width: 100vw;
height: 100vh;
}
div.screen {
width: 800px;
height: 600px;
position: absolute;
left: 50%;
top: 50%;
translate: -50% -50%;
box-shadow: 0 0 64px 4px #fff4;
border-radius: 8px;
overflow: hidden;
}
div.screen iframe {
display: inline-block;
width: 100%;
height: 100%;
border: none;
margin: 0;
padding: 0;
font-size: 16px;
}
div.screen div.crt {
display: inline-block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: repeating-linear-gradient(
0deg,
#0006 0px,
#0006 1px,
#0000 1px,
#0000 2px
);
background-repeat: repeat;
background-position: 0 0;
background-size: 2px;
user-select: none;
pointer-events: none;
z-index: 9000;
}

86
views/styles/myapp.css Normal file
View file

@ -0,0 +1,86 @@
@font-face {
font-family: "Lexend Deca";
src: url("/assets/fonts/Lexend Deca/Variable.ttf");
}
@font-face {
font-family: "Minecraft";
src: url("/assets/fonts/Minecraft/Regular.otf");
}
@font-face {
font-family: "Renogare";
src: url("/assets/fonts/Renogare/Regular.otf");
}
@font-face {
font-family: "NDS12";
src: url("/assets/fonts/NDS12/Regular.otf");
}
@font-face {
font-family: "OpenDyslexic";
src: url("/assets/fonts/OpenDyslexic/Regular.otf");
}
:root {
--header-font-family: "Renogare";
--header-font-weight: 400;
--font-family: "Lexend Deca";
--border-width: calc(2rem / 16);
--base-color: 325, 50%;
--border-radius: calc(4rem / 16);
}
#accessibility {
/* position: sticky;
bottom: 1em;
left: 1em; */
padding: 0.5em;
}
#accessibility fieldset {
margin-block: 0;
}
/* * {
font-smooth: never;
-webkit-font-smoothing: subpixel-antialiased;
-moz-osx-font-smoothing: grayscale;
} */
.nav {
max-width: 38em;
margin: auto;
}
.nav p {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 1em;
}
.nav p * {
text-align: center;
}
html.base .nav {
margin: 0;
}
html.base .nav p * {
text-align: start;
}
h1 {
border-bottom: var(--border-width) var(--border-style) var(--border-color);
padding-bottom: 0em;
}
h2 {
border-bottom: var(--border-width) var(--border-style) var(--border-color);
padding-bottom: 0.25em;
}
h1 a,
h2 a {
text-decoration: none !important;
}
.rs {
float: inline-end;
}

View file

@ -1,63 +1,18 @@
@font-face {
font-family: "Lexend Deca";
src: url("/assets/fonts/Lexend Deca/Variable.ttf");
}
@font-face {
font-family: "Minecraft";
src: url("/assets/fonts/Minecraft/Regular.otf");
}
@font-face {
font-family: "Renogare";
src: url("/assets/fonts/Renogare/Regular.otf");
}
@font-face {
font-family: "NDS12";
src: url("/assets/fonts/NDS12/Regular.otf");
}
@font-face {
font-family: "OpenDyslexic";
src: url("/assets/fonts/OpenDyslexic/Regular.otf");
@import url("./myapp.css");
html:not(.base) {
background: url("/assets/bg.jpg");
background-position: center;
background-size: cover;
background-attachment: fixed;
}
:root {
--font-family: "Lexend Deca";
--background-color: hsla(var(--base-color), 15%, 0.9);
}
#accessibility {
/* position: sticky;
bottom: 1em;
left: 1em; */
padding: 0.5em;
}
#accessibility fieldset {
margin-block: 0;
}
/* * {
font-smooth: never;
-webkit-font-smoothing: subpixel-antialiased;
-moz-osx-font-smoothing: grayscale;
} */
.nav {
max-width: 38em;
margin: auto;
}
.nav p {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 1em;
}
.nav p a {
text-align: center;
}
html.base .nav {
margin: 0;
}
html.base .nav p a {
text-align: start;
@media (prefers-color-scheme: light) {
:root {
--background-color: hsla(var(--base-color), 80%, 0.9);
}
}

View file

@ -27,6 +27,7 @@ div.window-object {
color: var(--accent-color-fg);
--border-color: var(--color);
border: var(--border-width) var(--border-style) var(--border-color);
border-radius: var(--border-radius);
resize: both;
overflow: hidden;
padding: 0.25em;
@ -47,6 +48,7 @@ html.base div.window-object {
background-color: var(--accent-color);
color: var(--accent-color-fg);
border: var(--border-width) var(--border-style) var(--border-color);
border-radius: var(--border-radius);
}
@media (forced-colors: active) {
@ -108,6 +110,7 @@ div.window-object {
background-color: var(--accent-color);
color: var(--accent-color-fg);
border: var(--border-width) var(--border-style) var(--border-color);
border-radius: var(--border-radius);
}
}
@ -122,6 +125,7 @@ div.window-object {
background-color: var(--accent-color);
color: var(--accent-color-fg);
border: var(--border-width) var(--border-style) var(--border-color);
border-radius: var(--border-radius);
}
}
@ -217,6 +221,7 @@ div.window-object > div.window-manager > div > button {
margin-inline-start: 0.25em;
box-sizing: border-box;
border: var(--border-width) var(--border-style) var(--border-color);
border-radius: var(--border-radius);
background-color: transparent;
color: currentColor;
font-family: "Material Symbols Outlined";
@ -231,6 +236,7 @@ div.window-object > iframe.window-content {
height: 100%;
width: 100%;
border: var(--border-width) var(--border-style) var(--border-color);
border-radius: var(--border-radius);
min-height: 0;
min-width: 0;
}

View file

@ -0,0 +1,201 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>abtmtr.link</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles/normal.css" />
<link rel="stylesheet" href="/styles/myapp.css" />
<style>
:root {
--base-color: 128, 50%;
}
</style>
</head>
<body>
<header>
<h1>TIGERRS Minecraft Server</h1>
<p>
Information regarding the Minecraft server I'm running for the TIGERRS
community.
</p>
<$ nav.html $>
<section id="accessibility" hidden></section>
</header>
<main>
<section>
<h1>Rules</h1>
<p>These are the rules of the server.</p>
<h2>Good Faith</h2>
<p>
Assume that all players are working in good faith. If someone does
something that seems ignorant or rude to you, speak up gently, not
harshly.
</p>
<h2>Sensitive Topics</h2>
<p>
When discussing sensitive topics, you can use spoiler syntax around
your text like this:
</p>
<pre>&lt;spoiler&gt;Test&lt;/spoiler&gt;</pre>
<p>
You can also use this shorter syntax to spoiler everything after the
tag:
</p>
<pre>
This will not be spoilered but &lt;spoiler&gt;all of this will be spoilered</pre
>
<p>In order to show the spoilers, hover over the text.</p>
<h2>Language</h2>
<p>
Please do not use any slurs, reclaimed or not, as we would like to keep spirits high.
</p>
<p>Just be silly :3</p>
</section>
<section>
<h1>Open Hours</h1>
<p><i>All times are in Central Time.</i></p>
<p><b>Sunday:</b> 9:00 AM10:00 PM</p>
<!-- 0 -->
<p><b>Monday:</b> 3:00 PM10:00 PM</p>
<!-- 1 -->
<p><b>Tuesday:</b> 3:00 PM12:00 AM (next day)</p>
<!-- 2 -->
<p><b>Wednesday:</b> 9:00 AM10:00 PM</p>
<!-- 3 -->
<p><b>Thursday:</b> 3:00 PM10:00 PM</p>
<!-- 4 -->
<p><b>Friday:</b> 3:00 PM12:00 AM (next day)</p>
<!-- 5 -->
<p><b>Saturday:</b> 9:00 AM12:00 AM (next day)</p>
<!-- 6 -->
</section>
<section>
<h1>Recommended Setup</h1>
<p>
While the server supports all Minecraft versions since <b>1.19.4</b>,
it is recommended that you use version 1.19.4 specifically,
<a href="https://fabricmc.net/use/installer/" target="_blank"
>especially with the Fabric mod loader</a
>.
</p>
<p>
If you're using Fabric, it's also recommended that you install these
mods:
</p>
<ul>
<li>
<a
href="https://modrinth.com/mod/clientsidenoteblocks"
target="_blank"
>Client Side Noteblocks</a
>
</li>
<li>
<a
href="https://modrinth.com/plugin/invisible-frames"
target="_blank"
>Invisible Frames</a
>
</li>
<li>
<a href="https://modrinth.com/mod/krypton" target="_blank"
>Krypton</a
>
</li>
<li>
<a href="https://modrinth.com/mod/lambdynamiclights" target="_blank"
>LambDynamicLights</a
>
</li>
<li>
<a href="https://modrinth.com/mod/serverpingerfixer" target="_blank"
>Server Pinger Fixer</a
>
</li>
<li>
<a
href="https://modrinth.com/mod/show-me-what-you-got"
target="_blank"
>Show Me What You Got</a
>
</li>
<li>
<a
href="https://modrinth.com/plugin/simple-voice-chat"
target="_blank"
>Simple Voice Chat</a
>
</li>
<li>
<a href="https://modrinth.com/mod/zume" target="_blank">Zume</a>
</li>
</ul>
<p>
Even if you don't have any mods installed, these modded actions will
still work:
</p>
<ul>
<li><i>EditSign</i> sign editing (especially if you're on 1.20+)</li>
<li><i>VeinDigging</i> vein digging</li>
<li><i>FallingTree</i> tree cutting</li>
<li><i>TabTPS</i> server performance in player menu</li>
<li><i>Couplings</i> opening double-doors simultaneously</li>
<li>
<i>Show Me What You Got</i> item chat (you cannot chat items,
however)
</li>
<li>
<i>Invisible Frames</i> item frames (you cannot make item frames
invisible, however)
</li>
<li><i>Flan</i> region claiming</li>
<li><i>WorldEdit</i> wand usage</li>
<li><i>SleepWarp</i> night skipping</li>
<li><i>StyledChat</i> chat formatting</li>
<li><i>Healthcare</i> health indicators</li>
</ul>
</section>
<section>
<h1>Server Address</h1>
<p>
The server can be found at
<b onclick="window.getSelection().selectAllChildren(this);"
>tigerrs.abtmtr.link</b
>
for Minecraft: Java Edition versions <b>1.19.4+</b>.
</p>
<p>
For Minecraft (Bedrock Edition), you should use version
<b>1.20.51</b> (latest).
</p>
<p>In order to join the server, you have to be whitelisted.</p>
<h2>Web map</h2>
<p>
A web map of the world can be found at
<a href="https://tigerrs.abtmtr.link" target="_blank"
>the same domain, on HTTPS</a
>.
</p>
<p>
If you can't join, you can at least peer inside and see what's
happening.
</p>
</section>
<section>
<h1>Is this official?</h1>
<p>
Keep in mind this server is <b>completely unofficial</b>, and is
running unaffiliated to the TIGERRS collective.
</p>
<p>
This means that none of the activities on the server are officially
moderated - instead, server rules and regulations are up to the server
admins.
</p>
</section>
</main>
<script src="/scripts/accessibility.js"></script>
<script src="./scripts/data_check_uptime.js"></script>
</body>
</html>

View file

@ -1,34 +1,30 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Domain - abtmtr.link</title>
<title>abtmtr.link</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles/normal.css" />
<link rel="stylesheet" href="/styles/style.css" />
<style>
:root {
--base-color: 15, 100%;
}
</style>
</head>
<body>
<$ loader.html $>
<header>
<section>
<h1>Domain</h1>
<p>What's on this domain.</p>
</section>
<h1>Who are we?</h1>
<$ nav.html $>
<section id="accessibility" hidden></section>
<div id="buttonroll" hidden></div>
</header>
<main>
<section>
<h1>We're abtmtr.link.</h1>
<p>abtmtr.link is an unprofessional consulting-firm/open-source-project-manager ran by one person who is actually more.</p>
<p>We're not an actual company. Don't try to call us. <a href="mailto:me@abtmtr.link">You can email us, however.</a></p>
</section>
<section id="data_get">
<h1>Getting domain indexes...</h1>
<h1>Getting member indexes...</h1>
</section>
</main>
<section id="accessibility" hidden></section>
<script src="/scripts/accessibility.js"></script>
<script src="./scripts/data_get_domain.js"></script>
<script src="./scripts/data_get_members.js"></script>
</body>
</html>

View file

@ -0,0 +1,13 @@
fetch_ask("https://pronouns.cc/api/v1/users/clhd8desmggedm3q1t3g")
.then((x) => x.json())
.then((data) => {
data_get.innerHTML = data.members.reduce((html, member) => (
html +
`<h2><a href="https://pronouns.cc/@${data.name}/${member.name}" target="_blank">${member.display_name}</a> <small>${member.pronouns.map(x => x.pronouns.split("/")[0]).join("/")}</small></h2>
<fieldset>
${member.names.map(x => `<p><b>${x.value}</b> (${x.status})</p>`).join("")}
</fieldset>
<p>${member.bio.replaceAll("\n", "<br />")}</p>
<p>Signature: <b>${member.name}</b></p>`
), "");
});