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 @@
1export async function buildFonts(p: progress.Ref, fonts: sg.Font[]) {
1export async function buildFonts(p: progress.Ref, fonts: sg.Font[], base: string) {
22 if (fonts.length === 0) return;
33 using node = p.start("build fonts");
44 function getArgs(vars: sg.FontVars) {
......@@ -22,7 +22,14 @@ export async function buildFonts(p: progress.Ref, fonts: sg.Font[]) {
2222 await Promise.all(fonts.map((font) =>
2323 incr.work(node, async (io, font) => {
2424 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 }
2633 await using baseBuffer = async.deferred(fs.readFile(baseFile));
2734 await Promise.all(font.subsets.map(async (subset) => {
2835 const args = getArgs(subset.vars ?? {});
......@@ -66,6 +73,44 @@ export async function buildFonts(p: progress.Ref, fonts: sg.Font[]) {
6673 ));
6774}
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
69114export async function fetchFont(ref: progress.Ref, name: string, sources: string[]) {
70115 using _ = ref.start("fetch");
71116 const errs = [];
......@@ -82,6 +127,7 @@ export async function fetchFont(ref: progress.Ref, name: string, sources: string
82127 cacheName,
83128 new Uint8Array(await response.arrayBuffer()),
84129 );
130 return cacheName;
85131 } else {
86132 errs.push(
87133 new Error(
......@@ -129,6 +175,7 @@ export async function fetchFont(ref: progress.Ref, name: string, sources: string
129175 }
130176 }
131177
178 ASSERT(errs.length > 0);
132179 throw new AggregateError(errs, `Failed to fetch the ${name} font`);
133180}
134181
framework/generate.ts+1-1
......@@ -38,7 +38,7 @@ export async function generate(p: progress.Ref) {
3838 );
3939 }
4040
41 const builtFonts = fonts.buildFonts(p, siteConfig.fonts);
41 const builtFonts = fonts.buildFonts(p, siteConfig.fonts, siteConfig.meta.base);
4242
4343 await loadMarkoCache(p);
4444
framework/watch.ts+4-2
......@@ -10,7 +10,7 @@ process.on("beforeExit", () => {
1010let watch: Watch;
1111
1212export 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" });
1414 // Catch up state by running a main build.
1515 if (!process.argv.includes("-f")) await incr.restore();
1616 watch = new Watch(rebuild);
......@@ -47,7 +47,9 @@ async function rebuild(files: string[]) {
4747 try {
4848 const formattingFiles = files.filter(x => !x.endsWith(".mdo"));
4949 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 });
5153 }
5254 } catch (err) {
5355 isRunning = false;
package-lock.json+8-8
......@@ -16,7 +16,7 @@
1616 "harfbuzzjs": "0.10.1",
1717 "hls.js": "1.6.5",
1818 "hono": "4.12.8",
19 "marko": "5.38.33",
19 "marko": "5.38.38",
2020 "msgpackr": "1.11.5",
2121 "playwright": "1.58.2",
2222 "prettier": "^3.8.1",
......@@ -694,9 +694,9 @@
694694 }
695695 },
696696 "node_modules/@marko/runtime-tags": {
697 "version": "6.0.159",
698 "resolved": "https://registry.npmjs.org/@marko/runtime-tags/-/runtime-tags-6.0.159.tgz",
699 "integrity": "sha512-gtw2oiqLKb1mbC+gsupbXL/BVBshNH2maDbiJ8s4L6BSmFlxkSmqJgkl6l6aCvoXqMh7PRqRZArwpT5RN9/Tpw==",
697 "version": "6.0.169",
698 "resolved": "https://registry.npmjs.org/@marko/runtime-tags/-/runtime-tags-6.0.169.tgz",
699 "integrity": "sha512-PApCgxeIOGR71TPx+BVwlNV9L2ej61hkDcB1ZBkCLKaUwJMdA9/LWEPMXNEJWB0Vn172iP/kwGIsi9dEPTPo1A==",
700700 "license": "MIT",
701701 "dependencies": {
702702 "@marko/compiler": "^5.39.62",
......@@ -1974,13 +1974,13 @@
19741974 }
19751975 },
19761976 "node_modules/marko": {
1977 "version": "5.38.33",
1978 "resolved": "https://registry.npmjs.org/marko/-/marko-5.38.33.tgz",
1979 "integrity": "sha512-e056TGqKjTE9CyO76eSlYLKk7P9z9uu0VGXt1Z3OrceCz728SCP/UhTas55ru9sNqihZEJ+vwv4ApwVwUsbtNg==",
1977 "version": "5.38.38",
1978 "resolved": "https://registry.npmjs.org/marko/-/marko-5.38.38.tgz",
1979 "integrity": "sha512-dH5a6x/vcxb+fq/ZdWOTIuyz/Nf0kcdfWu9GwrGzl3AeM/FM2mAyp+szRssDmKYLw0VjxHiJTp3ddACSssVPPA==",
19801980 "license": "MIT",
19811981 "dependencies": {
19821982 "@marko/compiler": "^5.39.62",
1983 "@marko/runtime-tags": "^6.0.159",
1983 "@marko/runtime-tags": "^6.0.169",
19841984 "app-module-path": "^2.2.0",
19851985 "argly": "^1.2.0",
19861986 "browser-refresh-client": "1.1.4",
package.json+1-1
......@@ -12,7 +12,7 @@
1212 "harfbuzzjs": "0.10.1",
1313 "hls.js": "1.6.5",
1414 "hono": "4.12.8",
15 "marko": "5.38.33",
15 "marko": "5.38.38",
1616 "msgpackr": "1.11.5",
1717 "playwright": "1.58.2",
1818 "prettier": "^3.8.1",
src/file-viewer/backend.ts+3-3
......@@ -163,13 +163,13 @@ app.get("/file/*", async (c, next) => {
163163 if (rangeHeader) {
164164 const ranges = http.parseRangeHeader(rangeHeader, file.size);
165165 if (ranges?.[0] && ranges.length === 1) {
166 start = ranges[0][0];
167 end = ranges[0][1];
166 start = ranges[0].start;
167 end = ranges[0].end;
168168
169169 status = 206;
170170 headers.set(
171171 "Content-Range",
172 `bytes ${ranges[0][0]}-${ranges[0][1]}/${file.size}`,
172 `bytes ${ranges[0].start}-${ranges[0].end}/${file.size}`,
173173 );
174174 headers.delete("Content-Length");
175175 } 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 = {
2020 i am a girl that is adorable and
2121 <a class="custom cite" href="https://en.wikipedia.org/wiki/Life">alive</a>.
2222 </p>
23 <p>this site contains my art.</p>
24 <h2>posts</h2>
23 <h2>art</h2>
2524 <ul class="section-list">
2625 <li>
2726 video collab:
2827 <a href="/history-of-japan-reanimated">history of japan reanimated</a>
2928 (2026-02-02)
3029 </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">
3135 <li>
3236 blog:
3337 <a href="/blog/webdev/one-year-next-app-router">
34 One Year with Next.js App Router — why we're moving on
38 everyone hates next.js
3539 </a>
3640 (2025-10-21)
3741 </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>
4042 </ul>
4143 <h2>things</h2>
4244 <ul class="section-list">
4345 <li><a href="/q+a">questions and answers</a></li>
4446 <li><a href="/log">recent activity</a></li>
4547 <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>
4750 <li><a href="/subscribe">mailing list</a></li>
4851 </ul>
4952 <h2>links</h2>
src/pages/subscribe.marko+1-3
......@@ -19,9 +19,7 @@ export const meta = {
1919 </code>
2020 <p>
2121 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 are
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.
22 it. there won't be a confirmation email for subscribing.
2523 </p>
2624 <p>to unsubscribe, send an email asking to unsubscribe.</p>
2725 <br><br><br>