⭐ Moe-Counter Compatible Website Hit Counter Written in Gleam
mayu.due.moe
hit-counter
svg
moe
1<!doctype html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <title>Mayu</title>
7 </head>
8
9 <body>
10 <style>
11 @import url("https://fonts.googleapis.com/css2?family=Overpass:ital,wght@0,100..900;1,100..900&display=swap");
12 @import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap");
13
14 body {
15 background-color: #0b1622;
16 color: rgb(159, 173, 189);
17 font-family:
18 Roboto,
19 -apple-system,
20 BlinkMacSystemFont,
21 Segoe UI,
22 Oxygen,
23 Ubuntu,
24 Cantarell,
25 Fira Sans,
26 Droid Sans,
27 Helvetica Neue,
28 sans-serif;
29 }
30
31 main {
32 background-color: #151f2e;
33 padding: 20px 35.4px;
34 border-radius: 4px;
35 display: flex;
36 gap: 80px;
37 max-width: 100%;
38 position: absolute;
39 top: 50%;
40 left: 50%;
41 transform: translate(-50%, -50%);
42 box-shadow:
43 0 0 8px -2px rgba(0, 0, 0, 0.1),
44 0 6px 20px -3px rgba(0, 0, 0, 0.2);
45 }
46
47 .example-image {
48 display: block;
49 margin: 10px 0;
50 width: 100%;
51 border-radius: 3px;
52 }
53
54 h2 {
55 font-size: 1rem;
56 font-weight: 500;
57 margin-bottom: 15px;
58 }
59
60 input,
61 select {
62 background-color: rgb(11, 22, 34);
63 color: rgb(159, 173, 189);
64 border-radius: 4px;
65 border: 0;
66 height: 40px;
67 padding: 0 15px;
68 line-height: 40px;
69 display: inline-block;
70 font-size: 0.9rem;
71 }
72
73 ::placeholder {
74 /* color: #9fadbd; */
75 color: rgb(114, 138, 161);
76 }
77
78 select {
79 color: #9fadbd;
80 margin-bottom: 35%;
81 appearance: none;
82 background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%239fadbd'%3E%3Cpath d='M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z'/%3E%3C/svg%3E");
83 background-repeat: no-repeat;
84 background-position: right 0.75em top 50%;
85 background-size: 1.25em;
86 }
87
88 select:focus {
89 outline: none;
90 }
91
92 input:focus {
93 outline: none;
94 }
95
96 .label {
97 margin-top: -10px;
98 padding-bottom: 0px;
99 font-size: 0.9rem;
100 color: rgb(114, 138, 161);
101 font-weight: normal;
102 }
103
104 pre {
105 font-family: monospace;
106 }
107
108 .copy-codes {
109 background-color: #0f1926;
110 padding: 10px;
111 border-radius: 4px;
112 color: #9fadbd;
113 font-size: 0.9rem;
114 overflow-x: auto;
115 white-space: pre-wrap;
116 word-wrap: break-word;
117 max-width: 100%;
118 margin-top: 20px;
119 }
120
121 a {
122 color: rgb(61, 180, 242);
123 text-decoration: none;
124 }
125
126 p {
127 font-size: 0.9rem;
128 color: #9fadbd;
129 line-height: 1.4;
130 }
131
132 .attribution {
133 position: absolute;
134 bottom: 20px;
135 left: 35.4px;
136 }
137
138 .attribution-2 {
139 display: none;
140 }
141
142 .counter {
143 display: flex;
144 flex-direction: column;
145 justify-content: center;
146 }
147
148 @media (max-width: 768px) {
149 main {
150 flex-direction: column;
151 gap: 40px;
152 }
153
154 .attribution-1 {
155 display: none;
156 }
157
158 .attribution-2 {
159 display: block;
160 }
161
162 .attribution {
163 position: unset;
164 margin-top: 40px;
165 }
166
167 select {
168 margin-bottom: unset;
169 }
170
171 .mascot {
172 display: none;
173 visibility: hidden;
174 }
175 }
176
177 @media (min-width: 768px) {
178 .mascot {
179 position: fixed;
180 bottom: 0;
181 right: 20px;
182 width: 300px;
183 margin: 0;
184 border-radius: 0;
185 z-index: -1;
186 display: none;
187 }
188 }
189
190 .copy-codes-label {
191 font-size: 0.9rem;
192 color: rgb(114, 138, 161);
193 font-weight: normal;
194 margin-top: 20px;
195 margin-bottom: -10px;
196 }
197
198 @media (max-width: 768px) {
199 main {
200 padding: 10px 17.7px;
201 }
202 }
203 </style>
204
205 <main>
206 <div>
207 <h2>Username</h2>
208
209 <p class="label">
210 Enter the username you'd like to use as the ID of your counter.
211 </p>
212
213 <input type="text" id="idInput" placeholder="demo" />
214
215 <p></p>
216
217 <select id="themeSelect">
218 <option value="asoul">A-SOUL</option>
219 <option value="gelbooru">Gelbooru</option>
220 <option value="moebooru">Moebooru</option>
221 <option value="rule34">Rule 34</option>
222 <option value="urushi">Urushi Yaotome</option>
223 <option value="lain">Lain Iwakura</option>
224 <option value="garukura">Girls Band Cry</option>
225 </select>
226
227 <p class="attribution attribution-1">
228 Created by
229 <a href="https://anilist.co/user/fuwn/" target="_blank">@Fuwn</a>
230
231 <br />
232
233 Source code available on
234 <a href="https://github.com/Fuwn/mayu" target="_blank">GitHub</a>
235 {{ MAYU_VERSION }}
236 </p>
237 </div>
238
239 <div class="counter">
240 <img
241 id="example"
242 src="/get/@demo"
243 alt="Example counter"
244 class="example-image"
245 />
246
247 <p class="copy-codes-label">
248 Copy one of the image embed snippets below to paste into your
249 biography
250 </p>
251
252 <pre id="copy-codes" class="copy-codes"></pre>
253
254 <p class="attribution attribution-2">
255 Created by
256 <a href="https://anilist.co/user/fuwn/" target="_blank">@Fuwn</a>
257
258 <br />
259
260 Source code available on
261 <a href="https://github.com/Fuwn/mayu" target="_blank">GitHub</a>
262 {{ MAYU_VERSION }}
263 </p>
264 </div>
265 </main>
266
267 <img src="" alt="Mascot" class="mascot" title="Mascot" />
268
269 <script>
270 const themeQueryParameter = new URLSearchParams(
271 window.location.search,
272 ).get("theme");
273 const defaultConfiguration = {
274 id: new URLSearchParams(window.location.search).get("id") || "demo",
275 theme: themeQueryParameter || "asoul",
276 };
277 let inputTimeout;
278 const idInput = document.getElementById("idInput");
279 const themeSelect = document.getElementById("themeSelect");
280 const image = document.getElementById("example");
281 const copyCodesInput = document.getElementById("copy-codes");
282 let inputValue = defaultConfiguration.id;
283 let themeValue = defaultConfiguration.theme;
284 const setCopyCodes = () => {
285 copyCodesInput.innerText = `\n\n<img src="${image.src}" alt="${inputValue}" />`;
286 };
287 const set = () => {
288 if (idInput.value !== "") {
289 inputValue = idInput.value;
290 } else {
291 inputValue = defaultConfiguration.id;
292
293 if (inputValue !== null && inputValue !== "demo") {
294 idInput.value = inputValue;
295 }
296 }
297
298 if (themeQueryParameter) {
299 themeValue = themeQueryParameter;
300 themeSelect.value = themeValue;
301 } else {
302 themeValue = themeSelect.value;
303 }
304
305 image.src = `/get/@${inputValue}?theme=${themeValue}&padding=6`;
306
307 setCopyCodes();
308 };
309 const mascots = [
310 { name: "Akaza Akari", image: "https://i.imgur.com/efamPLp.png" },
311 { name: "Akemi Homura", image: "https://i.imgur.com/8v6aSbV.png" },
312 { name: "Akiyama Mio", image: "https://i.imgur.com/fgbEdWe.png" },
313 { name: "Ali Baba", image: "https://i.imgur.com/FWC00jB.png" },
314 { name: "Cirno", image: "https://i.imgur.com/u65u8oN.png" },
315 { name: "Fubuki Atsuya", image: "https://i.imgur.com/97DaEBD.png" },
316 { name: "Gasai Yuno", image: "https://i.imgur.com/BurJnOd.png" },
317 { name: "Hatsune Miku", image: "https://i.imgur.com/HsqOvDJ.png" },
318 { name: "Hirasawa Yui", image: "https://i.imgur.com/ZttwSus.png" },
319 {
320 name: "Hitagi Senjougahara",
321 image: "https://i.imgur.com/ttQDirH.png",
322 },
323 { name: "Horo", image: "https://i.imgur.com/4F6BAMZ.png" },
324 { name: "Kaname Madoka", image: "https://i.imgur.com/TVlagl3.png" },
325 { name: "Kikuchi Makoto", image: "https://i.imgur.com/aXVz09Y.png" },
326 { name: "Kirisame Marisa", image: "https://i.imgur.com/MCvbGgt.png" },
327 { name: "Kisaragi Chihaya", image: "https://i.imgur.com/4Hdd0R8.png" },
328 { name: "Kousaka Kirino", image: "https://i.imgur.com/E0sdnuC.png" },
329 { name: "Kurisu Makise", image: "https://i.imgur.com/pMNliiX.png" },
330 { name: "Kuriyama Mirai", image: "https://i.imgur.com/TwcfQPD.png" },
331 { name: "Kuroko no Basuke", image: "https://i.imgur.com/FNgiG0D.png" },
332 { name: "Mashiro Shiina", image: "https://i.imgur.com/BxJylsO.png" },
333 { name: "Mayoi Hachikuji", image: "https://i.imgur.com/Xgzke7s.png" },
334 { name: "Miyamoto Konatsu", image: "https://i.imgur.com/GhwBI5n.png" },
335 { name: "Nagase Iori", image: "https://i.imgur.com/SRWU4e1.png" },
336 { name: "Nakano Azusa", image: "https://i.imgur.com/pfqZLHo.png" },
337 { name: "Poko Fox", image: "https://i.imgur.com/OF68sMQ.png" },
338 { name: "Remilia Scarlet", image: "https://i.imgur.com/3YSqyiI.png" },
339 { name: "Ruri Gokou", image: "https://i.imgur.com/UtjqvU6.png" },
340 { name: "Saber", image: "https://i.imgur.com/H7GtZ9j.png" },
341 { name: "Shana", image: "https://i.imgur.com/MPD95PX.png" },
342 { name: "Touwa Erio", image: "https://i.imgur.com/AFINQjc.png" },
343 { name: "Yin", image: "https://i.imgur.com/rddrYFR.png" },
344 { name: "Yoko Littner", image: "https://i.imgur.com/bEPA1rz.png" },
345 {
346 name: "Yukinoshita Yukino",
347 image: "https://i.imgur.com/uu6Dh0I.png",
348 },
349 { name: "Yuuki Asuna", image: "https://i.imgur.com/283FX36.png" },
350 { name: "Yuzuki Yukari", image: "https://i.imgur.com/84HOYIc.png" },
351 ];
352
353 set();
354 idInput.addEventListener("input", () => {
355 clearTimeout(inputTimeout);
356
357 inputTimeout = setTimeout(set, 500);
358 });
359 themeSelect.addEventListener("change", set);
360
361 const randomMascot = mascots[Math.floor(Math.random() * mascots.length)];
362
363 document.querySelector(".mascot").src = randomMascot.image;
364 document.querySelector(".mascot").alt = randomMascot.name;
365 document.querySelector(".mascot").title = randomMascot.name;
366 document.querySelector(".mascot").style.display = "block";
367 </script>
368 </body>
369</html>