silly personal website
1

Configure Feed

Select the types of activity you want to include in your feed.

chore: update to latest snarl

july (Mar 3, 2026, 1:11 PM -0300) 12964a60 f004f9b2

+117 -165
+4 -3
deno.json
··· 13 13 "~/": "./src/", 14 14 "@minify-html/deno": "jsr:@minify-html/deno@^0.18.1", 15 15 "@std/path": "jsr:@std/path@^1.1.4", 16 - "@july/snarl": "jsr:@july/snarl@^0.3.3", 16 + "@july/snarl": "jsr:@july/snarl@^0.3.4", 17 17 "@std/encoding": "jsr:@std/encoding@^1.0.10" 18 18 }, 19 19 "fmt": { ··· 21 21 "semiColons": true, 22 22 "lineWidth": 120, 23 23 "exclude": [ 24 - "./src/hash/meowmix1.ts" 24 + "./src/hash/meowmix1.ts", 25 + "./static" 25 26 ] 26 27 }, 27 28 "compilerOptions": { ··· 32 33 "lint": { 33 34 "rules": { 34 35 "tags": ["recommended"], 35 - "exclude": ["no-explicit-any"] 36 + "exclude": ["no-explicit-any", "jsx-key", "jsx-curly-braces", "jsx-button-has-type"] 36 37 } 37 38 } 38 39 }
+4 -4
deno.lock
··· 1 1 { 2 2 "version": "5", 3 3 "specifiers": { 4 - "jsr:@july/snarl@~0.3.3": "0.3.3", 4 + "jsr:@july/snarl@~0.3.4": "0.3.4", 5 5 "jsr:@minify-html/deno@~0.18.1": "0.18.1", 6 6 "jsr:@std/encoding@^1.0.10": "1.0.10", 7 7 "jsr:@std/internal@^1.0.12": "1.0.12", ··· 9 9 "npm:@musicorum/lastfm@0.4": "0.4.0" 10 10 }, 11 11 "jsr": { 12 - "@july/snarl@0.3.3": { 13 - "integrity": "b07ee48f7514e06dab971c23a96833cd2be9320093f2ae784726de4571e67a8d", 12 + "@july/snarl@0.3.4": { 13 + "integrity": "693788d89ba34a473e320af8f28f65df0d016741d9e9b32165ecf402bf129261", 14 14 "dependencies": [ 15 15 "jsr:@std/encoding", 16 16 "jsr:@std/path" ··· 71 71 }, 72 72 "workspace": { 73 73 "dependencies": [ 74 - "jsr:@july/snarl@~0.3.3", 74 + "jsr:@july/snarl@~0.3.4", 75 75 "jsr:@minify-html/deno@~0.18.1", 76 76 "jsr:@std/encoding@^1.0.10", 77 77 "jsr:@std/path@^1.1.4",
-2
src/components/Footer.tsx
··· 79 79 </a> 80 80 </li> 81 81 <li> 82 - {/* @ts-expect-error */} 83 82 <button onclick="let t=this.lastChild,n=t.nodeValue;navigator.clipboard.writeText(n.trim());if(!this.dataset.t){this.dataset.t=n;t.nodeValue=' copied to clipboard';setTimeout(()=>{t.nodeValue=this.dataset.t;delete this.dataset.t},3000)}"> 84 83 <Fluxer /> 85 84 queen#0001 86 85 </button> 87 86 </li> 88 87 <li> 89 - {/* @ts-expect-error */} 90 88 <button onclick="window.open(atob(this.textContent.trim()),'_blank','noopener')"> 91 89 <Mail /> 92 90 bWFpbG1lOmtAa3l1LnJlCg==
-1
src/components/KVTable.tsx
··· 48 48 {Object.entries(data).map(([key, [name, url]]) => ( 49 49 <div key={key} class="kv-row"> 50 50 <span class="kv-key">{key}</span> 51 - {/* @ts-expect-error */} 52 51 <a class="kv-val link" href={url} target="_blank" rel="noopener noreferrer">{name}</a> 53 52 </div> 54 53 ))}
-3
src/components/MediaItem.tsx
··· 148 148 function Art({ url }: { url: string | undefined }) { 149 149 return ( 150 150 <div class="art"> 151 - {/* @ts-expect-error */} 152 151 <img class="art-cover" alt="" src={url} onload="this.parentNode.dataset.loaded=''" onerror="this.remove()" /> 153 152 <div class="default-pattern"> 154 153 {ART_PATTERN.flat().map((n) => <span class={`art-px art-px-${n}`} />)} ··· 174 173 <div class="live"> 175 174 {platform} 176 175 </div> 177 - {/* @ts-expect-error */} 178 176 <a class="link" href={url} target="_blank" rel="noopener noreferrer"> 179 177 {action} 180 178 </a> 181 179 </div> 182 - {/* @ts-expect-error */} 183 180 <a class="body" href={url} target="_blank" rel="noopener noreferrer"> 184 181 <Art url={coverUrl} /> 185 182 <div class="info">
+6 -16
src/components/Projects.tsx
··· 145 145 <Import styles={[projects]} /> 146 146 {props.projects.map((project) => ( 147 147 <li> 148 - {/* @ts-expect-error */} 149 148 <a class="project-card" href={project.url} target="_blank" rel="noopener noreferrer"> 150 149 <span class="external-icon" aria-label="Open externally"> 151 150 <svg 152 - xmlns={/* @ts-expect-error */ "http://www.w3.org/2000/svg"} 153 - width={/* @ts-expect-error */ "18"} 154 - height={/* @ts-expect-error */ "18"} 151 + xmlns="http://www.w3.org/2000/svg" 152 + width="18" 153 + height="18" 155 154 viewBox="0 0 24 24" 156 155 fill="none" 157 156 stroke="currentColor" ··· 160 159 stroke-linejoin="round" 161 160 > 162 161 <title>Open Externally</title> 163 - {/* @ts-expect-error */} 164 162 <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> 165 - {/* @ts-expect-error */} 166 163 <path d="M12 6h-6a2 2 0 0 0 -2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-6"></path> 167 - {/* @ts-expect-error */} 168 164 <path d="M11 13l9 -9"></path> 169 - {/* @ts-expect-error */} 170 165 <path d="M15 4h5v5"></path> 171 166 </svg> 172 167 </span> ··· 183 178 : "Unknown"} 184 179 <div class="license" aria-label="License"> 185 180 <svg 186 - xmlns={/* @ts-expect-error */ "http://www.w3.org/2000/svg"} 187 - width={/* @ts-expect-error */ "12"} 188 - height={/* @ts-expect-error */ "12"} 181 + xmlns="http://www.w3.org/2000/svg" 182 + width="12" 183 + height="12" 189 184 viewBox="0 0 24 24" 190 185 fill="none" 191 186 stroke="currentColor" ··· 194 189 stroke-linejoin="round" 195 190 > 196 191 <title>License</title> 197 - {/* @ts-expect-error */} 198 192 <path d="m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"></path> 199 - {/* @ts-expect-error */} 200 193 <path d="m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"></path> 201 - {/* @ts-expect-error */} 202 194 <path d="M7 21h10"></path> 203 - {/* @ts-expect-error */} 204 195 <path d="M12 3v18"></path> 205 - {/* @ts-expect-error */} 206 196 <path d="M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2"></path> 207 197 </svg> 208 198 <span>BSD-3-Clause</span>
-1
src/global.ts
··· 6 6 import { Middleware } from "@july/snarl"; 7 7 import { Context } from "@july/snarl"; 8 8 import { AsyncLocalStorage } from "node:async_hooks"; 9 - import { ScopedStyles } from "./mech/css.ts"; 10 9 11 10 const contextStorage = new AsyncLocalStorage<Context>(); 12 11
-7
src/layout.tsx
··· 3 3 * SPDX-License-Identifier: AGPL-3.0-or-later 4 4 */ 5 5 6 - import { Context } from "@july/snarl"; 7 6 import { jsx, JsxElement, JsxNode } from "@july/snarl/jsx-runtime"; 8 7 import { css } from "./mech/css.ts"; 9 - import meowmix1 from "./hash/meowmix1.ts"; 10 8 11 9 const styles = css(` 12 10 :scope { ··· 194 192 <meta name="viewport" content="width=device-width, initial-scale=1" /> 195 193 <style type="text/css"></style> 196 194 <link rel="icon" type="image/png" href="/favicon.png" /> 197 - {/* @ts-expect-error */} 198 195 <meta property="og:site_name" content="kyu.re" /> 199 - {/* @ts-expect-error */} 200 196 <meta property="og:type" content="profile" /> 201 - {/* @ts-expect-error */} 202 197 <meta property="og:description" content="one of the girls of all time" /> 203 - {/* @ts-expect-error */} 204 198 <meta property="og:image" content="/bnuy.webp" /> 205 - {/* @ts-expect-error */} 206 199 <meta property="og:title" content="random corner" /> 207 200 <meta name="theme-color" content="#151217" /> 208 201 <link rel="stylesheet" href={`/css/${styles.scope}.css`} />
+1 -3
src/mech/css.ts
··· 5 5 6 6 import meowmix1 from "~/hash/meowmix1.ts"; 7 7 import { Context, Middleware } from "@july/snarl"; 8 - import { Component, Fragment, jsx, JsxElement, JsxNode } from "@july/snarl/jsx-runtime"; 8 + import { Component, Fragment, jsx, JsxElement } from "@july/snarl/jsx-runtime"; 9 9 import { retrieveContext } from "../global.ts"; 10 10 11 11 const CLASS_RE = /\.([a-zA-Z_][\w-]*)/g; ··· 13 13 const styleRegistry = new Map<string, string>(); 14 14 15 15 const contextualisedStyles = new WeakMap<Context<any>, Set<string>>(); 16 - type Until<T extends string, Delims extends string> = T extends `${infer Before}${Delims}${string}` ? Before 17 - : T; 18 16 19 17 type ExtractFlatClassKeys<T extends string> = T extends `${infer _}.${infer Rest}` 20 18 ? Rest extends `${infer Name}${" " | "{" | ":" | "\n" | "\t" | "," | ">" | "+" | "~" | "[" | "." | "#"}${infer Tail}`
+3 -1
src/mod.ts
··· 12 12 13 13 const router = createRouter(); 14 14 router.use(minify(), styleInjectionMiddleware(), contextMiddleware(), scopedStyles()); 15 - router.use(staticFiles("./static")); 15 + router.use(staticFiles("./static", { 16 + maxAge: 259200, 17 + })); 16 18 17 19 scan(router); 18 20
-8
src/routes/mod.tsx
··· 3 3 * SPDX-License-Identifier: AGPL-3.0-or-later 4 4 */ 5 5 6 - import { Context } from "@july/snarl"; 7 6 import { css, Import } from "~/mech/css.ts"; 8 7 import { Layout } from "~/layout.tsx"; 9 8 import { NavigationBar } from "~/components/NavigationBar.tsx"; ··· 12 11 import Projects, { ProjectLanguage } from "~/components/Projects.tsx"; 13 12 import { MediaItem } from "~/components/MediaItem.tsx"; 14 13 import { Fm } from "~/components/Fm.tsx"; 15 - import { Bluesky, Fluxer, GitHub, Mail, Tangled } from "~/components/Icon.tsx"; 16 14 import KVTable from "~/components/KVTable.tsx"; 17 15 import { Footer } from "../components/Footer.tsx"; 18 - 19 - const age = new Date(new Date().getFullYear(), 8, 1) >= new Date() 20 - ? new Date().getFullYear() - 2007 - 1 21 - : new Date().getFullYear() - 2007; 22 16 23 17 const home = css(` 24 18 .heading { ··· 199 193 linguistics, functional programming, the C programming language, and unconventional TypeScript. 200 194 </p> 201 195 <input type="checkbox" id="intro-toggle" class="read-more-checkbox" autocomplete="off" /> 202 - {/* @ts-expect-error */} 203 196 <label for="intro-toggle" class="read-more-btn link read-more-label-expand">Read more</label> 204 - {/* @ts-expect-error */} 205 197 <label for="intro-toggle" class="read-more-btn link read-more-label-collapse">Show less</label> 206 198 <div class="read-more-content"> 207 199 <div class="break" />
+1 -1
src/routes/reports.tsx
··· 14 14 return ( 15 15 <Layout class={reports.scope}> 16 16 <Import styles={[reports]} /> 17 - wip, sorry 17 + <span>wip, sorry</span> 18 18 </Layout> 19 19 ); 20 20 };
+98 -115
static/button.html
··· 1 1 <!DOCTYPE html> 2 2 <html> 3 - <head> 4 - <meta charset="utf-8"> 5 - <style> 6 - * { margin:0; padding:0 } 7 - html, body { width:88px; height:31px; overflow:hidden; background:#000 } 8 - canvas { display:block } 9 - </style> 10 - </head> 11 - <body> 12 - <canvas id="c" width="88" height="31"></canvas> 13 - <script> 14 - const canvas = document.getElementById('c'), ctx = canvas.getContext('2d'); 3 + <head> 4 + <meta charset="utf-8"> 5 + <style> 6 + * { 7 + margin: 0; 8 + padding: 0; 9 + } 10 + html, body { 11 + width: 88px; 12 + height: 31px; 13 + overflow: hidden; 14 + background: #000; 15 + } 16 + canvas { 17 + display: block; 18 + } 19 + </style> 20 + </head> 21 + <body> 22 + <canvas id="c" width="88" height="31"></canvas> 23 + <script> 24 + const canvas = document.getElementById("c"), ctx = canvas.getContext("2d"); 15 25 16 - const BUTTON_W = 88, BUTTON_H = 31; 17 - const pixelBuffer = new Uint8ClampedArray(BUTTON_W * BUTTON_H * 4); 26 + const BUTTON_W = 88, BUTTON_H = 31; 27 + const pixelBuffer = new Uint8ClampedArray(BUTTON_W * BUTTON_H * 4); 18 28 19 - let time = 0; 20 - 21 - const bayer = [0,8,2,10,12,4,14,6,3,11,1,9,15,7,13,5].map(v => v / 16); 22 - function dither(brightness, x, y) { 23 - const threshold = bayer[(y & 3) * 4 + (x & 3)]; 24 - return brightness > threshold ? 255 : 0; 25 - } 29 + let time = 0; 26 30 31 + const bayer = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5].map((v) => v / 16); 32 + function dither(brightness, x, y) { 33 + const threshold = bayer[(y & 3) * 4 + (x & 3)]; 34 + return brightness > threshold ? 255 : 0; 35 + } 27 36 28 - function wave(x, y, time) { 29 - const normX = x / BUTTON_W; 30 - const normY = y / BUTTON_H; 37 + function wave(x, y, time) { 38 + const normX = x / BUTTON_W; 39 + const normY = y / BUTTON_H; 31 40 32 - const depth = normY; 33 - const depth2 = depth * depth; 41 + const depth = normY; 42 + const depth2 = depth * depth; 34 43 35 - const frequency = 7 + 18 * depth2; 36 - const scrollSpeed = time * (0.5 + depth2 * 1.4); 44 + const frequency = 7 + 18 * depth2; 45 + const scrollSpeed = time * (0.5 + depth2 * 1.4); 37 46 38 - const w1 = Math.sin(normX * frequency + scrollSpeed + Math.sin(normX * 3.1 - time * 0.4) * 1.5); 39 - const w2 = Math.sin(normX * frequency * 0.6 - scrollSpeed * 1.2 + Math.cos(normX * 5.7 + time * 0.5) * 1.0); 40 - const w3 = Math.sin(normX * frequency * 1.4 + scrollSpeed * 0.8 + normY * 5 - time); 41 - const w4 = Math.cos(normX * frequency * 0.35 - scrollSpeed * 0.6 + Math.sin(normY * 8 + time * 0.7) * 0.8); 47 + const w1 = Math.sin(normX * frequency + scrollSpeed + Math.sin(normX * 3.1 - time * 0.4) * 1.5); 48 + const w2 = Math.sin( 49 + normX * frequency * 0.6 - scrollSpeed * 1.2 + Math.cos(normX * 5.7 + time * 0.5) * 1.0, 50 + ); 51 + const w3 = Math.sin(normX * frequency * 1.4 + scrollSpeed * 0.8 + normY * 5 - time); 52 + const w4 = Math.cos( 53 + normX * frequency * 0.35 - scrollSpeed * 0.6 + Math.sin(normY * 8 + time * 0.7) * 0.8, 54 + ); 42 55 43 - const weightedBrightness = w1 * 0.35 + w2 * 0.3 + w3 * 0.2 + w4 * 0.15; 44 - const foamCrest = Math.max(0, weightedBrightness - 0.45) * 4 * depth; 45 - const contrastScaled = weightedBrightness * (0.25 + depth2 * 0.75) + 0.4; 56 + const weightedBrightness = w1 * 0.35 + w2 * 0.3 + w3 * 0.2 + w4 * 0.15; 57 + const foamCrest = Math.max(0, weightedBrightness - 0.45) * 4 * depth; 58 + const contrastScaled = weightedBrightness * (0.25 + depth2 * 0.75) + 0.4; 46 59 47 - return Math.max(0, Math.min(1, contrastScaled + foamCrest)); 48 - } 60 + return Math.max(0, Math.min(1, contrastScaled + foamCrest)); 61 + } 49 62 50 - const glyphs = { 51 - k: ['1000', 52 - '1001', 53 - '1010', 54 - '1100', 55 - '1010', 56 - '1001'], 57 - y: ['0000', 58 - '1001', 59 - '1001', 60 - '0111', 61 - '0001', 62 - '0110'], 63 - u: ['0000', 64 - '1001', 65 - '1001', 66 - '1001', 67 - '1001', 68 - '0111'], 69 - '.': ['0', 70 - '0', 71 - '0', 72 - '0', 73 - '0', 74 - '1'], 75 - r: ['0000', 76 - '1011', 77 - '1100', 78 - '1000', 79 - '1000', 80 - '1000'], 81 - e: ['0000', 82 - '0110', 83 - '1001', 84 - '1111', 85 - '1000', 86 - '0110'], 87 - }; 63 + const glyphs = { 64 + k: ["1000", "1001", "1010", "1100", "1010", "1001"], 65 + y: ["0000", "1001", "1001", "0111", "0001", "0110"], 66 + u: ["0000", "1001", "1001", "1001", "1001", "0111"], 67 + ".": ["0", "0", "0", "0", "0", "1"], 68 + r: ["0000", "1011", "1100", "1000", "1000", "1000"], 69 + e: ["0000", "0110", "1001", "1111", "1000", "0110"], 70 + }; 88 71 89 - const charWidths = { k:4, y:4, u:4, '.':1, r:4, e:4 }; 90 - const labelChars = ['k','y','u','.','r','e']; 72 + const charWidths = { k: 4, y: 4, u: 4, ".": 1, r: 4, e: 4 }; 73 + const labelChars = ["k", "y", "u", ".", "r", "e"]; 91 74 92 - const TAG_W = 34, TAG_H = 11; 93 - const TAG_X = BUTTON_W - TAG_W; 94 - const TAG_Y = BUTTON_H - TAG_H; 95 - const TEXT_X = TAG_X + 4; 96 - const TEXT_Y = TAG_Y + 2; 75 + const TAG_W = 34, TAG_H = 11; 76 + const TAG_X = BUTTON_W - TAG_W; 77 + const TAG_Y = BUTTON_H - TAG_H; 78 + const TEXT_X = TAG_X + 4; 79 + const TEXT_Y = TAG_Y + 2; 97 80 98 - function drawTag() { 99 - ctx.fillStyle = '#000'; 100 - ctx.fillRect(TAG_X, TAG_Y, TAG_W, TAG_H); 81 + function drawTag() { 82 + ctx.fillStyle = "#000"; 83 + ctx.fillRect(TAG_X, TAG_Y, TAG_W, TAG_H); 101 84 102 - ctx.fillStyle = '#fff'; 103 - let cursorX = TEXT_X; 104 - labelChars.forEach(char => { 105 - glyphs[char].forEach((row, rowIndex) => { 106 - [...row].forEach((bit, bitIndex) => { 107 - if (bit === '1') ctx.fillRect(cursorX + bitIndex, TEXT_Y + rowIndex, 1, 1); 108 - }); 109 - }); 110 - cursorX += charWidths[char] + 1; 111 - }); 112 - } 85 + ctx.fillStyle = "#fff"; 86 + let cursorX = TEXT_X; 87 + labelChars.forEach((char) => { 88 + glyphs[char].forEach((row, rowIndex) => { 89 + [...row].forEach((bit, bitIndex) => { 90 + if (bit === "1") ctx.fillRect(cursorX + bitIndex, TEXT_Y + rowIndex, 1, 1); 91 + }); 92 + }); 93 + cursorX += charWidths[char] + 1; 94 + }); 95 + } 113 96 114 - function drawFrame() { 115 - time += 0.038; 116 - for (let y = 0; y < BUTTON_H; y++) { 117 - for (let x = 0; x < BUTTON_W; x++) { 118 - const brightness = dither(wave(x, y, time), x, y); 119 - const bufferIndex = (y * BUTTON_W + x) * 4; 120 - pixelBuffer[bufferIndex] = brightness; 121 - pixelBuffer[bufferIndex + 1] = brightness; 122 - pixelBuffer[bufferIndex + 2] = brightness; 123 - pixelBuffer[bufferIndex + 3] = 255; 124 - } 125 - } 97 + function drawFrame() { 98 + time += 0.038; 99 + for (let y = 0; y < BUTTON_H; y++) { 100 + for (let x = 0; x < BUTTON_W; x++) { 101 + const brightness = dither(wave(x, y, time), x, y); 102 + const bufferIndex = (y * BUTTON_W + x) * 4; 103 + pixelBuffer[bufferIndex] = brightness; 104 + pixelBuffer[bufferIndex + 1] = brightness; 105 + pixelBuffer[bufferIndex + 2] = brightness; 106 + pixelBuffer[bufferIndex + 3] = 255; 107 + } 108 + } 126 109 127 - ctx.putImageData(new ImageData(pixelBuffer, BUTTON_W, BUTTON_H), 0, 0); 128 - drawTag(); 110 + ctx.putImageData(new ImageData(pixelBuffer, BUTTON_W, BUTTON_H), 0, 0); 111 + drawTag(); 129 112 130 - requestAnimationFrame(drawFrame); 131 - } 113 + requestAnimationFrame(drawFrame); 114 + } 132 115 133 - drawFrame(); 134 - </script> 135 - </body> 116 + drawFrame(); 117 + </script> 118 + </body> 136 119 </html>