⭐ Moe-Counter Compatible Website Hit Counter Written in Gleam mayu.due.moe
hit-counter svg moe

feat: simple index page

fuwn.net 978a22a5 075cf1f0

verified
+174
+1
Earthfile
··· 10 10 11 11 COPY +build/erlang-shipment/ /mayu/erlang-shipment/ 12 12 COPY themes/ /mayu/themes/ 13 + COPY index.html /mayu/ 13 14 14 15 WORKDIR /mayu/ 15 16
+166
index.html
··· 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: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, 18 + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 19 + } 20 + 21 + main { 22 + background-color: #151f2e; 23 + padding: 20px 35.4px; 24 + border-radius: 4px; 25 + display: flex; 26 + gap: 80px; 27 + max-width: 100%; 28 + position: absolute; 29 + top: 50%; 30 + left: 50%; 31 + transform: translate(-50%, -50%); 32 + box-shadow: 0 0 8px -2px rgba(0, 0, 0, 0.1), 33 + 0 6px 20px -3px rgba(0, 0, 0, 0.2); 34 + } 35 + 36 + @media (max-width: 768px) { 37 + main { 38 + flex-direction: column; 39 + } 40 + } 41 + 42 + img { 43 + display: block; 44 + margin: 10px 0; 45 + width: 100%; 46 + border-radius: 3px; 47 + } 48 + 49 + h2 { 50 + font-size: 1rem; 51 + font-weight: 500; 52 + margin-bottom: 15px; 53 + } 54 + 55 + input { 56 + background-color: rgb(11, 22, 34); 57 + color: rgb(159, 173, 189); 58 + border-radius: 4px; 59 + border: 0; 60 + height: 40px; 61 + padding: 0 15px; 62 + line-height: 40px; 63 + display: inline-block; 64 + font-size: 0.9rem; 65 + } 66 + 67 + input:focus { 68 + outline: none; 69 + } 70 + 71 + .label { 72 + margin-top: -10px; 73 + padding-bottom: 0px; 74 + font-size: 0.9rem; 75 + color: rgb(114, 138, 161); 76 + font-weight: normal; 77 + } 78 + 79 + pre { 80 + font-family: monospace; 81 + } 82 + 83 + .copy-codes { 84 + background-color: #0f1926; 85 + padding: 10px; 86 + border-radius: 4px; 87 + color: #9fadbd; 88 + font-size: 0.9rem; 89 + overflow-x: auto; 90 + white-space: pre-wrap; 91 + word-wrap: break-word; 92 + max-width: 100%; 93 + margin-top: 40px; 94 + } 95 + 96 + a { 97 + color: rgb(61, 180, 242); 98 + text-decoration: none; 99 + } 100 + 101 + p { 102 + font-size: 0.9rem; 103 + color: #9fadbd; 104 + line-height: 1.4; 105 + } 106 + 107 + .attribution { 108 + position: absolute; 109 + bottom: 20px; 110 + left: 35.4px; 111 + } 112 + </style> 113 + 114 + <main> 115 + <div> 116 + <h2>Username</h2> 117 + 118 + <p class="label"> 119 + Enter the username you'd like to use as the ID of your counter. 120 + </p> 121 + 122 + <input type="text" id="inputField" placeholder="@demo" /> 123 + 124 + <p class="attribution"> 125 + Written by 126 + <a href="https://anilist.co/user/fuwn/" target="_blank">@Fuwn</a> 127 + 128 + <br /> 129 + 130 + Source code available on 131 + <a href="https://github.com/Fuwn/mayu" target="_blank">GitHub</a> 132 + </p> 133 + </div> 134 + 135 + <div class="counter"> 136 + <img 137 + id="example" 138 + src="https://counter.due.moe/get/@demo" 139 + alt="Example counter" 140 + /> 141 + 142 + <pre id="copy-codes" class="copy-codes">hi</pre> 143 + </div> 144 + </main> 145 + 146 + <script> 147 + let inputTimeout; 148 + const idInput = document.getElementById("inputField"); 149 + const image = document.getElementById("example"); 150 + const copyCodesInput = document.getElementById("copy-codes"); 151 + let inputValue = "demo"; 152 + 153 + copyCodesInput.innerText = `![${inputValue}](https://counter.due.moe/get/@${inputValue})\n\n<img src="https://counter.due.moe/get/@${inputValue}" alt="${inputValue}" />`; 154 + 155 + idInput.addEventListener("input", () => { 156 + clearTimeout(inputTimeout); 157 + 158 + inputTimeout = setTimeout(() => { 159 + inputValue = idInput.value; 160 + image.src = `https://counter.due.moe/get/@${inputValue}`; 161 + copyCodesInput.innerText = `![${inputValue}](https://counter.due.moe/get/@${inputValue})\n\n<img src="https://counter.due.moe/get/@${inputValue}" alt="${inputValue}" />`; 162 + }, 500); 163 + }); 164 + </script> 165 + </body> 166 + </html>
+7
src/request.gleam
··· 1 1 import database 2 2 import gleam/json 3 3 import gleam/string_builder 4 + import simplifile 4 5 import svg 5 6 import wisp 6 7 ··· 18 19 use _ <- middleware(request) 19 20 20 21 case wisp.path_segments(request) { 22 + [] -> 23 + case simplifile.read("index.html") { 24 + Ok(content) -> 25 + wisp.html_response(string_builder.from_string(content), 200) 26 + Error(_) -> wisp.not_found() 27 + } 21 28 ["heart-beat"] -> 22 29 wisp.html_response(string_builder.from_string("alive"), 200) 23 30 ["get", "@" <> name] -> {