authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-05-05 00:34:48-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-06-01 12:56:36-07:00
log35197e9d466b5eca56137fceccd0d061cf3bbb1c
tree840897c05de2275247afc8f82f215b120ed73ed7
parentf9994128fc914b0bf22191d8305a9d2e9f435dfc
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

feat: random stuff on site and bug fixes too


9 files changed, 118 insertions(+), 26 deletions(-)

framework/font.ts+49-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1export async function buildFonts(p: progress.Ref, fonts: sg.Font[]) {1export async function buildFonts(p: progress.Ref, fonts: sg.Font[], base: string) {
2 if (fonts.length === 0) return;2 if (fonts.length === 0) return;
3 using node = p.start("build fonts");3 using node = p.start("build fonts");
4 function getArgs(vars: sg.FontVars) {4 function getArgs(vars: sg.FontVars) {
...@@ -22,7 +22,14 @@ export async function buildFonts(p: progress.Ref, fonts: sg.Font[]) {...@@ -22,7 +22,14 @@ export async function buildFonts(p: progress.Ref, fonts: sg.Font[]) {
22 await Promise.all(fonts.map((font) =>22 await Promise.all(fonts.map((font) =>
23 incr.work(node, async (io, font) => {23 incr.work(node, async (io, font) => {
24 using node = io.start(font.name);24 using node = io.start(font.name);
25 const baseFile = await fetchFont(node, font.name, font.sources);25 let baseFile: string;
26 try {
27 baseFile = await fetchFont(node, font.name, font.sources);
28 } catch (err) {
29 using _ = node.start("fetch fallback");
30 await fetchProductionFontFiles(node, io, font, base);
31 return;
32 }
26 await using baseBuffer = async.deferred(fs.readFile(baseFile));33 await using baseBuffer = async.deferred(fs.readFile(baseFile));
27 await Promise.all(font.subsets.map(async (subset) => {34 await Promise.all(font.subsets.map(async (subset) => {
28 const args = getArgs(subset.vars ?? {});35 const args = getArgs(subset.vars ?? {});
...@@ -66,6 +73,44 @@ export async function buildFonts(p: progress.Ref, fonts: sg.Font[]) {...@@ -66,6 +73,44 @@ export async function buildFonts(p: progress.Ref, fonts: sg.Font[]) {
66 ));73 ));
67}74}
6875
76async function fetchProductionFontFiles(
77 ref: progress.Ref,
78 io: incr.Io,
79 font: sg.Font,
80 base: string,
81) {
82 const fetchPromises = font.subsets.flatMap((subset) => {
83 return ["woff2", "ttf", "eot"].map(async (ext) => {
84 const assetPath = subset.asset.replace("[ext]", ext);
85 const cacheDir = path.join(os.tmpdir(), "sitegen-fonts", font.name);
86 fs.mkdirSync(cacheDir);
87 const cacheName = path.join(cacheDir, assetPath.replace(/[\/\\]/g, "_"));
88
89 if (!fs.existsSync(cacheName)) {
90 const url = new URL(assetPath, base).href;
91 try {
92 using _ = ref.start("fetch: " + assetPath);
93 const response = await fetch(url);
94 if (response.ok) {
95 await fs.writeMkdir(cacheName, new Uint8Array(await response.arrayBuffer()));
96 } else {
97 throw new Error(`Fetching fallback from ${url} failed: ${response.status} ${response.statusText}`);
98 }
99 } catch (err) {
100 throw new Error(`Fetching fallback from ${url} failed: ${error.message(err)}`);
101 }
102 }
103
104 const buffer = await fs.readFile(cacheName);
105 await io.writeAsset({
106 pathname: assetPath,
107 buffer: buffer,
108 });
109 });
110 });
111 await Promise.all(fetchPromises);
112}
113
69export async function fetchFont(ref: progress.Ref, name: string, sources: string[]) {114export async function fetchFont(ref: progress.Ref, name: string, sources: string[]) {
70 using _ = ref.start("fetch");115 using _ = ref.start("fetch");
71 const errs = [];116 const errs = [];
...@@ -82,6 +127,7 @@ export async function fetchFont(ref: progress.Ref, name: string, sources: string...@@ -82,6 +127,7 @@ export async function fetchFont(ref: progress.Ref, name: string, sources: string
82 cacheName,127 cacheName,
83 new Uint8Array(await response.arrayBuffer()),128 new Uint8Array(await response.arrayBuffer()),
84 );129 );
130 return cacheName;
85 } else {131 } else {
86 errs.push(132 errs.push(
87 new Error(133 new Error(
...@@ -129,6 +175,7 @@ export async function fetchFont(ref: progress.Ref, name: string, sources: string...@@ -129,6 +175,7 @@ export async function fetchFont(ref: progress.Ref, name: string, sources: string
129 }175 }
130 }176 }
131177
178 ASSERT(errs.length > 0);
132 throw new AggregateError(errs, `Failed to fetch the ${name} font`);179 throw new AggregateError(errs, `Failed to fetch the ${name} font`);
133}180}
134181
framework/generate.ts+1-1
...@@ -38,7 +38,7 @@ export async function generate(p: progress.Ref) {...@@ -38,7 +38,7 @@ export async function generate(p: progress.Ref) {
38 );38 );
39 }39 }
4040
41 const builtFonts = fonts.buildFonts(p, siteConfig.fonts);41 const builtFonts = fonts.buildFonts(p, siteConfig.fonts, siteConfig.meta.base);
4242
43 await loadMarkoCache(p);43 await loadMarkoCache(p);
4444
framework/watch.ts+4-2
...@@ -10,7 +10,7 @@ process.on("beforeExit", () => {...@@ -10,7 +10,7 @@ process.on("beforeExit", () => {
10let watch: Watch;10let watch: Watch;
1111
12export async function main() {12export async function main() {
13 child_process.execSync("./node_modules/.bin/dprint fmt .", { stdio: "inherit" });13 child_process.execSync("./node_modules/.bin/dprint fmt --allow-no-files .", { stdio: "inherit" });
14 // Catch up state by running a main build.14 // Catch up state by running a main build.
15 if (!process.argv.includes("-f")) await incr.restore();15 if (!process.argv.includes("-f")) await incr.restore();
16 watch = new Watch(rebuild);16 watch = new Watch(rebuild);
...@@ -47,7 +47,9 @@ async function rebuild(files: string[]) {...@@ -47,7 +47,9 @@ async function rebuild(files: string[]) {
47 try {47 try {
48 const formattingFiles = files.filter(x => !x.endsWith(".mdo"));48 const formattingFiles = files.filter(x => !x.endsWith(".mdo"));
49 if (formattingFiles.length > 0) {49 if (formattingFiles.length > 0) {
50 child_process.execSync("./node_modules/.bin/dprint fmt " + formattingFiles.join(" "), { stdio: "inherit" });50 child_process.execSync("./node_modules/.bin/dprint fmt --allow-no-files " + formattingFiles.join(" "), {
51 stdio: "inherit",
52 });
51 }53 }
52 } catch (err) {54 } catch (err) {
53 isRunning = false;55 isRunning = false;
package-lock.json+8-8
...@@ -16,7 +16,7 @@...@@ -16,7 +16,7 @@
16 "harfbuzzjs": "0.10.1",16 "harfbuzzjs": "0.10.1",
17 "hls.js": "1.6.5",17 "hls.js": "1.6.5",
18 "hono": "4.12.8",18 "hono": "4.12.8",
19 "marko": "5.38.33",19 "marko": "5.38.38",
20 "msgpackr": "1.11.5",20 "msgpackr": "1.11.5",
21 "playwright": "1.58.2",21 "playwright": "1.58.2",
22 "prettier": "^3.8.1",22 "prettier": "^3.8.1",
...@@ -694,9 +694,9 @@...@@ -694,9 +694,9 @@
694 }694 }
695 },695 },
696 "node_modules/@marko/runtime-tags": {696 "node_modules/@marko/runtime-tags": {
697 "version": "6.0.159",697 "version": "6.0.169",
698 "resolved": "https://registry.npmjs.org/@marko/runtime-tags/-/runtime-tags-6.0.159.tgz",698 "resolved": "https://registry.npmjs.org/@marko/runtime-tags/-/runtime-tags-6.0.169.tgz",
699 "integrity": "sha512-gtw2oiqLKb1mbC+gsupbXL/BVBshNH2maDbiJ8s4L6BSmFlxkSmqJgkl6l6aCvoXqMh7PRqRZArwpT5RN9/Tpw==",699 "integrity": "sha512-PApCgxeIOGR71TPx+BVwlNV9L2ej61hkDcB1ZBkCLKaUwJMdA9/LWEPMXNEJWB0Vn172iP/kwGIsi9dEPTPo1A==",
700 "license": "MIT",700 "license": "MIT",
701 "dependencies": {701 "dependencies": {
702 "@marko/compiler": "^5.39.62",702 "@marko/compiler": "^5.39.62",
...@@ -1974,13 +1974,13 @@...@@ -1974,13 +1974,13 @@
1974 }1974 }
1975 },1975 },
1976 "node_modules/marko": {1976 "node_modules/marko": {
1977 "version": "5.38.33",1977 "version": "5.38.38",
1978 "resolved": "https://registry.npmjs.org/marko/-/marko-5.38.33.tgz",1978 "resolved": "https://registry.npmjs.org/marko/-/marko-5.38.38.tgz",
1979 "integrity": "sha512-e056TGqKjTE9CyO76eSlYLKk7P9z9uu0VGXt1Z3OrceCz728SCP/UhTas55ru9sNqihZEJ+vwv4ApwVwUsbtNg==",1979 "integrity": "sha512-dH5a6x/vcxb+fq/ZdWOTIuyz/Nf0kcdfWu9GwrGzl3AeM/FM2mAyp+szRssDmKYLw0VjxHiJTp3ddACSssVPPA==",
1980 "license": "MIT",1980 "license": "MIT",
1981 "dependencies": {1981 "dependencies": {
1982 "@marko/compiler": "^5.39.62",1982 "@marko/compiler": "^5.39.62",
1983 "@marko/runtime-tags": "^6.0.159",1983 "@marko/runtime-tags": "^6.0.169",
1984 "app-module-path": "^2.2.0",1984 "app-module-path": "^2.2.0",
1985 "argly": "^1.2.0",1985 "argly": "^1.2.0",
1986 "browser-refresh-client": "1.1.4",1986 "browser-refresh-client": "1.1.4",
package.json+1-1
...@@ -12,7 +12,7 @@...@@ -12,7 +12,7 @@
12 "harfbuzzjs": "0.10.1",12 "harfbuzzjs": "0.10.1",
13 "hls.js": "1.6.5",13 "hls.js": "1.6.5",
14 "hono": "4.12.8",14 "hono": "4.12.8",
15 "marko": "5.38.33",15 "marko": "5.38.38",
16 "msgpackr": "1.11.5",16 "msgpackr": "1.11.5",
17 "playwright": "1.58.2",17 "playwright": "1.58.2",
18 "prettier": "^3.8.1",18 "prettier": "^3.8.1",
src/file-viewer/backend.ts+3-3
...@@ -163,13 +163,13 @@ app.get("/file/*", async (c, next) => {...@@ -163,13 +163,13 @@ app.get("/file/*", async (c, next) => {
163 if (rangeHeader) {163 if (rangeHeader) {
164 const ranges = http.parseRangeHeader(rangeHeader, file.size);164 const ranges = http.parseRangeHeader(rangeHeader, file.size);
165 if (ranges?.[0] && ranges.length === 1) {165 if (ranges?.[0] && ranges.length === 1) {
166 start = ranges[0][0];166 start = ranges[0].start;
167 end = ranges[0][1];167 end = ranges[0].end;
168168
169 status = 206;169 status = 206;
170 headers.set(170 headers.set(
171 "Content-Range",171 "Content-Range",
172 `bytes ${ranges[0][0]}-${ranges[0][1]}/${file.size}`,172 `bytes ${ranges[0].start}-${ranges[0].end}/${file.size}`,
173 );173 );
174 headers.delete("Content-Length");174 headers.delete("Content-Length");
175 } else {175 } else {
src/pages/dream.mdo created+42
...@@ -0,0 +1,42 @@
1---
2meta:
3 title: paper clover's media license
4---
5<div style='display:flex;align-items:center;justify-content:center;min-height:100dvh'>
6<main style='max-width:666px;line-height:1.5;text-align:justify'>
7
8[go back to the home page](/)
9
10# my life's dream
11
12if i could make art (music and videos) full time, forever, i would take that
13life in a heartbeat, never looking back. this has happened once in may 2025 for
14a month (an interlude between jobs). i called this **ART MONTH**, and it was the
15best month of my life. i want to do it again, for longer: **ART DECADE**. or if it was possible, **ART
16FOREVER**. the desire has been in me since age 16 or so. i don't want to work in
17the music industry, and i don't want to be contingent on my music being
18profitable. i want to make my art, the way i envision it.
19
20currently, i have a stupid tech job, which i am exceptionally good at. too good
21at it maybe. my knowledge and experience was built from doing crazy things in
22school and is the reason my colleagues rely on me for accurate technical advice.
23i'm the one to attempt and succeed with large, technically intricate projects.
24because of this, i make a cozy amount of money. this is convenient because it
25costs money to be alive. so, i'm saving and investing that money however
26possible to reach the level i could "retire" and get **ART DECADE** going.
27retire is the wrong word as i'll probably only work more.
28
29until then, the art here is done in my free time, elegantly balancing the time i
30spent on human connection, media consumption, and the art. i've learned so many
31times over that ruining this balance really fucks your head up.
32
33my one other dream is travel. not to places. but people. i love my friends with
34all my heart, and i want to visit all of them, especially the ones that wouldn't
35ever be able to travel to me. i do this a little now, but i only get so much
36time off work. if i could go to everyone.
37
38i will put in everything from my soul to make these two goals a reality.
39eventually, however long it takes.
40
41</main>
42</div>
src/pages/index.marko+9-6
...@@ -20,30 +20,33 @@ export const meta = {...@@ -20,30 +20,33 @@ export const meta = {
20 i am a girl that is adorable and20 i am a girl that is adorable and
21 <a class="custom cite" href="https://en.wikipedia.org/wiki/Life">alive</a>.21 <a class="custom cite" href="https://en.wikipedia.org/wiki/Life">alive</a>.
22 </p>22 </p>
23 <p>this site contains my art.</p>23 <h2>art</h2>
24 <h2>posts</h2>
25 <ul class="section-list">24 <ul class="section-list">
26 <li>25 <li>
27 video collab:26 video collab:
28 <a href="/history-of-japan-reanimated">history of japan reanimated</a>27 <a href="/history-of-japan-reanimated">history of japan reanimated</a>
29 (2026-02-02)28 (2026-02-02)
30 </li>29 </li>
30 <li>song: <a href="/in-the-summer">in the summer</a> (2025-08-08)</li>
31 <li>song: <a href="/waterfalls">waterfalls</a> (2025-01-01)</li>
32 </ul>
33 <h2>webdev blog</h2>
34 <ul class="section-list">
31 <li>35 <li>
32 blog:36 blog:
33 <a href="/blog/webdev/one-year-next-app-router">37 <a href="/blog/webdev/one-year-next-app-router">
34 One Year with Next.js App Router — why we're moving on38 everyone hates next.js
35 </a>39 </a>
36 (2025-10-21)40 (2025-10-21)
37 </li>41 </li>
38 <li>song: <a href="/in-the-summer">in the summer</a> (2025-08-08)</li>
39 <li>song: <a href="/waterfalls">waterfalls</a> (2025-01-01)</li>
40 </ul>42 </ul>
41 <h2>things</h2>43 <h2>things</h2>
42 <ul class="section-list">44 <ul class="section-list">
43 <li><a href="/q+a">questions and answers</a></li>45 <li><a href="/q+a">questions and answers</a></li>
44 <li><a href="/log">recent activity</a></li>46 <li><a href="/log">recent activity</a></li>
45 <li><a href="/file">file browser</a></li>47 <li><a href="/file">file browser</a></li>
46 <li><a href="https://git.paperclover.net/clo">code forge</a></li>48 <li><a href="https://git.paperclover.net/clo">my software</a></li>
49 <li><a href="/dream">my life's dream</a></li>
47 <li><a href="/subscribe">mailing list</a></li>50 <li><a href="/subscribe">mailing list</a></li>
48 </ul>51 </ul>
49 <h2>links</h2>52 <h2>links</h2>
src/pages/subscribe.marko+1-3
...@@ -19,9 +19,7 @@ export const meta = {...@@ -19,9 +19,7 @@ export const meta = {
19 </code>19 </code>
20 <p>20 <p>
21 i read every message, so you can say whatever else you want and i will see21 i read every message, so you can say whatever else you want and i will see
22 it. there won't be a confirmation email for subscribing. for those who are22 it. there won't be a confirmation email for subscribing.
23 close friends with me, add your name and i'll add you to the 'friends' list,
24 where i send life updates in addition to new content.
25 </p>23 </p>
26 <p>to unsubscribe, send an email asking to unsubscribe.</p>24 <p>to unsubscribe, send an email asking to unsubscribe.</p>
27 <br><br><br>25 <br><br><br>