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": { "tasks": {
"dev": "deno run --allow-read=./ --allow-net main.ts" "dev": "deno run --allow-read=./ --allow-net --allow-env main.ts"
}, },
"imports": { "imports": {
"@std/assert": "jsr:@std/assert@1", "@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" "@std/media-types": "jsr:@std/media-types@^1.1.0"
} }
} }

23
deno.lock generated
View File

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

60
main.ts
View File

@@ -1,27 +1,22 @@
import path from "node:path"; 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 _PORT = Deno.env.get('NET_PORT');
const NET_HOST = '127.0.0.1'; if (!_PORT || isNaN(parseInt(_PORT))) throw new Error("No NET_PORT specified in env");
const WEB_ROOT = './res/'; const NET_PORT = parseInt(_PORT);
interface FileMapping { const NET_HOST = Deno.env.get('NET_HOST');
endpoints: string[], if (!NET_HOST) throw new Error("No NET_HOST specified in env");
path: string,
mime: string
}
const mappings: FileMapping[] = [ const WEB_ROOT = Deno.env.get('WEB_ROOT');
{ if (!WEB_ROOT) throw new Error("No WEB_ROOT specified in env");
endpoints: ['/', '/index.html'],
path: "/index.html", const DIR_ROOT = path.join(Deno.cwd(), WEB_ROOT);
mime: "text/html" const FILES = (await Array.fromAsync(walk(DIR_ROOT))).filter(entry => entry.isFile).map(entry => entry.path.replaceAll(Deno.cwd(), '').replaceAll('\\', '/'));
},
{ console.log(FILES)
endpoints: ['/style.css'],
path: "/style.css",
mime: "text/css"
}
] as const;
Deno.serve({ Deno.serve({
hostname: NET_HOST, hostname: NET_HOST,
@@ -34,21 +29,34 @@ Deno.serve({
headers: { "Content-Type": "text/plain" } headers: { "Content-Type": "text/plain" }
}); });
try {
const url = new URL(req.url); 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'}`); console.log(`${addr.remoteAddr.hostname}:${addr.remoteAddr.port} ${req.method} ${url.pathname} | mapping exists: ${typeof match !== 'undefined'}`);
return new Promise<Response>(resolve => { return new Promise<Response>(resolve => {
if (mapping) { if (match) {
Deno.readFile(path.join(WEB_ROOT, mapping.path)).then(data => { Deno.readFile(path.join(Deno.cwd(), match)).then(data => {
resolve(new Response(data, { headers: { "Content-Type": mapping.mime }})); 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 => { }).catch(reason => {
console.error(reason); console.error(reason);
resolve(notFound); resolve(notFound);
}); });
} else 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 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 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> </head>
<body> <body>
@@ -22,19 +22,24 @@
<div style="flex: 1;"></div> <div style="flex: 1;"></div>
<h1 style="font-size: 2vw;">See Below</h1> <span style="display: flex; flex-direction: column; align-items: center; width: auto;"><button class="attention" onclick="scrollToHighlights()">VIEW HIGHLIGHTS</button></span>
<span class="material-symbols-outlined" style="text-align: center; user-select: none; font-size: 2vw;">arrow_downward</span>
<div style="flex: 0.2;"></div> <div style="flex: 0.2;"></div>
</header> </header>
<hr class="break"></hr> <hr class="break" id="highlights"></hr>
<!-- top reactions --> <!-- top reactions -->
<div style="padding: 0 5vw 0 5vw"> <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> <div style="display: flex; align-items: center;">
<h2 style="font-size: 2vw; margin-top: 0;">Here are the top 10 most used reactions:</h2> <h2 style="font-size: 4vw; margin: 0;"><span style="font-weight: 300;">Is it just me,</span> or is it comfy in here?</h2>
<ul> <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"> <li class="emoji-line-center">
<img class="emoji" src="https://cdn.discordapp.com/emojis/1136028749557678103.webp?size=128&animated=true" title=":squishycomfy:"> <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> <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:"> <img class="emoji" src="https://cdn.discordapp.com/emojis/1175603450852147262.webp?size=128" title=":40Sekai:">
<span style="padding-left: 10px;">x2,460</span> <span style="padding-left: 10px;">x2,460</span>
</li> </li>
</ul> </ol>
</div> </div>
<hr class="break"></hr> <hr class="break"></hr>
<!-- top emotes --> <!-- top emotes -->
<div style="padding: 0 5vw 0 5vw"> <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: 4vw; margin: 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> <h2 style="font-size: 2vw; margin: 0;">Here are the top 10 most used emojis:</h2>
<ul> <ol style="display: flex; align-items: center; flex-direction: column; padding: 0;">
<li class="emoji-line-center"> <li class="emoji-line-center">
<img class="emoji" src="https://cdn.discordapp.com/emojis/960550152689582171.webp?size=128" title=":mikukek:"> <img class="emoji" src="https://cdn.discordapp.com/emojis/960550152689582171.webp?size=128" title=":mikukek:">
<span style="padding-left: 10px;">x10,285</span> <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:"> <img class="emoji" src="https://cdn.discordapp.com/emojis/1173317846676275230.webp?size=128" title=":Miku39Sekai:">
<span style="padding-left: 10px;">x2,394</span> <span style="padding-left: 10px;">x2,394</span>
</li> </li>
</ul> </ol>
</div> </div>
<hr class="break"></hr> <hr class="break"></hr>
<!-- top stickers --> <!-- top stickers -->
<div style="padding: 0 5vw 0 5vw"> <div style="padding: 0 5vw 0 5vw;">
<h2 style="font-size: 4vw; margin-bottom: 0;">Stickerbomb!</h2> <div style="display: flex; align-items: center;">
<h2 style="font-size: 2vw; margin-top: 0;">Here are the top 10 most used stickers:</h2> <h2 style="font-size: 4vw; margin: 0 10px 0 0;">Stickerbomb!</h2>
<ul> <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"> <li class="emoji-line-center">
<img class="emoji" src="https://media.discordapp.net/stickers/1421313906953158848.png?size=128&quality=lossless" title="Best Friend 39"> <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> <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"> <img class="emoji" src="https://media.discordapp.net/stickers/1152286791706083389.png?size=128&quality=lossless" title="Show">
<span style="padding-left: 10px;">x110</span> <span style="padding-left: 10px;">x110</span>
</li> </li>
</ul> </ol>
</div> </div>
<hr class="break"></hr> <hr class="break"></hr>
<!-- # of messages --> <!-- # of messages -->
<div class="column-display" style="min-height: 75svh;"> <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-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 style="flex: 0.2;"></div> <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> </div>
<hr class="break"></hr> <hr class="break"></hr>
<!-- top message reactions --> <!-- top message reactions -->
<div style="padding: 0 5vw 0 5vw; font-size: 1.5vw;"> <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: 3.2vw; margin: 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> <h2 style="font-size: 2vw; margin: 0;">Here are each month's most reacted messages:</h2>
<ul> <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>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>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> <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 --> <!-- top message counts -->
<div style="padding: 0 5vw 0 5vw; font-size: 1.5vw;"> <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: 3.2vw; margin: 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> <h2 style="font-size: 2vw; margin: 0;">Here are the top 10 users with the most messages:</h2>
<ul> <ol style="display: flex; align-items: center; flex-direction: column; padding: 0;">
<li>@miraigummies - 52,746</li> <li>@miraigummies - 52,746</li>
<li>@koolaidkan - 42,391</li> <li>@koolaidkan - 42,391</li>
<li>@iam_stove - 35,365</li> <li>@iam_stove - 35,365</li>
@@ -228,16 +237,19 @@
<li>@zunda_nectar - 28,119</li> <li>@zunda_nectar - 28,119</li>
<li>@simplename21 - 23,234</li> <li>@simplename21 - 23,234</li>
<li>@kelcody - 22,782</li> <li>@kelcody - 22,782</li>
</ul> </ol>
</div> </div>
<hr class="break"></hr> <hr class="break"></hr>
<!-- top 39 interactions --> <!-- top 39 interactions -->
<div style="padding: 0 5vw 0 5vw; font-size: 1.5vw;"> <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> <div style="display: flex; align-items: center;">
<h2 style="font-size: 2vw; margin-top: 0;">Here are the top 10 users with the most '39' messages:</h2> <h2 style="font-size: 4vw; margin: 0 10px 0 0;">You were always here with us.</h2>
<ul> <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>@iam_stove - 3,790</li>
<li>@aozora39 - 3,445</li> <li>@aozora39 - 3,445</li>
<li>@cfm_megurine_luka - 3,325</li> <li>@cfm_megurine_luka - 3,325</li>
@@ -247,8 +259,8 @@
<li>@the_apricity_effect - 1,706</li> <li>@the_apricity_effect - 1,706</li>
<li>@yokoo99 - 1,354</li> <li>@yokoo99 - 1,354</li>
<li>@_1v40_ - 1,294</li> <li>@_1v40_ - 1,294</li>
<li>@iam_stove - 1,262</li> <li>@39.mik - 1,262</li>
</ul> </ol>
</div> </div>
<hr class="break"></hr> <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> <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;"> <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;"> <div style="text-align: center;">
<a href="https://gitea.proxnet.dev/zombieb/hatsune-2025-stats" style="color: white;">Open-source.</a> <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). 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; font-family: "DM Sans", sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
background: #12db9f; background: linear-gradient(180deg, rgb(49, 49, 49) 98%, rgb(0, 61, 57) 100%);
background: linear-gradient(180deg,rgb(49, 49, 49) 99%, rgb(0, 61, 57) 100%);
color: white; color: white;
} }
@@ -40,3 +39,27 @@ body {
.emoji { .emoji {
width: 128px; 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;
}