big update

This commit is contained in:
2026-01-01 21:27:58 -05:00
parent a1b7382288
commit 933d47c150
27 changed files with 184 additions and 73 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.env

View File

@@ -1,9 +1,11 @@
{
"tasks": {
"dev": "deno run --allow-read=./ --allow-net main.ts"
"dev": "deno run --allow-read=./ --allow-net --allow-env main.ts"
},
"imports": {
"@std/assert": "jsr:@std/assert@1",
"@std/dotenv": "jsr:@std/dotenv@^0.225.6",
"@std/fs": "jsr:@std/fs@^1.0.21",
"@std/media-types": "jsr:@std/media-types@^1.1.0"
}
}

23
deno.lock generated
View File

@@ -2,8 +2,11 @@
"version": "5",
"specifiers": {
"jsr:@std/assert@1": "1.0.16",
"jsr:@std/dotenv@~0.225.6": "0.225.6",
"jsr:@std/fs@^1.0.21": "1.0.21",
"jsr:@std/internal@^1.0.12": "1.0.12",
"jsr:@std/media-types@^1.1.0": "1.1.0"
"jsr:@std/media-types@^1.1.0": "1.1.0",
"jsr:@std/path@^1.1.4": "1.1.4"
},
"jsr": {
"@std/assert@1.0.16": {
@@ -12,16 +15,34 @@
"jsr:@std/internal"
]
},
"@std/dotenv@0.225.6": {
"integrity": "1d6f9db72f565bd26790fa034c26e45ecb260b5245417be76c2279e5734c421b"
},
"@std/fs@1.0.21": {
"integrity": "d720fe1056d78d43065a4d6e0eeb2b19f34adb8a0bc7caf3a4dbf1d4178252cd",
"dependencies": [
"jsr:@std/internal",
"jsr:@std/path"
]
},
"@std/internal@1.0.12": {
"integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027"
},
"@std/media-types@1.1.0": {
"integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
},
"@std/path@1.1.4": {
"integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5",
"dependencies": [
"jsr:@std/internal"
]
}
},
"workspace": {
"dependencies": [
"jsr:@std/assert@1",
"jsr:@std/dotenv@~0.225.6",
"jsr:@std/fs@^1.0.21",
"jsr:@std/media-types@^1.1.0"
]
}

76
main.ts
View File

@@ -1,27 +1,22 @@
import path from "node:path";
import { walk } from "@std/fs/walk";
import "@std/dotenv/load";
import { contentType } from "@std/media-types";
const NET_PORT = 4536;
const NET_HOST = '127.0.0.1';
const WEB_ROOT = './res/';
const _PORT = Deno.env.get('NET_PORT');
if (!_PORT || isNaN(parseInt(_PORT))) throw new Error("No NET_PORT specified in env");
const NET_PORT = parseInt(_PORT);
interface FileMapping {
endpoints: string[],
path: string,
mime: string
}
const NET_HOST = Deno.env.get('NET_HOST');
if (!NET_HOST) throw new Error("No NET_HOST specified in env");
const mappings: FileMapping[] = [
{
endpoints: ['/', '/index.html'],
path: "/index.html",
mime: "text/html"
},
{
endpoints: ['/style.css'],
path: "/style.css",
mime: "text/css"
}
] as const;
const WEB_ROOT = Deno.env.get('WEB_ROOT');
if (!WEB_ROOT) throw new Error("No WEB_ROOT specified in env");
const DIR_ROOT = path.join(Deno.cwd(), WEB_ROOT);
const FILES = (await Array.fromAsync(walk(DIR_ROOT))).filter(entry => entry.isFile).map(entry => entry.path.replaceAll(Deno.cwd(), '').replaceAll('\\', '/'));
console.log(FILES)
Deno.serve({
hostname: NET_HOST,
@@ -34,21 +29,34 @@ Deno.serve({
headers: { "Content-Type": "text/plain" }
});
const url = new URL(req.url);
try {
const url = new URL(req.url);
if (url.pathname == '/') url.pathname = '/index.html';
if (req.headers.get('user-agent')?.includes("Mobile") && url.pathname == '/') url.pathname = '/mobile.html';
else if (url.pathname == '/') url.pathname = '/index.html';
const mapping = mappings.find(val => val.endpoints.some(val => url.pathname === val));
const match = FILES.find(file => file.replace(WEB_ROOT, '') == url.pathname);
console.log(`${addr.remoteAddr.hostname}:${addr.remoteAddr.port} ${req.method} ${url.pathname} | mapping exists: ${typeof mapping !== 'undefined'}`);
return new Promise<Response>(resolve => {
if (mapping) {
Deno.readFile(path.join(WEB_ROOT, mapping.path)).then(data => {
resolve(new Response(data, { headers: { "Content-Type": mapping.mime }}));
}).catch(reason => {
console.error(reason);
resolve(notFound);
});
} else resolve(notFound);
});
console.log(`${addr.remoteAddr.hostname}:${addr.remoteAddr.port} ${req.method} ${url.pathname} | mapping exists: ${typeof match !== 'undefined'}`);
return new Promise<Response>(resolve => {
if (match) {
Deno.readFile(path.join(Deno.cwd(), match)).then(data => {
const headers = new Headers();
const last = match.split('/').at(-1);
if (last) {
const mime = contentType(last.substring(last.indexOf('.')));
if (mime) headers.set("Content-Type", mime);
}
resolve(new Response(data, { headers }));
}).catch(reason => {
console.error(reason);
resolve(notFound);
});
} else resolve(notFound);
});
} catch {
return notFound;
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

BIN
res/imgs/Miku_With_Tag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

BIN
res/imgs/New_Drawing1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

BIN
res/imgs/Wing_of_Form.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
res/imgs/Wing_of_Sound.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
res/imgs/absolute_comfy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
res/imgs/cmpfpf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
res/imgs/miku_hammer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
res/imgs/poco_eevee.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -8,7 +8,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0&icon_names=arrow_downward" />
<script src="./script.js"></script>
</head>
<body>
@@ -21,20 +21,25 @@
<h1 style="text-align: right; font-size: 2vw;">We would like to present the highlights.</h1>
<div style="flex: 1;"></div>
<h1 style="font-size: 2vw;">See Below</h1>
<span class="material-symbols-outlined" style="text-align: center; user-select: none; font-size: 2vw;">arrow_downward</span>
<span style="display: flex; flex-direction: column; align-items: center; width: auto;"><button class="attention" onclick="scrollToHighlights()">VIEW HIGHLIGHTS</button></span>
<div style="flex: 0.2;"></div>
</header>
<hr class="break"></hr>
<hr class="break" id="highlights"></hr>
<!-- top reactions -->
<div style="padding: 0 5vw 0 5vw">
<h2 style="font-size: 4vw; margin-bottom: 0;"><span style="font-weight: 300;">Is it just me,</span> or is it comfy in here?</h2>
<h2 style="font-size: 2vw; margin-top: 0;">Here are the top 10 most used reactions:</h2>
<ul>
<div style="display: flex; align-items: center;">
<h2 style="font-size: 4vw; margin: 0;"><span style="font-weight: 300;">Is it just me,</span> or is it comfy in here?</h2>
<img src="/imgs/digiral/Relaxed_-_Miku_and_Strawberry_Milk_Tea.png" style="max-height: 10vh; border-radius: 50%;">
</div>
<div style="display: flex; align-items: center;">
<img src="/imgs/absolute_comfy.png" style="max-height: 8vh; border-radius: 50%;">
<h2 style="font-size: 2vw; margin: 0;">Here are the top 10 most used reactions:</h2>
</div>
<ol style="display: flex; align-items: center; flex-direction: column; padding: 0;">
<li class="emoji-line-center">
<img class="emoji" src="https://cdn.discordapp.com/emojis/1136028749557678103.webp?size=128&animated=true" title=":squishycomfy:">
<span style="padding-left: 10px;">x13,594</span>
@@ -75,16 +80,16 @@
<img class="emoji" src="https://cdn.discordapp.com/emojis/1175603450852147262.webp?size=128" title=":40Sekai:">
<span style="padding-left: 10px;">x2,460</span>
</li>
</ul>
</ol>
</div>
<hr class="break"></hr>
<!-- top emotes -->
<div style="padding: 0 5vw 0 5vw">
<h2 style="font-size: 4vw; margin-bottom: 0;">Add some spice to your message.</h2>
<h2 style="font-size: 2vw; margin-top: 0;">Here are the top 10 most used emojis:</h2>
<ul>
<h2 style="font-size: 4vw; margin: 0;">Add some spice to your message.</h2>
<h2 style="font-size: 2vw; margin: 0;">Here are the top 10 most used emojis:</h2>
<ol style="display: flex; align-items: center; flex-direction: column; padding: 0;">
<li class="emoji-line-center">
<img class="emoji" src="https://cdn.discordapp.com/emojis/960550152689582171.webp?size=128" title=":mikukek:">
<span style="padding-left: 10px;">x10,285</span>
@@ -125,16 +130,19 @@
<img class="emoji" src="https://cdn.discordapp.com/emojis/1173317846676275230.webp?size=128" title=":Miku39Sekai:">
<span style="padding-left: 10px;">x2,394</span>
</li>
</ul>
</ol>
</div>
<hr class="break"></hr>
<!-- top stickers -->
<div style="padding: 0 5vw 0 5vw">
<h2 style="font-size: 4vw; margin-bottom: 0;">Stickerbomb!</h2>
<h2 style="font-size: 2vw; margin-top: 0;">Here are the top 10 most used stickers:</h2>
<ul>
<div style="padding: 0 5vw 0 5vw;">
<div style="display: flex; align-items: center;">
<h2 style="font-size: 4vw; margin: 0 10px 0 0;">Stickerbomb!</h2>
<img src="/imgs/digiral/Joy_-_Miku_and_Air_Guitar.png" style="max-height: 10vh; border-radius: 50%;">
</div>
<h2 style="font-size: 2vw; margin: 0;">Here are the top 10 most used stickers:</h2>
<ol style="display: flex; align-items: center; flex-direction: column; padding: 0;">
<li class="emoji-line-center">
<img class="emoji" src="https://media.discordapp.net/stickers/1421313906953158848.png?size=128&quality=lossless" title="Best Friend 39">
<span style="padding-left: 10px;">x624</span>
@@ -175,27 +183,28 @@
<img class="emoji" src="https://media.discordapp.net/stickers/1152286791706083389.png?size=128&quality=lossless" title="Show">
<span style="padding-left: 10px;">x110</span>
</li>
</ul>
</ol>
</div>
<hr class="break"></hr>
<!-- # of messages -->
<div class="column-display" style="min-height: 75svh;">
<div style="flex: 0.2;"></div>
<h1 style="font-size: 5vw; text-align: center; margin-bottom: 0;">Let's talk messages.</h1>
<h1 style="font-size: 6vw; text-align: center; font-weight: 300; margin-top: 0;">We sent <span style="font-weight: 900;">1,091,742 of them.</span></h1>
<h1 style="font-size: 2vw; text-align: center;">Jeez.</h1>
<div class="column-display" style="min-height: 75svh; align-items: center;">
<div style="flex: 0.2;"></div>
<h1 style="font-size: 5vw; text-align: center; margin: 0;">Let's talk messages.</h1>
<h1 style="font-size: 6vw; text-align: center; font-weight: 300; margin: 0;">We sent <span style="font-weight: 900;">1,091,742 of them.</span></h1>
<h1 style="font-size: 2vw; text-align: center; margin: 0;">We're glad you spent this much time with us.</h1>
<div style="flex: 0.1;"></div>
<img style="max-width: 16vw; border-radius: 50%;" src="/imgs/digiral/Excitement_-_Miku_and_Sparkling_Eyes.png">
</div>
<hr class="break"></hr>
<!-- top message reactions -->
<div style="padding: 0 5vw 0 5vw; font-size: 1.5vw;">
<h2 style="font-size: 3.2vw; margin-bottom: 0;">Some of those messages were pretty popular.</h2>
<h2 style="font-size: 2vw; margin-top: 0;">Here are each month's most reacted messages:</h2>
<ul>
<h2 style="font-size: 3.2vw; margin: 0;">Some of those messages were pretty popular.</h2>
<h2 style="font-size: 2vw; margin: 0;">Here are each month's most reacted messages:</h2>
<ul style="display: flex; align-items: center; flex-direction: column; padding: 0;">
<li>January: <a href="https://discord.com/channels/959218185356328960/960712970831278180/1327391825639506022" style="color: white;">#general-01 by powerblade3</a> - 220</li>
<li>February: <a href="https://discord.com/channels/959218185356328960/960712970831278180/1336869960051462214" style="color: white;">#general-01 by mikuoctoling39</a> - 119</li>
<li>March: <a href="https://discord.com/channels/959218185356328960/960712970831278180/1349444678029938739" style="color: white;">#general-01 by miku.hatsune</a> - 99</li>
@@ -215,9 +224,9 @@
<!-- top message counts -->
<div style="padding: 0 5vw 0 5vw; font-size: 1.5vw;">
<h2 style="font-size: 3.2vw; margin-bottom: 0;">We had several keyboard enthusiasts.</h2>
<h2 style="font-size: 2vw; margin-top: 0;">Here are the top 10 users with the most messages:</h2>
<ul>
<h2 style="font-size: 3.2vw; margin: 0;">We had several keyboard enthusiasts.</h2>
<h2 style="font-size: 2vw; margin: 0;">Here are the top 10 users with the most messages:</h2>
<ol style="display: flex; align-items: center; flex-direction: column; padding: 0;">
<li>@miraigummies - 52,746</li>
<li>@koolaidkan - 42,391</li>
<li>@iam_stove - 35,365</li>
@@ -228,16 +237,19 @@
<li>@zunda_nectar - 28,119</li>
<li>@simplename21 - 23,234</li>
<li>@kelcody - 22,782</li>
</ul>
</ol>
</div>
<hr class="break"></hr>
<!-- top 39 interactions -->
<div style="padding: 0 5vw 0 5vw; font-size: 1.5vw;">
<h2 style="font-size: 3.2vw; margin-bottom: 0;">You were always here with us.</h2>
<h2 style="font-size: 2vw; margin-top: 0;">Here are the top 10 users with the most '39' messages:</h2>
<ul>
<div style="display: flex; align-items: center;">
<h2 style="font-size: 4vw; margin: 0 10px 0 0;">You were always here with us.</h2>
<img src="/imgs/digiral/Affection_-_Miku_and_Heart_Pose.png" style="max-height: 10vh; border-radius: 50%;">
</div>
<h2 style="font-size: 2vw; margin: 0;">Here are the top 10 users with the most '39' messages:</h2>
<ol style="display: flex; align-items: center; flex-direction: column; padding: 0;">
<li>@iam_stove - 3,790</li>
<li>@aozora39 - 3,445</li>
<li>@cfm_megurine_luka - 3,325</li>
@@ -247,8 +259,8 @@
<li>@the_apricity_effect - 1,706</li>
<li>@yokoo99 - 1,354</li>
<li>@_1v40_ - 1,294</li>
<li>@iam_stove - 1,262</li>
</ul>
<li>@39.mik - 1,262</li>
</ol>
</div>
<hr class="break"></hr>
@@ -256,6 +268,10 @@
<h2 style="font-size: 2.6vw; text-align: center;">Thank you all for a great year! We're here to share the next with you.</h2>
<footer style="padding: 3vh 0 3vh;">
<div style="text-align: center;">
Credits for art on this page: @aozora39; @bready_todie; @miraigummies; @digiral; @miku.hatsune; among others of unknown origin.<br>
</div>
<br>
<div style="text-align: center;">
<a href="https://gitea.proxnet.dev/zombieb/hatsune-2025-stats" style="color: white;">Open-source.</a>
100% human-generated. Page design by (@zombieb). Data by (@poco0317).

17
res/mobile.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<title>Hatsune 2025 Stats</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./style-mobile.css">
</head>
<body>
<h2>Please note that this page was designed for desktop viewing.</h2>
<a href="/index.html">Press here to continue.</a>
</body>
</html>

9
res/script.js Normal file
View File

@@ -0,0 +1,9 @@
// deno-lint-ignore no-unused-vars
function scrollToHighlights() {
const element = document.getElementById("highlights");
element.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
console.log("scroll")
}

14
res/style-mobile.css Normal file
View File

@@ -0,0 +1,14 @@
body {
margin: 0;
padding: 0 5vw 0 5vw;
font-family: "DM Sans", sans-serif;
-webkit-font-smoothing: antialiased;
color: white;
background-color: #323232;
}
a {
color: white;
}

View File

@@ -10,8 +10,7 @@ body {
font-family: "DM Sans", sans-serif;
-webkit-font-smoothing: antialiased;
background: #12db9f;
background: linear-gradient(180deg,rgb(49, 49, 49) 99%, rgb(0, 61, 57) 100%);
background: linear-gradient(180deg, rgb(49, 49, 49) 98%, rgb(0, 61, 57) 100%);
color: white;
}
@@ -39,4 +38,28 @@ body {
.emoji {
width: 128px;
}
button {
width: 12vw;
height: 6vh;
border: none;
border-radius: 12vw;
font-size: larger;
font-family: "DM Sans", sans-serif;
color: rgb(255, 255, 255);
cursor: pointer;
background-color: #0ca97a;
transition: background-color 100ms;
}
button:hover,
focus,
active {
background-color: #0fc28c;
}