init
vi did:web:vt3e.cat
Thu, 17 Sep 2026 09:41:22 +0100
8 files changed,
613 insertions(+),
0 deletions(-)
A
.gitignore
@@ -0,0 +1,34 @@
+# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store
A
README.md
@@ -0,0 +1,15 @@
+# click + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.13. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
A
assets/main.css
@@ -0,0 +1,314 @@
+@media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + --text: 64 58.621% 94.314%; + --subtext1: 152 20.588% 73.333%; + --subtext0: 160 16.667% 64.706%; + --overlay1: 182 10.121% 48.431%; + --surface0: 200 12.245% 19.216%; + --base: 202 13.58% 15.882%; + --mantle: 200 13.846% 12.745%; + --crust: 203 14.815% 10.588%; + --pink: 316 68% 85.294%; + --blue: 216 62.105% 81.373%; + } + :root { + --shadow: var(--crust); + --on-accent: var(--crust); + } +} + +@media (prefers-color-scheme: light) { + :root { + color-scheme: light; + --text: 203 14.815% 10.588%; + --subtext1: 195 13.333% 29.412%; + --subtext0: 193 12.299% 36.667%; + --overlay1: 193 10.843% 51.176%; + --surface0: 203 22.5% 84.314%; + --base: 210 33.333% 88.235%; + --mantle: 209 29.412% 83.333%; + --crust: 212 29.412% 80%; + --pink: 318 29.333% 70.588%; + --blue: 214 25.683% 64.118%; + } + :root { + --shadow: var(--subtext1); + --on-accent: var(--text); + } +} + +:root { + --ease: cubic-bezier(0.4, 0, 0.2, 1); + --accent: var(--pink); + + --transition-properties: + margin, grid-template-rows, grid-template-columns, stroke-dasharray, + stroke-dashoffset, outline-color, outline-offset, color, + background-color, box-shadow, outline, border-color, border-radius, + font-weight, opacity, transform, backdrop-filter, text-decoration-color, + filter, scale, height, left; + --transition-duration: 0.2s; + --transition: var(--transition-duration) var(--ease); +} + +::-webkit-scrollbar { + width: 0.5rem; + height: 6px; + background: transparent; +} +::-webkit-scrollbar-thumb { + background: hsla(var(--overlay1) / 1); + border-radius: 3px; +} +::-webkit-scrollbar-track { + background: transparent; +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; + + scrollbar-width: thin; + scrollbar-color: hsla(var(--overlay1) / 1) transparent; + + -webkit-tap-highlight-color: transparent; + outline: 2px solid; + outline-offset: 2px; + outline-color: hsla(var(--blue) / 0); + + transition-property: var(--transition-properties); + transition-duration: var(--transition-duration); + transition-timing-function: var(--ease); +} +*:focus-visible { + outline-color: hsl(var(--blue)); + z-index: 5; +} +::selection { + background-color: hsla(var(--overlay1) / 1); + color: hsl(var(--crust)); +} + +body { + color: hsl(var(--text)); + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + sans-serif; + font-weight: 500; + background-color: hsl(var(--crust)); + font-size: 15px; + line-height: 1.5; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; +} + +h1, +h2, +h3, +h4 { + font-variation-settings: "ROND" 0; +} + +html, +body { + min-height: 100vh; + height: 100%; +} + +body { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 0 1rem; +} + +main { + position: relative; + max-width: 512px; + width: 100%; + margin: 0 auto; + background: hsl(var(--base)); + padding: 0.25rem; + border-radius: 2rem; + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +h1, +p { + text-align: center; +} + +header { + padding: 1rem; + border-radius: 1.75rem 1.75rem 0.5rem 0.5rem; + background: hsl(var(--mantle)); +} + +form { + padding: 1rem; + border-radius: 0.5rem 0.5rem 1.75rem 1.75rem; + background: hsl(var(--mantle)); + display: flex; + flex-direction: column; + gap: 0.5rem; + + .field { + display: flex; + flex-direction: column; + } + + label { + font-size: 0.85rem; + font-weight: 900; + color: hsl(var(--subtext0)); + } + + input { + background-color: hsl(var(--surface0)); + border: 1px solid transparent; + padding: 0.5rem; + border-radius: 0.5rem; + margin-left: -0.5rem; + margin-right: -0.5rem; + color: hsl(var(--text)); + } + + button { + position: relative; + overflow: hidden; + margin: auto; + margin-bottom: -0.5rem; + padding: 0.5rem 1rem; + background-color: hsla(var(--accent) / 0.9); + border: 2px solid transparent; + border-radius: 5rem; + color: hsl(var(--on-accent)); + font-weight: 900; + + &:hover { + background-color: hsla(var(--accent) / 1); + box-shadow: 0 0 0 0.4rem hsla(var(--accent) / 0.12); + } + + &:active { + background-color: hsla(var(--accent) / 0.8); + transform: scale(0.94); + } + + &:disabled { + cursor: wait; + opacity: 0.7; + } + + .btn-label, + .btn-state { + display: inline-block; + } + + .btn-state { + position: absolute; + inset: 50% auto auto 50%; + opacity: 0; + transform: translate(-50%, calc(-50% + 5px)); + white-space: nowrap; + + i { + display: inline-block; + width: 0.7em; + height: 0.7em; + margin-left: 0.15em; + border: 2px solid currentColor; + border-right-color: transparent; + border-radius: 50%; + vertical-align: -0.05em; + animation: spin 600ms linear infinite; + } + } + + &.is-sending { + .btn-label { + opacity: 0; + transform: translateY(-5px); + } + + .btn-state { + opacity: 1; + transform: translate(-50%, -50%); + } + } + + &.is-sent { + background-color: hsl(var(--accent)) !important; + + .btn-label { + animation: sent-pop 500ms var(--ease); + } + } + } +} + +main { + .status-box { + position: absolute; + top: 100%; + left: 0; + width: 100%; + padding-top: 0.75rem; + text-align: center; + visibility: hidden; + opacity: 0; + transform: translateY(-3px); + + &.success, + &.error { + visibility: visible; + opacity: 1; + transform: translateY(0); + } + + &.dismissing { + visibility: visible; + opacity: 0; + transform: translateY(-3px); + } + + &.success { color: hsl(var(--accent)); } + &.error { color: hsl(var(--red)); } + &.pop { animation: status-pop 320ms var(--ease); } + } +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} +@keyframes sent-pop { + 50% { + transform: scale(1.12); + } +} +@keyframes status-pop { + 50% { + transform: translateY(0) scale(1.04); + } +} +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation: none !important; + transition: none !important; + } +}
A
bun.lock
@@ -0,0 +1,26 @@
+{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "click", + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.4.2", "", { "dependencies": { "bun-types": "1.4.2" } }, "sha512-GimotNn7+ZV0uVArItBbriZsR1oNf0+WTzPkdcFrzShI7k2norL0uzEaJT8T33dWr7O/c9ZDuAFQrctKCi72oQ=="], + + "@types/node": ["@types/node@22.20.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-DZmzkmwHzXrLPAXPyKNDzlIwMMUZCVacoD25ywdy5YTKGbOx/2ld+Q38Im2zJ0vBuZP5Prd3VZutKZyXwkOS8A=="], + + "bun-types": ["bun-types@1.4.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + } +}
A
index.html
@@ -0,0 +1,100 @@
+<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>clicker</title> + + <link rel="stylesheet" href="./assets/main.css" /> + </head> + <body> + <main> + <header> + <h1>click</h1> + <p class="sub">make my phone go ""Clic k "" .!</p> + </header> + + <form id="click-form"> + <div class="field"> + <label for="message">leave a message!</label> + <input + id="message" + type="text" + placeholder="or don't that's okay idc i wont care" + autocomplete="off" + /> + </div> + + <button type="submit" id="btn" class="btn"> + <span class="btn-label">send click!</span> + <span class="btn-state" aria-hidden="true">sending <i></i></span> + </button> + </form> + <div id="status" class="status-box" role="status" aria-live="polite"></div> + </main> + + <script> + const form = document.getElementById("click-form"); + const input = document.getElementById("message"); + const btn = document.getElementById("btn"); + const statusEl = document.getElementById("status"); + let statusTimeout; + + form.addEventListener("submit", async (e) => { + e.preventDefault(); + + btn.disabled = true; + btn.classList.add("is-sending"); + window.clearTimeout(statusTimeout); + statusEl.className = "status-box"; + statusEl.textContent = ""; + + const message = input.value.trim() || undefined; + + try { + const res = await fetch("/api/click", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ message }), + }); + + const result = await res.json(); + + if (result.error) { + statusEl.className = "status-box error"; + statusEl.textContent = `couldn't click :c ${result.error.error.toLowerCase()}`; + } else { + statusEl.className = "status-box success pop"; + statusEl.textContent = "click sent!"; + input.value = ""; + btn.classList.remove("is-sending"); + btn.classList.add("is-sent"); + window.setTimeout(() => btn.classList.remove("is-sent"), 900); + } + + statusTimeout = window.setTimeout(() => { + statusEl.className = "status-box dismissing"; + window.setTimeout(() => { + statusEl.className = "status-box"; + statusEl.textContent = ""; + }, 180); + }, 3000); + } catch (err) { + statusEl.className = "status-box error"; + statusEl.textContent = + "couldn't click, try again?"; + statusTimeout = window.setTimeout(() => { + statusEl.className = "status-box dismissing"; + window.setTimeout(() => { + statusEl.className = "status-box"; + statusEl.textContent = ""; + }, 180); + }, 3000); + } finally { + btn.disabled = false; + btn.classList.remove("is-sending"); + } + }); + </script> + </body> +</html>
A
index.ts
@@ -0,0 +1,82 @@
+type ErrorResponse = { + error: string; + code: string; +}; + +type PostClicksSelfResponse = { + success: true; + dndSuppressed: boolean; +}; + +type WrappedResponse<T> = + { data: T; error: null } | { data: null; error: ErrorResponse }; + +const API_KEY = process.env.TOKEN!; +const API_URL = "https://puppyclicker-api.boundfire.com/api/v2/clicks/self"; + +async function sendClick( + message?: string, +): Promise<WrappedResponse<PostClicksSelfResponse>> { + try { + const res = await fetch(API_URL, { + method: "POST", + headers: { + Authorization: `Bearer ${API_KEY}`, + ...(message ? { "Content-Type": "application/json" } : {}), + }, + ...(message ? { body: JSON.stringify({ message }) } : {}), + }); + + const body = await res.json().catch(() => null); + + if (res.ok && body) { + return { data: body as PostClicksSelfResponse, error: null }; + } + + return { + data: null, + error: (body as ErrorResponse) ?? { + error: res.statusText || "Request failed", + code: `HTTP_${res.status}`, + }, + }; + } catch (err) { + const message = err instanceof Error ? err.message : "Internal error"; + return { + data: null, + error: { error: message, code: "FETCH_ERROR" }, + }; + } +} + +function parseMessage(body: unknown): string | undefined { + if ( + typeof body === "object" && + body !== null && + "message" in body && + typeof body.message === "string" + ) { + return body.message; + } + return undefined; +} + +const server = Bun.serve({ + routes: { + "/": () => new Response(Bun.file("./index.html")), + "/assets/main.css": () => new Response(Bun.file("./assets/main.css")), + "/api/click": { + POST: async (req) => { + const body = await req.json().catch(() => null); + const message = parseMessage(body); + + const result = await sendClick(message); + const status = result.error ? 502 : 200; + + return Response.json(result, { status }); + }, + }, + }, +}); + +console.log(`Server running at ${server.url}`);
A
package.json
@@ -0,0 +1,12 @@
+{ + "name": "click", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + } +}
A
tsconfig.json
@@ -0,0 +1,30 @@
+{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + "types": ["bun"], + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}