aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatarLarge Libravatar memdmp <memdmpmemewarenet>2025-01-11 20:20:16 +0100
committerLibravatarLarge Libravatar memdmp <memdmpmemewarenet>2025-01-11 20:20:16 +0100
commitd57a5985982ed2ba386eb4c0b4ca05ab3498238a (patch)
treeca3a20c0fa14646d0f87902541a32df9060df3c7 /src
downloadmem-estrogen-zone-d57a5985982ed2ba386eb4c0b4ca05ab3498238a.tar.gz
mem-estrogen-zone-d57a5985982ed2ba386eb4c0b4ca05ab3498238a.tar.bz2
mem-estrogen-zone-d57a5985982ed2ba386eb4c0b4ca05ab3498238a.tar.lz
mem-estrogen-zone-d57a5985982ed2ba386eb4c0b4ca05ab3498238a.zip

feat: initial commit

Diffstat (limited to 'src')
-rw-r--r--src/app.css12
-rw-r--r--src/app.d.ts13
-rw-r--r--src/app.html31
-rw-r--r--src/lib/fonts/Ac437_IBM_EGA_8x14.css18
-rw-r--r--src/lib/fonts/Ac437_IBM_EGA_8x14.ttfbin0 -> 25540 bytes
-rw-r--r--src/lib/fonts/Ac437_IBM_EGA_8x14.woffbin0 -> 9784 bytes
-rw-r--r--src/lib/fonts/Ac437_IBM_EGA_8x14.woff2bin0 -> 6608 bytes
-rw-r--r--src/lib/fonts/Px437_DOS-V_re_JPN12.css18
-rw-r--r--src/lib/fonts/Px437_DOS-V_re_JPN12.ttfbin0 -> 25960 bytes
-rw-r--r--src/lib/fonts/Px437_DOS-V_re_JPN12.woffbin0 -> 9520 bytes
-rw-r--r--src/lib/fonts/Px437_DOS-V_re_JPN12.woff2bin0 -> 6628 bytes
-rw-r--r--src/lib/fonts/PxPlus_IBM_EGA_8x14.css18
-rw-r--r--src/lib/fonts/PxPlus_IBM_EGA_8x14.ttfbin0 -> 70172 bytes
-rw-r--r--src/lib/fonts/PxPlus_IBM_EGA_8x14.woffbin0 -> 22356 bytes
-rw-r--r--src/lib/fonts/PxPlus_IBM_EGA_8x14.woff2bin0 -> 14712 bytes
-rw-r--r--src/lib/fonts/all-local-after-woff2.css36
-rw-r--r--src/lib/fonts/all.css36
-rw-r--r--src/lib/index.ts10
-rw-r--r--src/lib/license.ts7
-rw-r--r--src/lib/styles.ts10
-rw-r--r--src/routes/+layout.svelte11
-rw-r--r--src/routes/+layout.ts11
-rw-r--r--src/routes/+page.svelte414
-rw-r--r--src/routes/anim-gen.ts504
-rw-r--r--src/routes/boot-logo.pngbin0 -> 212582 bytes
-rw-r--r--src/routes/canaries/+page.svelte5
-rw-r--r--src/routes/canaries/canaries.ts25
-rw-r--r--src/routes/canaries/keystore.ts120
-rw-r--r--src/routes/canaries/napatha:kyun.host/+server.ts5
-rw-r--r--src/routes/distro-info.ts38
-rw-r--r--src/routes/license/+server.ts5
-rw-r--r--src/routes/shared.ts485
-rw-r--r--src/routes/upstream/+server.ts6
33 files changed, 1838 insertions, 0 deletions
diff --git a/src/app.css b/src/app.css
new file mode 100644
index 0000000..e920bfb
--- /dev/null
+++ b/src/app.css
@@ -0,0 +1,12 @@
+/*
+ Copyright (C) 2024 memdmp
+
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/src/app.d.ts b/src/app.d.ts
new file mode 100644
index 0000000..743f07b
--- /dev/null
+++ b/src/app.d.ts
@@ -0,0 +1,13 @@
+// See https://kit.svelte.dev/docs/types#app
+// for information about these interfaces
+declare global {
+ namespace App {
+ // interface Error {}
+ // interface Locals {}
+ // interface PageData {}
+ // interface PageState {}
+ // interface Platform {}
+ }
+}
+
+export {};
diff --git a/src/app.html b/src/app.html
new file mode 100644
index 0000000..f2d20ed
--- /dev/null
+++ b/src/app.html
@@ -0,0 +1,31 @@
+<!--
+/**
+ * @generated
+ * @license AGPL-3.0-OR-LATER
+ * @copyright 2024 memdmp
+ * @upstream see https://[currentdomain]/upstream
+ *
+ * Copyright (C) 2024 memdmp
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+ -->
+<!DOCTYPE html>
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <link rel="icon" href="%sveltekit.assets%/favicon.png" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ %sveltekit.head%
+</head>
+
+<body data-sveltekit-preload-data="hover">
+ <div style="display: contents">%sveltekit.body%</div>
+</body>
+
+</html> \ No newline at end of file
diff --git a/src/lib/fonts/Ac437_IBM_EGA_8x14.css b/src/lib/fonts/Ac437_IBM_EGA_8x14.css
new file mode 100644
index 0000000..01a053b
--- /dev/null
+++ b/src/lib/fonts/Ac437_IBM_EGA_8x14.css
@@ -0,0 +1,18 @@
+/*
+ Copyright (C) 2024 memdmp
+
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+@font-face {
+ font-family: "Ac437 IBM EGA 8x14";
+ src:
+ local("Ac437 IBM EGA 8x14"),
+ url("./Ac437_IBM_EGA_8x14.woff2") format("woff2"),
+ url("./Ac437_IBM_EGA_8x14.woff") format("woff"),
+ url("./Ac437_IBM_EGA_8x14.ttf") format("opentype");
+ font-display: swap;
+}
diff --git a/src/lib/fonts/Ac437_IBM_EGA_8x14.ttf b/src/lib/fonts/Ac437_IBM_EGA_8x14.ttf
new file mode 100644
index 0000000..d5b008b
--- /dev/null
+++ b/src/lib/fonts/Ac437_IBM_EGA_8x14.ttf
Binary files differ
diff --git a/src/lib/fonts/Ac437_IBM_EGA_8x14.woff b/src/lib/fonts/Ac437_IBM_EGA_8x14.woff
new file mode 100644
index 0000000..2a9d6dc
--- /dev/null
+++ b/src/lib/fonts/Ac437_IBM_EGA_8x14.woff
Binary files differ
diff --git a/src/lib/fonts/Ac437_IBM_EGA_8x14.woff2 b/src/lib/fonts/Ac437_IBM_EGA_8x14.woff2
new file mode 100644
index 0000000..fa786e2
--- /dev/null
+++ b/src/lib/fonts/Ac437_IBM_EGA_8x14.woff2
Binary files differ
diff --git a/src/lib/fonts/Px437_DOS-V_re_JPN12.css b/src/lib/fonts/Px437_DOS-V_re_JPN12.css
new file mode 100644
index 0000000..410e9de
--- /dev/null
+++ b/src/lib/fonts/Px437_DOS-V_re_JPN12.css
@@ -0,0 +1,18 @@
+/*
+ Copyright (C) 2024 memdmp
+
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+@font-face {
+ font-family: "Px437 DOS/V re. JPN12";
+ src:
+ local("Px437 DOS/V re. JPN12"),
+ url("./Px437_DOS-V_re_JPN12.woff2") format("woff2"),
+ url("./Px437_DOS-V_re_JPN12.woff") format("woff"),
+ url("./Px437_DOS-V_re_JPN12.ttf") format("opentype");
+ font-display: swap;
+}
diff --git a/src/lib/fonts/Px437_DOS-V_re_JPN12.ttf b/src/lib/fonts/Px437_DOS-V_re_JPN12.ttf
new file mode 100644
index 0000000..6f4d616
--- /dev/null
+++ b/src/lib/fonts/Px437_DOS-V_re_JPN12.ttf
Binary files differ
diff --git a/src/lib/fonts/Px437_DOS-V_re_JPN12.woff b/src/lib/fonts/Px437_DOS-V_re_JPN12.woff
new file mode 100644
index 0000000..311517f
--- /dev/null
+++ b/src/lib/fonts/Px437_DOS-V_re_JPN12.woff
Binary files differ
diff --git a/src/lib/fonts/Px437_DOS-V_re_JPN12.woff2 b/src/lib/fonts/Px437_DOS-V_re_JPN12.woff2
new file mode 100644
index 0000000..12926c4
--- /dev/null
+++ b/src/lib/fonts/Px437_DOS-V_re_JPN12.woff2
Binary files differ
diff --git a/src/lib/fonts/PxPlus_IBM_EGA_8x14.css b/src/lib/fonts/PxPlus_IBM_EGA_8x14.css
new file mode 100644
index 0000000..d1a3a97
--- /dev/null
+++ b/src/lib/fonts/PxPlus_IBM_EGA_8x14.css
@@ -0,0 +1,18 @@
+/*
+ Copyright (C) 2024 memdmp
+
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+@font-face {
+ font-family: "PxPlus IBM EGA 8x14";
+ src:
+ local("PxPlus IBM EGA 8x14"),
+ url("./PxPlus_IBM_EGA_8x14.woff2") format("woff2"),
+ url("./PxPlus_IBM_EGA_8x14.woff") format("woff"),
+ url("./PxPlus_IBM_EGA_8x14.ttf") format("opentype");
+ font-display: swap;
+}
diff --git a/src/lib/fonts/PxPlus_IBM_EGA_8x14.ttf b/src/lib/fonts/PxPlus_IBM_EGA_8x14.ttf
new file mode 100644
index 0000000..6dc1007
--- /dev/null
+++ b/src/lib/fonts/PxPlus_IBM_EGA_8x14.ttf
Binary files differ
diff --git a/src/lib/fonts/PxPlus_IBM_EGA_8x14.woff b/src/lib/fonts/PxPlus_IBM_EGA_8x14.woff
new file mode 100644
index 0000000..0763445
--- /dev/null
+++ b/src/lib/fonts/PxPlus_IBM_EGA_8x14.woff
Binary files differ
diff --git a/src/lib/fonts/PxPlus_IBM_EGA_8x14.woff2 b/src/lib/fonts/PxPlus_IBM_EGA_8x14.woff2
new file mode 100644
index 0000000..c29e71a
--- /dev/null
+++ b/src/lib/fonts/PxPlus_IBM_EGA_8x14.woff2
Binary files differ
diff --git a/src/lib/fonts/all-local-after-woff2.css b/src/lib/fonts/all-local-after-woff2.css
new file mode 100644
index 0000000..9bc8d65
--- /dev/null
+++ b/src/lib/fonts/all-local-after-woff2.css
@@ -0,0 +1,36 @@
+/*
+ Copyright (C) 2024 memdmp
+
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+@font-face {
+ font-family: "Ac437 IBM EGA 8x14";
+ src:
+ url("./Ac437_IBM_EGA_8x14.woff2") format("woff2"),
+ local("Ac437 IBM EGA 8x14"),
+ url("./Ac437_IBM_EGA_8x14.woff") format("woff"),
+ url("./Ac437_IBM_EGA_8x14.ttf") format("opentype");
+ font-display: swap;
+}
+@font-face {
+ font-family: "PxPlus IBM EGA 8x14";
+ src:
+ url("./PxPlus_IBM_EGA_8x14.woff2") format("woff2"),
+ local("PxPlus IBM EGA 8x14"),
+ url("./PxPlus_IBM_EGA_8x14.woff") format("woff"),
+ url("./PxPlus_IBM_EGA_8x14.ttf") format("opentype");
+ font-display: swap;
+}
+@font-face {
+ font-family: "Px437 DOS/V re. JPN12";
+ src:
+ url("./Px437_DOS-V_re_JPN12.woff2") format("woff2"),
+ local("Px437 DOS/V re. JPN12"),
+ url("./Px437_DOS-V_re_JPN12.woff") format("woff"),
+ url("./Px437_DOS-V_re_JPN12.ttf") format("opentype");
+ font-display: swap;
+}
diff --git a/src/lib/fonts/all.css b/src/lib/fonts/all.css
new file mode 100644
index 0000000..7b30c9a
--- /dev/null
+++ b/src/lib/fonts/all.css
@@ -0,0 +1,36 @@
+/*
+ Copyright (C) 2024 memdmp
+
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+@font-face {
+ font-family: "Ac437 IBM EGA 8x14";
+ src:
+ local("Ac437 IBM EGA 8x14"),
+ url("./Ac437_IBM_EGA_8x14.woff2") format("woff2"),
+ url("./Ac437_IBM_EGA_8x14.woff") format("woff"),
+ url("./Ac437_IBM_EGA_8x14.ttf") format("opentype");
+ font-display: swap;
+}
+@font-face {
+ font-family: "PxPlus IBM EGA 8x14";
+ src:
+ local("PxPlus IBM EGA 8x14"),
+ url("./PxPlus_IBM_EGA_8x14.woff2") format("woff2"),
+ url("./PxPlus_IBM_EGA_8x14.woff") format("woff"),
+ url("./PxPlus_IBM_EGA_8x14.ttf") format("opentype");
+ font-display: swap;
+}
+@font-face {
+ font-family: "Px437 DOS/V re. JPN12";
+ src:
+ local("Px437 DOS/V re. JPN12"),
+ url("./Px437_DOS-V_re_JPN12.woff2") format("woff2"),
+ url("./Px437_DOS-V_re_JPN12.woff") format("woff"),
+ url("./Px437_DOS-V_re_JPN12.ttf") format("opentype");
+ font-display: swap;
+}
diff --git a/src/lib/index.ts b/src/lib/index.ts
new file mode 100644
index 0000000..13c5889
--- /dev/null
+++ b/src/lib/index.ts
@@ -0,0 +1,10 @@
+/*
+ Copyright (C) 2024 memdmp
+
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+export * as styles from "./styles.ts";
diff --git a/src/lib/license.ts b/src/lib/license.ts
new file mode 100644
index 0000000..d9c8ffe
--- /dev/null
+++ b/src/lib/license.ts
@@ -0,0 +1,7 @@
+import * as license from "$/../LICENSE?raw";
+
+export default (typeof license === "string"
+ ? license
+ : "default" in license
+ ? license.default
+ : JSON.stringify(license, null, 2)) as string;
diff --git a/src/lib/styles.ts b/src/lib/styles.ts
new file mode 100644
index 0000000..fd361b4
--- /dev/null
+++ b/src/lib/styles.ts
@@ -0,0 +1,10 @@
+/*
+ Copyright (C) 2024 memdmp
+
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+export const button = "bg-white bg-opacity-10 text-white p-4 rounded-xl";
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
new file mode 100644
index 0000000..53bd941
--- /dev/null
+++ b/src/routes/+layout.svelte
@@ -0,0 +1,11 @@
+<script lang="ts">
+ import "../app.css";
+ import "$lib/fonts/all-local-after-woff2.css";
+ const { children } = $props();
+</script>
+
+<div
+ class="w-screen h-screen fixed top-0 left-0 bg-black text-white p-4 overflow-y-auto"
+>
+ {@render children()}
+</div>
diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts
new file mode 100644
index 0000000..2c01569
--- /dev/null
+++ b/src/routes/+layout.ts
@@ -0,0 +1,11 @@
+/*
+ Copyright (C) 2024 memdmp
+
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+export const prerender = true;
+export const trailingSlash = 'always';
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
new file mode 100644
index 0000000..701132b
--- /dev/null
+++ b/src/routes/+page.svelte
@@ -0,0 +1,414 @@
+<!--
+/**
+ * @license AGPL-3.0-OR-LATER
+ * @copyright 2024 memdmp
+ *
+ * Copyright (C) 2024 memdmp
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+ -->
+<script lang="ts">
+ import logo from "./boot-logo.png";
+ import "./anim.css";
+ import { architecture, hostname, tty, versions } from "./distro-info";
+ import { login, ttyLines, type RenderBlock, type TTYText } from "./shared";
+ import { onDestroy, onMount } from "svelte";
+
+ const initTagLine = `line flex flex-row flex-wrap gap-3 justify-between`;
+
+ const fsckFileCount = Math.floor(Math.random() * 32768 + 6732);
+ const fsckTotalCount = Math.round(
+ (fsckFileCount / 6732) * 753664 + Math.random() * 10000,
+ );
+ const blocksTotal = Math.floor(Math.random() * 8000000 + 4000000);
+ const blocksCount = Math.floor(Math.random() * 600000 + 200000);
+
+ const lanLeastSignificantIPPart = Math.floor(Math.random() * 253 + 2);
+
+ let isScripted = false;
+ onMount(() => (isScripted = true));
+ onDestroy(() => (isScripted = false));
+</script>
+
+<svelte:head>
+ <title>mem.estrogen.zone</title>
+</svelte:head>
+
+{#snippet tagLeft(colour: string)}
+ <span class="text-[{colour}]">*</span>
+{/snippet}
+{#snippet okTagLeft()}
+ {@render tagLeft("#51f051")}
+{/snippet}
+{#snippet tagRight(innerText: string, innerColour: string)}
+ <span class="text-[#5f5fff]">[</span>
+ <span class="text-[{innerColour}]">{innerText}</span>
+ <span class="text-[#5f5fff]">]</span>
+{/snippet}
+{#snippet okTagRight()}
+ {@render tagRight("ok", "#51f051")}
+{/snippet}
+{#snippet okLine(text: string, classes?: string, rightClass?: string)}
+ <div class="{initTagLine}{classes ? ' ' + classes : ''}">
+ <div class="left">
+ {@render okTagLeft()}
+ {text}
+ </div>
+ <div class="right{rightClass ? ' ' + rightClass : ''}">
+ {@render okTagRight()}
+ </div>
+ </div>
+{/snippet}
+{#snippet ttyTextInnerRenderer(section: RenderBlock)}
+ <span
+ class="ttytext-block {section.colour
+ ? ` text-[${section.colour}]`
+ : ''}{section.bg ? ` bg-[${section.bg}]` : ''} {section.weight
+ ? ` ${['', 'font-thin', 'font-extralight', 'font-light', 'font-normal', 'font-medium', 'font-semibold', 'font-bold', 'font-extrabold', 'font-black'][section.weight / 100]}`
+ : ''}{typeof section.italic === 'undefined'
+ ? ''
+ : section.italic
+ ? ' italic'
+ : ' not-italic'}{typeof section.underlined === 'undefined'
+ ? ''
+ : section.underlined
+ ? ` underline`
+ : ' no-underline'} inline"
+ >{#if section.raw}{@html section.value}{:else}{#each section.value
+ .split("\n")
+ .map( (l, i, a) => (i === a.length - 1 ? [0, l] : [1, l]), ) as [nl, l]}{l}{#if nl}<br
+ />{/if}{/each}{/if}</span
+ >
+{/snippet}
+{#snippet ttyTextMiddleRenderer(
+ section: RenderBlock,
+ line: TTYText & { kind: "text" },
+)}
+ {#if section.url}
+ {#if typeof section.url === "string"}
+ {#if section.url.startsWith("newtab:")}
+ <a
+ href={section.url.substring(7)}
+ target="_blank"
+ rel="noopener noreferrer"
+ class="no-underline text-inherit"
+ download={section.dl}>{@render ttyTextInnerRenderer(section)}</a
+ >
+ {:else if section.url.startsWith("currenttab:")}
+ <a
+ href={section.url.substring(11)}
+ class="no-underline text-inherit"
+ download={section.dl}>{@render ttyTextInnerRenderer(section)}</a
+ >
+ {:else}
+ ERR: Unknown Link Format
+ {/if}
+ {:else}
+ <span
+ on:click={() =>
+ typeof section.url !== "function" ? void 0 : section.url(line)}
+ on:keypress={() =>
+ typeof section.url !== "function" ? void 0 : section.url(line)}
+ role="link"
+ tabindex="0">{@render ttyTextInnerRenderer(section)}</span
+ >
+ {/if}
+ {:else}
+ {@render ttyTextInnerRenderer(section)}
+ {/if}
+{/snippet}
+{#snippet ttyText(line: TTYText)}
+ {#if line.kind === "text"}
+ {#if (line.renderrestriction ?? "everywhere") === "everywhere" || line.renderrestriction === "noscript" || (line.renderrestriction === "js-only" && isScripted)}
+ {#if line.renderrestriction === "noscript"}
+ <noscript class="inline-block max-w-[100%] {line.classes.join(' ')}"
+ >{#each line.value as v}{@render ttyTextMiddleRenderer(
+ v,
+ line,
+ )}{/each}</noscript
+ >
+ {:else}
+ <span class="inline-block max-w-[100%] {line.classes.join(' ')}"
+ >{#each line.value as v}{@render ttyTextMiddleRenderer(
+ v,
+ line,
+ )}{/each}</span
+ >
+ {/if}
+ {/if}
+ {/if}
+{/snippet}
+
+<div class="hidden">
+ {#each ["inline-block", "block", "flex", "inline-flex", "max-w-full", "font-thin", "font-extralight", "font-light", "font-normal", "font-medium", "font-semibold", "font-bold", "font-extrabold", "font-black"] as c}
+ <span class={c}></span>
+ {/each}
+</div>
+
+<div class="fixed top-0 left-0 w-screen h-screen font-mono" id="app">
+ <div
+ id="bios"
+ class="flex flex-col items-center justify-between relative font-bios text-lg"
+ >
+ <div class="top flex items-center justify-center flex-col">
+ <div class="inner text-[#999999]">
+ <p class="leading-4 mt-8 -mb-8 max-w-[36rem]">
+ <span class="bdsdxe-load"
+ >BdsDxe: loading Boot0002 "UEFI Misc Device" from PciRoot
+ (0x0)/Pci(0x2,0x4)/Pci(0x0,0x0)</span
+ ><br />
+ <span class="bdsdxe-start"
+ >BdsDxe: starting Boot0002 "UEFI Misc Device" from PciRoot
+ (0x0)/Pci(0x2,0x4)/Pci(0x0,0x0)</span
+ >
+ </p>
+ </div>
+ </div>
+ <div class="middle">
+ <img
+ src={logo}
+ alt="Bootloader Logo"
+ class="w-32 h-32 flex items-center justify-center"
+ />
+ </div>
+ <div
+ class="bottom flex flex-col items-stretch justify-center text-center gap-0.5 w-full pb-4"
+ >
+ <div class="start-text">Start boot option</div>
+ <div
+ class="bar h-4"
+ style={`background-image: url("data:image/svg+xml,${encodeURIComponent(`<svg width="32" height="32" viewbox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+ <rect x="1" y="1" width="30" height="30" fill="white" stroke="black" stroke-width="2"/>
+</svg>
+`)}");background-size:contain;`}
+ ></div>
+ </div>
+ </div>
+ <div id="grub" class="font-grub text-sm">
+ <div
+ class="relative p-4 flex items-stretch justify-stretch flex-col w-screen h-screen"
+ >
+ <p class="title text-[#a2a2a2] mb-4 h-4 text-center">
+ GNU GRUB &nbsp;version 2.12
+ </p>
+ <div
+ class="outer-border border-2 border-[#a8a8a8] border-solid h-full px-1 py-2 flex flex-col items-stretch"
+ >
+ <div class="entry bg-[#a8a8a8] text-[#000] text-left">
+ *Alpine Linux {versions.alpine.number}, with {versions.kernel
+ .humanReadable}
+ </div>
+ <div class="entry text-[#a8a8a8] text-left">
+ &nbsp;UEFI Firmware Setup
+ </div>
+ </div>
+ <p class="footer text-[#a2a2a2] my-4 text-left pl-2">
+ <span class="block pl-6">
+ Use the ↑ and ↓ keys to select which entry is highlighted.<br />
+ Press enter to boot the selected OS, `e' to edit the commands before booting
+ or `c' for a command-line.<br />
+ </span>
+ The highlighted entry will be executed automatically in
+ <span class="grub-2s inline-block">2</span><span
+ class="grub-1s inline-block">1</span
+ >s.
+ </p>
+ </div>
+ </div>
+ <div id="grub-term" class="font-grub text-sm text-left text-[#a2a2a2]">
+ <p class="p-8">
+ &nbsp;&nbsp;Booting `Alpine Linux {versions.alpine.number}, with {versions
+ .kernel.humanReadable}'<br /><br /><span class="load-kernel"
+ >Loading {versions.kernel.humanReadable} ...</span
+ ><br /><span class="load-ramdisk">Loading initial ramdisk ...</span>
+ </p>
+ </div>
+ <!--
+ flex col reverse and mb auto are hacks for it to auto scroll to bottom(!)
+ flex col reverse only works due to nested inner (which would be at bottom of screen, if it wasnt for mb-auto div above it)
+ -->
+ <div id="openrc" class="overflow-y-auto flex flex-col-reverse">
+ <div class="mb-auto"></div>
+ <div class="font-mono leading-4 text-[#b9b9b9] flex flex-col p-4">
+ <div class="line">
+ &nbsp;&nbsp;<span class="text-[#51f051]">OpenRC</span>
+ <span class="text-[#4bdfdf]">{versions.openrc}</span>
+ is starting up Linux
+ <span class="text-[#5f5fff]">{versions.kernel.id} ({architecture})</span
+ >
+ </div>
+ <div class="line">&ZeroWidthSpace;</div>
+ {@render okLine("/proc/ is already mounted")}
+ {@render okLine("Mounting /run ...")}
+ {@render okLine("/run/openrc: creating directory")}
+ {@render okLine("/run/lock: creating directory", "openrc-boot-step-1")}
+ {@render okLine("/run/lock: correcting owner", "openrc-boot-step-1")}
+ {@render okLine("Caching service dependencies ...", "openrc-boot-step-1")}
+ {@render okLine("Remounting devtmpfs on /dev ...", "openrc-boot-step-2")}
+ {@render okLine("Mounting /dev/mqueue ...", "openrc-boot-step-3")}
+ {@render okLine("Mounting security filesystem ...", "openrc-boot-step-4")}
+ {@render okLine("Mounting debug filesystem ...", "openrc-boot-step-5")}
+ {@render okLine(
+ "Mounting persistent storage (pstore) filesystem ...",
+ "openrc-boot-step-5",
+ )}
+ {@render okLine("Mounting efivarfs filesystem ...", "openrc-boot-step-5")}
+ {@render okLine("Starting busybox mdev ...", "openrc-boot-step-6")}
+ {@render okLine("Scanning hardware for mdev ...", "openrc-boot-step-6")}
+ {@render okLine("Loading hardware drivers ...", "openrc-boot-step-7")}
+ {@render okLine("Loading modules ...", "openrc-boot-step-8")}
+ {@render okLine(
+ "Setting system clock using the hardware clock [UTC] ...",
+ "openrc-boot-step-9",
+ )}
+ {@render okLine("Checking local filesystems ...", "openrc-boot-step-10")}
+ <div class="line openrc-boot-step-11">
+ /dev/mapper/bepis: clean, {fsckFileCount}/{fsckTotalCount} files, {blocksCount}/{blocksTotal}
+ blocks
+ </div>
+ {@render okLine(
+ "Remounting root filesystem read/write ...",
+ "openrc-boot-step-12",
+ )}
+ {@render okLine("Remounting filesystems ...", "openrc-boot-step-13")}
+ {@render okLine("Activating swap devices ...", "openrc-boot-step-14")}
+ {@render okLine("Mounting local filesystems ...", "openrc-boot-step-15")}
+ {@render okLine(
+ "Configuring kernel parameters ...",
+ "openrc-boot-step-16",
+ )}
+ {@render okLine("Creating user login records ...", "openrc-boot-step-17")}
+ {@render okLine("Setting hostname ...", "openrc-boot-step-18")}
+ {@render okLine("Setting keymap ...", "openrc-boot-step-19")}
+ {@render okLine("Starting networking ...", "openrc-boot-step-20")}
+ {@render okLine("\xa0\xa0lo ...", "openrc-boot-step-21")}
+ {@render okLine("\xa0\xa0eth0 ...", "openrc-boot-step-22")}
+ <div class="line openrc-boot-step-22">udhcpd: started</div>
+ <div class="line openrc-boot-step-23">udhcpd: broadcasting discover</div>
+ <!-- TODO: configurable subnet uwu -->
+ <div class="line openrc-boot-step-24">
+ udhcpd: broadcasting select for 192.168.1.{lanLeastSignificantIPPart},
+ server 192.168.1.1
+ </div>
+ <div class="line openrc-boot-step-25">
+ udhcpd: lease of 192.168.1.{lanLeastSignificantIPPart} obtained from 192.168.1.1,
+ lease time 3600
+ </div>
+ {@render okLine(
+ "Seeding random number generator ...",
+ "openrc-boot-step-26",
+ )}
+ {@render okLine(
+ "Seeding 256 bits without crediting",
+ "openrc-boot-step-26",
+ )}
+ {@render okLine(
+ "Saving 256 bits of creditable seed for next boot",
+ "openrc-boot-step-26",
+ )}
+ {@render okLine("Starting logbookd ...", "openrc-boot-step-27")}
+ {@render okLine("Starting busybox acpid ...", "openrc-boot-step-28")}
+ {@render okLine("Starting busybox crond ...", "openrc-boot-step-29")}
+ {@render okLine("Starting busybox ntpd ...", "openrc-boot-step-29")}
+ <div class="line openrc-boot-step-30">&ZeroWidthSpace;</div>
+ <div class="line openrc-boot-step-30">
+ Welcome to Alpine Linux {versions.alpine
+ .number}{#if versions.alpine.isEdge}&nbsp;(edge){/if}
+ </div>
+ <div class="line openrc-boot-step-30">
+ Kernel {versions.kernel.humanReadable} on an {architecture} (/dev/{tty})
+ </div>
+ <div class="line openrc-boot-step-30">&ZeroWidthSpace;</div>
+ <div class="line openrc-boot-step-30">
+ {hostname} login:&nbsp;<span class="openrc-username"
+ >{#each login.username
+ .split("")
+ .map((v, i) => [v, i] as const) as [char, idx]}<span
+ class="openrc-username-char openrc-username-char-{idx} inline-block"
+ >{char}</span
+ >{/each}</span
+ ><span class="openrc-hide-at-login-prompt-username-done"
+ ><span class="openrc-username-anim"
+ ><code class="hidden-after-anim">_</code></span
+ ></span
+ ><span class="openrc-pw-line"
+ ><br />
+ Password:&nbsp;<span class="openrc-password-anim"
+ ><code class="hidden-after-anim">_</code></span
+ ></span
+ >
+ </div>
+ <!-- TODO: Animate login and launching a tui here (for js enjoyers) or a minimal terminal after clearing the screen, and a no js screen alongside a plaintext version of the thing (for schizo noscript lovers) -->
+ <div class="hidden-after-anim">
+ <span class="openrc-hide-at-last-boot-step inline-block">
+ <pre class="flashing-cursor">_</pre>
+ </span>
+ </div>
+ <div class="line ttylines-openrc font-mono text-[#070505] flex flex-col">
+ {#each ttyLines.filter(((maxidx) => (_, i) => i < (maxidx === -1 ? Infinity : maxidx))(ttyLines.findIndex((v) => v.kind === "clear"))) as line}
+ {@render ttyText(line)}
+ {/each}
+ </div>
+ </div>
+ </div>
+ {#if ttyLines.find((v) => v.kind === "clear")}
+ {#each ttyLines
+ .map((v, i) => [v, i] as const)
+ .filter(([v]) => v.kind === "clear")
+ .map((v, i, a) => [v[1], a[i + 1]?.[1], i]) as [idx, nextIdx, clearIdx]}
+ <div
+ id="tty-{clearIdx}"
+ class="overflow-y-auto max-h-screen flex flex-col-reverse"
+ >
+ <div class="mb-auto"></div>
+ <div class="font-mono leading-4 text-[#070505] flex flex-col p-4">
+ {#each ttyLines.filter((_, i) => i >= idx && i < (nextIdx ?? Infinity)) as line}
+ {@render ttyText(line)}
+ {/each}
+ </div>
+ </div>
+ {/each}
+ {/if}
+</div>
+
+<style>
+ @keyframes flashing-cursor {
+ 0% {
+ opacity: 0;
+ }
+ 49% {
+ opacity: 0;
+ }
+ 50% {
+ opacity: 1;
+ }
+ 99% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+ }
+ .flashing-cursor {
+ animation-name: flashing-cursor;
+ animation-duration: 0.4s;
+ animation-iteration-count: infinite;
+ animation-timing-function: linear;
+ }
+ .hidden-after-anim {
+ margin-top: -99999vh;
+ margin-bottom: 99999vh;
+ margin-left: -99999vw;
+ margin-right: 99999vw;
+ transform: scaleX(0);
+ opacity: 0;
+ }
+ .ttytext-block {
+ white-space-collapse: preserve-spaces;
+ }
+</style>
diff --git a/src/routes/anim-gen.ts b/src/routes/anim-gen.ts
new file mode 100644
index 0000000..9138ca9
--- /dev/null
+++ b/src/routes/anim-gen.ts
@@ -0,0 +1,504 @@
+/*
+ Copyright (C) 2024 memdmp
+
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+import { Animation, MultiObjectKeyframe } from '@memdmp/keyframegen';
+import {
+ biosStepInterval,
+ biosSteps,
+ getDelay,
+ login,
+ ttyLines,
+} from './shared.ts';
+const anim = new Animation();
+let ttyCtr = 0;
+const stages = [
+ anim.selector('#bios'),
+ anim.selector('#grub'),
+ anim.selector('#grub-term'),
+ anim.selector('#openrc'),
+ ...(ttyLines.flatMap((v) =>
+ v.kind === 'clear'
+ ? [
+ anim.selector('#tty-' + ttyCtr++),
+ ]
+ : []
+ )),
+];
+const visibleStageStyles = `margin-top: 0;
+margin-bottom: 0;
+margin-left: 0;
+margin-right: 0;
+height: 100vh;
+width: 100vw;
+opacity: 1;`;
+const hiddenStageStyles = `margin-top: -99999vh;
+margin-bottom: 99999vh;
+margin-left: -99999vw;
+margin-right: 99999vw;
+height: 0px;
+width: 0px;
+opacity: 0;`;
+const visibleStyles = `margin-top: 0;
+margin-bottom: 0;
+margin-left: 0;
+margin-right: 0;
+transform: none;
+opacity: 1;`;
+const hiddenStyles = `margin-top: -99999vh;
+margin-bottom: 99999vh;
+margin-left: -99999vw;
+margin-right: 99999vw;
+transform: scaleX(0);
+opacity: 0;`;
+const altHiddenStyles = `opacity:0;
+transform:scaleX(0);
+width:0;
+height:0;
+margin-left:-100vw;
+margin-right:100vw;`;
+const altVisibleStyles = `opacity:1;
+transform:none;
+width:max-content;
+height:max-content;
+margin-left:0;
+margin-right:0;`;
+stages.forEach((v, i) =>
+ v.style(i === 0 ? visibleStageStyles : hiddenStageStyles)
+);
+let currentStage: typeof stages[number];
+const toStage = (stage: number) => {
+ const oldStage = currentStage;
+ currentStage = stages[stage];
+ if (typeof oldStage !== 'undefined') {
+ oldStage.style(
+ visibleStageStyles,
+ );
+ currentStage.style(
+ hiddenStageStyles,
+ );
+ anim._internal_timeline.now += 1;
+ oldStage.style(
+ hiddenStageStyles,
+ );
+ }
+ currentStage.style(
+ visibleStageStyles,
+ );
+};
+
+type Step = (next: () => void) => void;
+
+const biosStepHandlers: Step[] = [
+ // Show BIOS
+ (next) => {
+ toStage(0);
+ anim.in(1000 * 3 / 10, next);
+ },
+ // Show Start boot option
+ (next) => {
+ const s = anim.selector('#bios .start-text');
+ s.style(hiddenStyles);
+ anim._internal_timeline.now += 1;
+ s.style(visibleStyles);
+ anim.in(1000, next);
+ },
+ // Show bar parts
+ ...new Array(
+ biosSteps,
+ ).fill(
+ (quantity: number, lastQuantity: number, index: number) =>
+ (next: () => void) => {
+ const s = anim.selector(
+ `#bios .bar`,
+ );
+ s.style(`${index === 0 ? hiddenStyles : visibleStyles}
+width: ${lastQuantity * 100}vw;`);
+ anim._internal_timeline.now += 1;
+ s.style(`${visibleStyles}
+width: ${quantity * 100}vw;`);
+ anim.in(quantity === 1 ? 50 : biosStepInterval, next);
+ },
+ ).map((v, i, a) => v((i + 1) / a.length, i / a.length, i)),
+ // Show bdsdxe
+ (next) => {
+ const s1 = anim.selector('#bios .bdsdxe-load');
+ const s2 = anim.selector('#bios .bdsdxe-start');
+ s1.style(hiddenStyles);
+ s2.style(hiddenStyles);
+ anim._internal_timeline.now += 1;
+ s1.style(visibleStyles);
+ anim.in(75, () => {
+ s2.style(visibleStyles);
+ anim.in(50, next);
+ });
+ },
+];
+const grubStepHandlers: Step[] = [ // Show Grub
+ (next) => {
+ toStage(1);
+ anim.in(1000, next);
+ },
+ // Hide 2s, show 1s
+ (next) => {
+ const g2s = anim.selector('#grub .grub-2s');
+ const g1s = anim.selector('#grub .grub-1s');
+ g2s.style('width:max-content;opacity:1;');
+ g1s.style('width:0;opacity:0;');
+ anim._internal_timeline.now += 1;
+ g2s.style('width:0;opacity:0;');
+ g1s.style('width:max-content;opacity:1;');
+ anim.in(1000, next);
+ },
+ // Show Booting Alpine Grub
+ (next) => {
+ toStage(2);
+ anim.in(100, next);
+ },
+ (next) => {
+ const s = anim.selector('#grub-term .load-kernel');
+ s.style(hiddenStyles);
+ anim._internal_timeline.now += 1;
+ s.style(visibleStyles);
+ anim.in(200, next);
+ },
+ (next) => {
+ const s = anim.selector('#grub-term .load-ramdisk');
+ s.style(hiddenStyles);
+ anim._internal_timeline.now += 1;
+ s.style(visibleStyles);
+ anim.in(950, next);
+ },
+];
+const typingIndicatorFlashTime = 200;
+const typingIndicatorHidden = `
+opacity: 0;
+transform: scaleX(0);
+`;
+const typingIndicatorVisible = `
+opacity: 1;
+transform: none;
+`;
+/** Returns the last state it was in */
+const idleTypingBar = (
+ typingBarSelector: MultiObjectKeyframe,
+ idleFor: number,
+) => {
+ let flashingTimeCtr = 0;
+ let isHiddenInCycle = false;
+ typingBarSelector.style(typingIndicatorVisible);
+ const v = () =>
+ isHiddenInCycle ? typingIndicatorHidden : typingIndicatorVisible;
+ while ((flashingTimeCtr += typingIndicatorFlashTime) < idleFor) {
+ if (flashingTimeCtr === typingIndicatorFlashTime) flashingTimeCtr /= 2;
+ anim.in(flashingTimeCtr, () => {
+ isHiddenInCycle = !isHiddenInCycle;
+ typingBarSelector.style(
+ v(),
+ );
+ });
+ anim.in(flashingTimeCtr - 1, () => {
+ typingBarSelector.style(
+ v(),
+ );
+ });
+ }
+ return v();
+};
+const typeCharacter = (
+ typingBarSelector: MultiObjectKeyframe,
+ characterSelector: MultiObjectKeyframe | undefined | null,
+ next: () => void,
+ characterTime: number,
+) => {
+ const characterHidden = `
+ transform:scaleX(0);
+ width: 0;
+ opacity: 0;
+ `;
+ const characterVisible = `
+ transform:none;
+ width: max-content;
+ opacity: 1;
+ `;
+ typingBarSelector.style(typingIndicatorVisible);
+ if (characterSelector) {
+ characterSelector.style(characterHidden);
+ anim._internal_timeline.now += 1;
+ characterSelector.style(characterVisible);
+ }
+ if (characterTime > typingIndicatorFlashTime) {
+ const res = idleTypingBar(typingBarSelector, characterTime);
+ anim.in(characterTime - 1, () => {
+ typingBarSelector.style(res);
+ });
+ anim.in(characterTime, () => {
+ typingBarSelector.style(typingIndicatorHidden);
+ });
+ anim.in(characterTime + 1000 / 60, next);
+ } else {
+ anim.in(characterTime, () => {
+ typingBarSelector.style(typingIndicatorHidden);
+ });
+ anim.in(characterTime + 1000 / 60, next);
+ }
+};
+const openrcStepHandlers = (multi: number): Step[] => [
+ // Show OpenRC
+ (next) => {
+ toStage(3);
+ next();
+ },
+ ...[
+ // Steps 1-3
+ 1000 / 60,
+ 200,
+ 1000 / 30,
+ // Steps 4-9
+ 1000 / 60,
+ 1000 / 60,
+ 4000 / 60,
+ 1000 / 40,
+ 400,
+ 75,
+ // Steps 10-15
+ 150,
+ 100,
+ 85,
+ 100,
+ 3500 / 60,
+ 125,
+ // Steps 16-20
+ 3500 / 60,
+ 100,
+ 150,
+ 1000 / 60,
+ 1000 / 60,
+ 1000 / 60,
+ // Steps 21-25
+ 1000 / 85,
+ 1000 / 45,
+ 1000 / 60,
+ 1000 / 60,
+ 500,
+ // Steps 26-30
+ 750,
+ 400,
+ 100,
+ 100,
+ 400,
+ // Time till typing:
+ 1000,
+ ].map((v) => v / multi).map((time, idx, arr) => (next: () => void) => {
+ const s = anim.selector('#openrc .openrc-boot-step-' + idx);
+ s.style(`${hiddenStyles}
+max-height: 0;`);
+ if (idx === arr.length - 1) {
+ anim.selector('#openrc .openrc-hide-at-last-boot-step').style(
+ 'opacity:1;max-height:90vh;max-width:100vw;',
+ );
+ }
+ anim._internal_timeline.now += 1;
+ if (idx === arr.length - 1) {
+ anim.selector('#openrc .openrc-hide-at-last-boot-step').style(
+ 'opacity:0;max-height:0;max-width:0;',
+ );
+ }
+ s.style(`${visibleStyles}
+max-height: max-content;`);
+ if (idx === arr.length - 1) {
+ idleTypingBar(anim.selector('#openrc .openrc-username-anim'), time);
+ }
+ anim.in(time, next);
+ }),
+ // Typing Username
+ ...((() => {
+ const typingBar = anim.selector('#openrc .openrc-username-anim');
+ return login.username.split('').map((_, i) => {
+ const character = anim.selector(
+ `#openrc .openrc-username .openrc-username-char.openrc-username-char-${i}`,
+ );
+ return (next: () => void) =>
+ typeCharacter(typingBar, character, next, getDelay(login));
+ });
+ })()),
+ // Hide Username Cursor
+ (next) => {
+ const s = anim.selector(
+ '#openrc .openrc-hide-at-login-prompt-username-done',
+ );
+ s.style(`transform:none;width:max-content;opacity:1;`);
+ anim._internal_timeline.now += 1;
+ s.style(`transform:scaleX(0);width:0;opacity:0;`);
+ next();
+ },
+ // Show Password
+ (next) => {
+ const s = anim.selector('#openrc .openrc-pw-line');
+ s.style(`transform:scaleX(0);width:0;opacity:0;`);
+ anim._internal_timeline.now += 1;
+ s.style(`transform:none;width:max-content;opacity:1;`);
+ next();
+ },
+ // Password Typing
+ ...(new Array(login.passwordLength).fill(
+ (_idx: number) => (next: () => void) => {
+ const s = anim.selector('#openrc .openrc-password-anim');
+ const characterTime = getDelay(login);
+ typeCharacter(s, null, next, characterTime);
+ },
+ ).map((v, i) => v(i))),
+ (next) => {
+ const s = anim.selector('#openrc .openrc-password-anim');
+ s.style('opacity:0;transform:scaleX(0);');
+ anim.in(1, next);
+ },
+];
+const ttyStepHandlers: Step[] = [
+ (next) => anim.in(100, next),
+ (next) => {
+ const s = anim.selector(
+ `#openrc .ttylines-openrc`,
+ );
+ s.style(altHiddenStyles);
+ anim.in(1, () => {
+ s.style(altVisibleStyles);
+ next();
+ });
+ },
+ ...(() => {
+ let isBeforeFirstClear = true;
+ let ttyIdx = 0;
+ return ttyLines.map((step) => (next: () => void) => {
+ switch (step.kind) {
+ case 'text': {
+ const s = anim.selector(
+ `${isBeforeFirstClear ? '#openrc' : '#tty-' + (ttyIdx - 1)} ${
+ step.classes.map((v) => `.${v}`).join('')
+ }`,
+ );
+ s.style(altHiddenStyles);
+ anim.in(1, () => {
+ s.style(altVisibleStyles);
+ next();
+ });
+ break;
+ }
+ case 'removeNode': {
+ const s = anim.selector(
+ `${isBeforeFirstClear ? '#openrc' : '#tty-' + (ttyIdx - 1)} ${
+ step.removedItemClassList.map((v) => `.${v}`).join('')
+ }`,
+ );
+ s.style(altVisibleStyles);
+ anim.in(1, () => {
+ s.style(altHiddenStyles);
+ next();
+ });
+ break;
+ }
+ case 'time': {
+ anim.in(step.delay, next);
+ break;
+ }
+ case 'cursorVisibility': {
+ next();
+ break;
+ }
+ case 'clear': {
+ if (isBeforeFirstClear) {
+ isBeforeFirstClear = false;
+ }
+ toStage(3 + (++ttyIdx));
+ next();
+ break;
+ }
+ }
+ });
+ })(),
+];
+
+const handleSteps: Step[] = [
+ // (n) => {
+ // toStage(0);
+ // anim.in(500, n);
+ // },
+ ...biosStepHandlers,
+ ...grubStepHandlers,
+ ...openrcStepHandlers(1),
+ ...ttyStepHandlers,
+ (n) => {
+ const s = anim.selector('#app .hidden-after-anim');
+ s.style(visibleStyles);
+ anim._internal_timeline.now += 1;
+ s.style(hiddenStyles);
+ anim.in(1000, n);
+ },
+];
+const nextStep = () => handleSteps.shift()!(nextStep);
+handleSteps.push(() => {
+ anim.style('#nonexistentelement', '');
+ // we done
+});
+nextStep();
+const output = `/**
+ * @generated
+ * @license AGPL-3.0-OR-LATER
+ * @copyright 2024 memdmp
+ *
+ * Copyright (C) 2024 memdmp
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+${anim.exportToCSS()}
+${
+ stages.filter((_, i, a) => i !== a.length - 1).map((v) =>
+ `${v.selector} {
+${hiddenStageStyles}
+}
+.ttytext-removed-after-done {
+ display: inline-block;
+ opacity: 0;
+ width: 0;
+ height: 0;
+ margin-left: -100vw;
+ margin-right: 100vw;
+}
+${
+ (ttyLines.flatMap((v) =>
+ v.kind === 'text'
+ ? [
+ ...v.value.map((v) => ({
+ kind: 'text' as const,
+ ...v,
+ })),
+ ]
+ : []
+ ).flatMap((v) => [
+ ...(
+ v.kind === 'text'
+ ? [
+ v.colour
+ ? `.ttytext-block.text-\\[\\${v.colour}\\]{color:${v.colour};}`
+ : '',
+ v.bg
+ ? `.ttytext-block.bg-\\[\\${v.bg}\\]{background:${v.bg};}`
+ : '',
+ ]
+ : []
+ ),
+ ]).filter((v, i, a) => v.length !== 0 && a.indexOf(v) === i)).join('\n')
+ }
+`
+ ).join('')
+}`;
+console.log(output);
diff --git a/src/routes/boot-logo.png b/src/routes/boot-logo.png
new file mode 100644
index 0000000..fa3edc0
--- /dev/null
+++ b/src/routes/boot-logo.png
Binary files differ
diff --git a/src/routes/canaries/+page.svelte b/src/routes/canaries/+page.svelte
new file mode 100644
index 0000000..adbbdcc
--- /dev/null
+++ b/src/routes/canaries/+page.svelte
@@ -0,0 +1,5 @@
+<script lang="ts">
+ import keyStore from "./keystore";
+</script>
+
+{@debug keyStore}
diff --git a/src/routes/canaries/canaries.ts b/src/routes/canaries/canaries.ts
new file mode 100644
index 0000000..de5c44f
--- /dev/null
+++ b/src/routes/canaries/canaries.ts
@@ -0,0 +1,25 @@
+export const canaries: Canary[] = [];
+export class Canary {
+ public constructor(
+ public name: string,
+ public description: string,
+ public url: string,
+ public keyIdentifier: string,
+ ) {
+ canaries.push(this);
+ }
+}
+new Canary(
+ 'estrogen.zone, memdmp',
+ 'This canary is responsible for services hosted around estrogen.zone and yuridick.gay',
+ '/canaries/memdmp:estrogen.zone',
+ 'memdmp',
+);
+
+new Canary(
+ 'kyun.host',
+ 'This canary is responsible for the VPS provider "kyun.host"',
+ // '/canaries/napatha:kyun.host',
+ 'https://files.kyun.host/canary.txt',
+ 'napatha',
+);
diff --git a/src/routes/canaries/keystore.ts b/src/routes/canaries/keystore.ts
new file mode 100644
index 0000000..788428d
--- /dev/null
+++ b/src/routes/canaries/keystore.ts
@@ -0,0 +1,120 @@
+import { PublicKey, readCleartextMessage, readKey, verify } from 'openpgp';
+export const keyStore = new Map<string, PublicKey>();
+export const validateSignature = async (
+ message: string,
+ id: string,
+) => {
+ id = id.toUpperCase();
+ const key = keyStore.get(id) ?? keyStore.get(id.replace(/ /g, ''));
+ if (!key) throw new Error('Could not find key from keystore');
+ const signedMessage = await readCleartextMessage({
+ cleartextMessage: message,
+ });
+ const verificationResult = await verify({
+ message: signedMessage,
+ verificationKeys: key,
+ expectSigned: true,
+ });
+ return verificationResult.data;
+};
+const pushKey = async (
+ { ids, key, is_url, expectUserIds, signed_by }: {
+ ids?: string[];
+ expectUserIds?: string[];
+ key: string;
+ is_url?: boolean;
+ signed_by?: string;
+ },
+) => {
+ ids = ids ?? [];
+ if (is_url) {
+ key = await fetch(
+ new URL(key, 'https://keys.openpgp.org/vks/v1/by-fingerprint/'),
+ {},
+ ).then((v) => v.text());
+ }
+ if (signed_by) {
+ key = await validateSignature(key, signed_by);
+ }
+ const parsedKey = await readKey({
+ armoredKey: key,
+ }).then((v) => v.toPublic());
+ {
+ const missingUserIds =
+ expectUserIds?.filter((v) => !expectUserIds.includes(v)) ?? [];
+ if (missingUserIds.length) {
+ throw new Error(
+ `Key ${parsedKey.getFingerprint()} is missing User IDs: ${
+ missingUserIds.join(', ')
+ }`,
+ );
+ }
+ }
+ ids.push(
+ parsedKey.getKeyID().toHex().replace(/ /g, ''),
+ parsedKey.getFingerprint().replace(/ /g, ''),
+ ...(expectUserIds ?? []),
+ );
+ ids = ids.filter((v, i, a) => a.indexOf(v) === i).map((v) => v.toUpperCase());
+ for (const id of ids) {
+ keyStore.set(id, parsedKey);
+ }
+};
+await pushKey({
+ key: 'B546778F06BBCC8EC167DB3CD919706487B8B6DE',
+ ids: ['memdmp'],
+ expectUserIds: [
+ 'memdmp <memdmp@estrogen.zone>',
+ 'memdmp <memdmp@memeware.net>',
+ ],
+ is_url: true,
+});
+await pushKey({
+ // TODO: when primary memdmp key rotates, or when this key expires, replace this inline string with a new one
+ key: `-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+- -----BEGIN PGP PUBLIC KEY BLOCK-----
+Comment: User ID: memdmp canary keysig <memdmp-key-for-signing-canary-related-keys-for-external-services@fakemail.uwu>
+Comment: Valid from: 22 Nov 2024 18:31:11
+Comment: Valid until: 22 Nov 2027 12:00:00
+Comment: Type: 255-bit EdDSA (secret key available)
+Comment: Usage: Signing, Encryption, Certifying User IDs
+Comment: Fingerprint: 55D3582CAE78601990A8CA1DBFD0F9E61CB7D84E
+
+mDMEZ0C/3xYJKwYBBAHaRw8BAQdA5w4ET7V3FmasUc3h9sb0O0/y38LXp+IUV8Wf
+La95jm20ZG1lbWRtcCBjYW5hcnkga2V5c2lnIDxtZW1kbXAta2V5LWZvci1zaWdu
+aW5nLWNhbmFyeS1yZWxhdGVkLWtleXMtZm9yLWV4dGVybmFsLXNlcnZpY2VzQGZh
+a2VtYWlsLnV3dT6ImQQTFgoAQRYhBFXTWCyueGAZkKjKHb/Q+eYct9hOBQJnQL/f
+AhsDBQkFoz7RBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEL/Q+eYct9hO
+X68BAPPBy76J7EWb25+fj/QUD0rYyi/E2kLfGbW+PLhrB/AdAQDl5icCilAI/2xv
+X4jpGCH9KdJoClIV4g2AyKoEITKBDbg4BGdAv98SCisGAQQBl1UBBQEBB0CcYmml
+AWFCXVjIerJJrs/GA65EZDwoZowiVVTS99FvaQMBCAeIfgQYFgoAJhYhBFXTWCyu
+eGAZkKjKHb/Q+eYct9hOBQJnQL/fAhsMBQkFoz7RAAoJEL/Q+eYct9hOr2IA/22U
+2rOPevvUoiObv/DeeQlP2mvaQcOCFHp1HVF+4oHrAQDWZiihBvdIESbqm5MH0zLe
+EkEE03+lW4Zbe25P6MHsBg==
+=5NPo
+- -----END PGP PUBLIC KEY BLOCK-----
+-----BEGIN PGP SIGNATURE-----
+
+iIoEARYKADIWIQS1RnePBrvMjsFn2zzZGXBkh7i23gUCZ0DABRQcbWVtZG1wQG1l
+bWV3YXJlLm5ldAAKCRDZGXBkh7i23vV5AP9K2Q6j6cOGovTVqsWlThK7qxA2Faz+
+ZQ4KTbprMz8J4AD/bG33f9Kqg3AqehEyU2TldJs9U9Oni5AXGSGfKLJhmQc=
+=945T
+-----END PGP SIGNATURE-----
+`,
+ signed_by: 'memdmp <memdmp@memeware.net>',
+ ids: ['canary-sigkey-signing'],
+});
+await pushKey({
+ // TODO: adapt to the relevant url on current domain when up
+ key: 'https://files.catbox.moe/yf4x40.sig',
+ ids: ['napatha'],
+ expectUserIds: [
+ 'chef naphtha <naphtha@kyun.host>',
+ ],
+ is_url: true,
+ signed_by: 'canary-sigkey-signing',
+});
+
+export default keyStore;
diff --git a/src/routes/canaries/napatha:kyun.host/+server.ts b/src/routes/canaries/napatha:kyun.host/+server.ts
new file mode 100644
index 0000000..260d15e
--- /dev/null
+++ b/src/routes/canaries/napatha:kyun.host/+server.ts
@@ -0,0 +1,5 @@
+import { redirect } from '@sveltejs/kit';
+
+export const GET = () => {
+ throw redirect(307, 'https://files.kyun.host/canary.txt');
+};
diff --git a/src/routes/distro-info.ts b/src/routes/distro-info.ts
new file mode 100644
index 0000000..0c4656e
--- /dev/null
+++ b/src/routes/distro-info.ts
@@ -0,0 +1,38 @@
+/*
+ Copyright (C) 2024 memdmp
+
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+export const architecture = 'x86_64';
+export const versions = {
+ alpine: {
+ number: '3.21.0',
+ isEdge: true,
+ },
+ kernel: {
+ id: '6.6.53-0-lts',
+ humanReadable: 'Linux lts',
+ },
+ openrc: '0.55.1.ba16daf355',
+ udhcpd: '1.36.1',
+};
+export const tty = 'tty1';
+export const hostname = 'estrogen.zone';
+export const interfaces = {
+ // key: iface name
+ // value: time for it coming up, in millis
+ lo: 0,
+ eth0: Math.random() * 128 + 2,
+};
+
+if (versions.alpine.isEdge) {
+ versions.alpine.number += `_alpha${
+ new Date().getUTCFullYear().toString() +
+ new Date().getUTCMonth().toString().padStart(2, '0') +
+ new Date().getUTCDate().toString().padStart(2, '0')
+ }`;
+}
diff --git a/src/routes/license/+server.ts b/src/routes/license/+server.ts
new file mode 100644
index 0000000..14db510
--- /dev/null
+++ b/src/routes/license/+server.ts
@@ -0,0 +1,5 @@
+import license from "$lib/license.ts";
+
+export const GET = () => {
+ return new Response(license);
+};
diff --git a/src/routes/shared.ts b/src/routes/shared.ts
new file mode 100644
index 0000000..a5ff683
--- /dev/null
+++ b/src/routes/shared.ts
@@ -0,0 +1,485 @@
+/*
+ Copyright (C) 2024 memdmp
+
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+export const biosSteps = 3;
+export const biosStepInterval = 1000;
+
+const wpmToTypingSpeed = (wpm: number, avgWordLen: number) =>
+ (1 / (wpm * avgWordLen / 60)) * 1000;
+
+const averageWordLength = 4.793;
+const typingInfo = (wpm = 80) => ({
+ typingSpeedAvg: wpmToTypingSpeed(wpm, averageWordLength),
+ typingDeviation: 20, // typing speed deviation is often correlated to the typing speed - TODO: at way lower speeds, the correlation is inverse; the more deviation there is (is this also the case for fast typing? what is the inflection point if not? i forgor i went down a rabbit hole yrs ago)
+});
+export type TypingSpeed = ReturnType<typeof typingInfo>;
+export const getDelay = (typingInfo: TypingSpeed) =>
+ (Math.random() * 2 - 1) * typingInfo.typingDeviation +
+ typingInfo.typingSpeedAvg;
+export const login = {
+ username: 'lain',
+ passwordLength: 12,
+ ...typingInfo(100), // one usually has muscle memory for well-known frequently-typed credentials, hence higher wpm
+};
+export const ttyTypingInfo = typingInfo();
+
+export type RenderBlock = {
+ value: string;
+ colour?: string;
+ weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
+ italic?: boolean;
+ underlined?: boolean;
+ url?:
+ | `newtab:${string}`
+ | `currenttab:${string}`
+ | ((textObj: TTYText & { kind: 'text' }) => void);
+ bg?: string;
+ raw?: boolean;
+ dl?: string;
+};
+export type TTYText = {
+ kind: 'text';
+ renderrestriction?: 'everywhere' | 'js-only' | 'noscript';
+ value: RenderBlock[];
+ id: string;
+ classes: string[];
+} | {
+ kind: 'removeNode';
+ removedId: string;
+ removedItemClassList: string[];
+} | {
+ kind: 'time';
+ delay: number;
+} | {
+ kind: 'cursorVisibility';
+ visible: boolean;
+} | {
+ kind: 'clear';
+};
+export type Only<Obj, Keys extends keyof Obj> = {
+ [k in Keys]: Obj[k];
+};
+export type Diff<
+ T extends {
+ [k: string]: unknown;
+ },
+ U extends keyof T,
+> = ({ [P in keyof T]: P } & { [P in U]: never } & { [x: string]: never })[
+ keyof T
+];
+export type Omit<
+ T extends {
+ [k: string]: unknown;
+ },
+ K extends keyof T,
+> = Pick<
+ T,
+ Diff<T, K>
+>;
+export const ttyLines: TTYText[] = (() => {
+ const lines: TTYText[] = [];
+ let ids: string[] = [];
+ const byId = new Map<string, TTYText>();
+ let i = 69420;
+ let defaultRenderRestriction: Only<
+ TTYText & { kind: 'text' },
+ 'renderrestriction'
+ >['renderrestriction'] = 'everywhere';
+ type LimitedRenderBlock = Omit<RenderBlock, 'value'>;
+ type RenderBlockArg = [
+ text: string,
+ options?: LimitedRenderBlock,
+ ];
+ /** due to hellish css constraints, each text() call creates its own line - however, each block in a text() is joined on the same line. */
+ const text = (
+ globalOptions:
+ | Only<TTYText & { kind: 'text' }, 'renderrestriction'>
+ | RenderBlockArg,
+ ...blocks: RenderBlockArg[]
+ ) => {
+ const id = (++i).toString();
+ ids.push(id);
+ if (Array.isArray(globalOptions)) {
+ blocks.unshift(globalOptions);
+ globalOptions = {};
+ }
+ const txt = {
+ kind: 'text',
+ renderrestriction: globalOptions.renderrestriction ??
+ defaultRenderRestriction,
+ value: blocks.map(([t, o]) => ({
+ value: t,
+ colour: '#b9b9b9',
+ ...o,
+ })),
+ id,
+ classes: [`ttytext-${id}`],
+ } as TTYText;
+ lines.push(txt);
+ byId.set(id, txt);
+ return id;
+ };
+ const wait = (time: number) =>
+ lines.push({
+ kind: 'time',
+ delay: time,
+ });
+ const del = (id: string | number) => {
+ const removedId = typeof id === 'string'
+ ? id
+ : id >= 0
+ ? ids[id]
+ : ids[ids.length + id];
+ const r = byId.get(removedId);
+ if (r?.kind === 'text') {
+ lines.push({
+ kind: 'removeNode',
+ removedId,
+ removedItemClassList: r.classes,
+ });
+ r.classes.push('ttytext-removed-after-done');
+ }
+ ids = ids.filter((v) => v !== removedId);
+ };
+ const delSince = (id: string | number) => {
+ const removedId = typeof id === 'string'
+ ? id
+ : id >= 0
+ ? ids[id]
+ : ids[ids.length + id];
+ const idIndex = ids.indexOf(removedId);
+ for (let i = idIndex; i < ids.length; i++) {
+ const removedId = ids[i];
+ const r = byId.get(removedId);
+ if (r?.kind === 'text') {
+ r.classes.push('ttytext-removed-after-done');
+ lines.push({
+ kind: 'removeNode',
+ removedId,
+ removedItemClassList: r.classes,
+ });
+ }
+ }
+ ids = ids.filter((_, i) => i < idIndex);
+ };
+ const getLast = () => {
+ const v = byId.get(ids[ids.length - 1]);
+ if (v?.kind == 'text') {
+ return v.value;
+ } else return null;
+ };
+ const overwriteLast = (...newValue: RenderBlockArg[]) => {
+ const v = byId.get(ids[ids.length - 1]);
+ const l = v?.kind == 'text' ? v.renderrestriction : 'everywhere';
+ del(-1);
+ return text({
+ renderrestriction: l,
+ }, ...newValue);
+ };
+ const replaceLast = (
+ handler: (newValue: RenderBlockArg[]) => RenderBlockArg[],
+ ) =>
+ overwriteLast(
+ ...handler(
+ getLast()!.map(
+ (v) =>
+ [
+ v.value,
+ Object.fromEntries(
+ Object.entries(v).filter(([k]) => k !== 'value'),
+ ),
+ ] as const,
+ ),
+ ),
+ );
+ const everyTTYClear: (() => void)[] = [];
+ const clear = () => {
+ if (lines.find((v) => v.kind === 'clear')) {
+ delSince(lines.findLastIndex((v) => v.kind === 'clear'));
+ }
+ lines.push({ kind: 'clear' });
+ everyTTYClear.forEach((v) => v());
+ };
+
+ everyTTYClear.push(() => {
+ text({
+ renderrestriction: 'noscript',
+ }, ['This browser does not support JS. Your experience may be degraded.', {
+ bg: '#ff0000',
+ colour: '#dedede',
+ }], ['\n', {}]);
+ });
+ everyTTYClear.forEach((v) => v());
+
+ //
+
+ wait(300);
+ text({ renderrestriction: 'js-only' }, [
+ ((v) => v[Math.floor(Math.random() * v.length)])([
+ 'cool beats are stored in the balls',
+ 'found xml documents in the firmware',
+ 'overhead: "I don\'t consent" "hey thats my line"',
+ 'uwu',
+ 'i regret making this hellhole of a codebase',
+ ]),
+ {
+ colour: '#777777',
+ },
+ ]);
+ wait(1000);
+ clear();
+ text(
+ ['lain', {
+ colour: '#FFFF53',
+ weight: 700,
+ }],
+ [' in '],
+ ['mem.estrogen.zone', {
+ colour: '#17B117',
+ weight: 700,
+ }],
+ [' in '],
+ ['~', {
+ colour: '#53FFFF',
+ weight: 700,
+ }],
+ );
+ text(
+ ['❯ ', { colour: '#53FF53', weight: 600 }],
+ [''],
+ ['', { colour: '#777777' }],
+ );
+ {
+ const targetstr = '/usr/bin/env wel';
+ const completions = [
+ '/bin/sh -c "$(/usr/bin/env curl -fsSL https://blahaj.estrogen.zone/)"',
+ '/usr/local/bin/become-estradiol',
+ '/usr/bin/shellutil ansi cheatsheet 24bit',
+ '/usr/bin/env sh',
+ '/usr/bin/env wc -l src/routes/anim.css',
+ '/usr/bin/env welcome -c ~/.local/share/welcome.toml',
+ ];
+ for (const c of [...targetstr.split(''), 'COMPLETE']) {
+ replaceLast((l) => {
+ const prompt = l[l.length - 2];
+ const suggestions = l[l.length - 1];
+ if (c === 'COMPLETE') {
+ prompt[0] += suggestions[0];
+ suggestions[0] = '';
+ } else {
+ prompt[0] += c;
+ const completion = completions.find((v) => v.startsWith(prompt[0]));
+ if (completion) {
+ suggestions[0] = completion.substring(prompt[0].length);
+ } else suggestions[0] = '';
+ }
+ return l;
+ });
+ if (c === 'COMPLETE') {
+ wait(100);
+ } else wait(50 + Math.random() * 100);
+ }
+ text(['Preparing...']);
+ wait(200);
+ }
+ wait(1000);
+ clear();
+ text(
+ ['Welcome to'],
+ [''],
+ [' the Estrogen Zone!\n\n'],
+ );
+ wait(300);
+ for (const c of ' my corner of'.split('')) {
+ replaceLast((l) => {
+ l[1][0] += c;
+ return l;
+ });
+ wait(70);
+ }
+ text(
+ ['Places you can find me:', {
+ colour: '#7a7a7a',
+ }],
+ );
+ wait(300);
+ text(
+ [' - ', {
+ colour: '#7a7a7a',
+ }],
+ ['Estradiol SourceHut: ', { colour: '#cdcdcd' }],
+ );
+ wait(100);
+ replaceLast((v) => [...v, ['sh.estrogen.zone/~memdmp', {
+ colour: '#99aaff',
+ underlined: true,
+ weight: 700,
+ url: 'newtab:https://sh.estrogen.zone/~memdmp',
+ }]]);
+ wait(100);
+ text(
+ [' - ', {
+ colour: '#7a7a7a',
+ }],
+ ['On jsr: ', { colour: '#cdcdcd' }],
+ );
+ wait(600);
+ replaceLast((v) => [...v, ['jsr.io/@memdmp', {
+ colour: '#f7df23',
+ underlined: true,
+ weight: 700,
+ url: 'newtab:https://sh.estrogen.zone/~memdmp',
+ }]]);
+ wait(200);
+ text(
+ [' - ', {
+ colour: '#7a7a7a',
+ }],
+ ['In random hackerspaces', { colour: '#aaaaaa' }],
+ );
+ wait(600);
+ text(
+ ['\nYou may find these useful:', {
+ colour: '#7a7a7a',
+ }],
+ );
+ wait(400);
+ text(
+ [' - ', {
+ colour: '#7a7a7a',
+ }],
+ ['GPG Root Key: ', { colour: '#cdcdcd' }],
+ );
+ wait(1000);
+ replaceLast((v) => [...v, ['B546778F06BBCC8EC167DB3CD919706487B8B6DE', {
+ colour: '#58C7F3',
+ underlined: true,
+ weight: 700,
+ url: 'currenttab:/keys/memdmp/primary.pgp',
+ dl: 'memdmp-primary.pgp',
+ }]]);
+ wait(100);
+ replaceLast((v) => [...v, [' (root key)', {
+ colour: '#999999',
+ }]]);
+ wait(400);
+ text(
+ [' - ', {
+ colour: '#7a7a7a',
+ }],
+ );
+ wait(70);
+ replaceLast(
+ (v) => [...v, ['GPG Git Key: ', { colour: '#cdcdcd' }]],
+ );
+ wait(100);
+ replaceLast((v) => [...v, ['5134F05BD8D9DB8C6C0E1515A9439D346AB6DF4E', {
+ colour: '#F0A3B3',
+ underlined: true,
+ weight: 700,
+ url: 'currenttab:/keys/memdmp/git.pgp',
+ dl: 'memdmp-git.pgp',
+ }]]);
+ wait(100);
+ replaceLast((v) => [...v, [' (', {
+ colour: '#999999',
+ }], ['signed', {
+ colour: '#F0A3B3',
+ underlined: true,
+ weight: 500,
+ url: 'currenttab:/keys/memdmp/git.pgp.sig',
+ dl: 'memdmp-git.pgp.sig',
+ }], [')', { colour: '#999999' }]]);
+ wait(45);
+ text(
+ [' - ', {
+ colour: '#7a7a7a',
+ }],
+ );
+ wait(70);
+ replaceLast(
+ (
+ v,
+ ) => [...v, ['GPG Release Signing Key: ', {
+ colour: '#cdcdcd',
+ }]],
+ );
+ wait(100);
+ replaceLast((v) => [...v, ['0D93102265071798C7B65A4C9F0739B9E0C8FD60', {
+ colour: '#ffffff',
+ underlined: true,
+ weight: 700,
+ url: 'currenttab:/keys/memdmp/release.pgp',
+ dl: 'memdmp-release.pgp',
+ }]]);
+ wait(100);
+ replaceLast((v) => [...v, [' (', {
+ colour: '#999999',
+ }], ['signed', {
+ colour: '#ffffff',
+ underlined: true,
+ weight: 500,
+ url: 'currenttab:/keys/memdmp/release.pgp.sig',
+ dl: 'memdmp-release.pgp.sig',
+ }], [')', { colour: '#999999' }]]);
+ wait(100);
+ text(
+ [' - ', {
+ colour: '#7a7a7a',
+ }],
+ );
+ wait(35);
+ replaceLast(
+ (
+ v,
+ ) => [...v, ['Source Code: ', {
+ colour: '#cdcdcd',
+ }]],
+ );
+ wait(100);
+ replaceLast((v) => [...v, ['git.sh.estrogen.zone/~memdmp/mem.estrogen.zone', {
+ colour: '#F0A3B3',
+ underlined: true,
+ weight: 700,
+ url: 'newtab:/upstream/',
+ }]]);
+ wait(100);
+ text(
+ [' - ', {
+ colour: '#7a7a7a',
+ }],
+ );
+ wait(35);
+ replaceLast(
+ (
+ v,
+ ) => [...v, ['Canaries: ', {
+ colour: '#cdcdcd',
+ }]],
+ );
+ wait(100);
+ replaceLast((v) => [...v, ['/canaries/', {
+ colour: '#58C7F3',
+ underlined: true,
+ weight: 700,
+ url: 'currenttab:/canaries/',
+ }]]);
+ wait(5000);
+ text([
+ `<button style="padding: 12px 12px;background: #fff2;margin-top: 0.4rem;border-radius: 0.7rem;opacity:0.1;margin-top:3rem;" data-el="le funny button">have a button :3</button>`,
+ {
+ raw: true,
+ url: () => alert('i abused too much css for this i wanna cry now'),
+ },
+ ]);
+
+ //
+
+ return lines;
+})();
diff --git a/src/routes/upstream/+server.ts b/src/routes/upstream/+server.ts
new file mode 100644
index 0000000..678e4cd
--- /dev/null
+++ b/src/routes/upstream/+server.ts
@@ -0,0 +1,6 @@
+import { redirect } from "@sveltejs/kit";
+import { repository } from "$/../package.json" with { type: "json" };
+
+export const GET = () => {
+ throw redirect(307, repository.url);
+};