| author | |
| committer | |
| log | 35197e9d466b5eca56137fceccd0d061cf3bbb1c |
| tree | 840897c05de2275247afc8f82f215b120ed73ed7 |
| parent | f9994128fc914b0bf22191d8305a9d2e9f435dfc |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
9 files changed, 118 insertions(+), 26 deletions(-)
framework/font.ts+49-2| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | export async function buildFonts(p: progress.Ref, fonts: sg.Font[]) { | |
| 1 | export async function buildFonts(p: progress.Ref, fonts: sg.Font[], base: string) { | |
| 2 | 2 | if (fonts.length === 0) return; |
| 3 | 3 | using node = p.start("build fonts"); |
| 4 | 4 | function getArgs(vars: sg.FontVars) { |
| ... | ... | @@ -22,7 +22,14 @@ export async function buildFonts(p: progress.Ref, fonts: sg.Font[]) { |
| 22 | 22 | await Promise.all(fonts.map((font) => |
| 23 | 23 | incr.work(node, async (io, font) => { |
| 24 | 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 | 33 | await using baseBuffer = async.deferred(fs.readFile(baseFile)); |
| 27 | 34 | await Promise.all(font.subsets.map(async (subset) => { |
| 28 | 35 | const args = getArgs(subset.vars ?? {}); |
| ... | ... | @@ -66,6 +73,44 @@ export async function buildFonts(p: progress.Ref, fonts: sg.Font[]) { |
| 66 | 73 | )); |
| 67 | 74 | } |
| 68 | 75 | |
| 76 | async 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 | ||
| 69 | 114 | export async function fetchFont(ref: progress.Ref, name: string, sources: string[]) { |
| 70 | 115 | using _ = ref.start("fetch"); |
| 71 | 116 | const errs = []; |
| ... | ... | @@ -82,6 +127,7 @@ export async function fetchFont(ref: progress.Ref, name: string, sources: string |
| 82 | 127 | cacheName, |
| 83 | 128 | new Uint8Array(await response.arrayBuffer()), |
| 84 | 129 | ); |
| 130 | return cacheName; | |
| 85 | 131 | } else { |
| 86 | 132 | errs.push( |
| 87 | 133 | new Error( |
| ... | ... | @@ -129,6 +175,7 @@ export async function fetchFont(ref: progress.Ref, name: string, sources: string |
| 129 | 175 | } |
| 130 | 176 | } |
| 131 | 177 | |
| 178 | ASSERT(errs.length > 0); | |
| 132 | 179 | throw new AggregateError(errs, `Failed to fetch the ${name} font`); |
| 133 | 180 | } |
| 134 | 181 |
framework/generate.ts+1-1| ... | ... | @@ -38,7 +38,7 @@ export async function generate(p: progress.Ref) { |
| 38 | 38 | ); |
| 39 | 39 | } |
| 40 | 40 | |
| 41 | const builtFonts = fonts.buildFonts(p, siteConfig.fonts); | |
| 41 | const builtFonts = fonts.buildFonts(p, siteConfig.fonts, siteConfig.meta.base); | |
| 42 | 42 | |
| 43 | 43 | await loadMarkoCache(p); |
| 44 | 44 |
framework/watch.ts+4-2| ... | ... | @@ -10,7 +10,7 @@ process.on("beforeExit", () => { |
| 10 | 10 | let watch: Watch; |
| 11 | 11 | |
| 12 | 12 | export 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 | 14 | // Catch up state by running a main build. |
| 15 | 15 | if (!process.argv.includes("-f")) await incr.restore(); |
| 16 | 16 | watch = new Watch(rebuild); |
| ... | ... | @@ -47,7 +47,9 @@ async function rebuild(files: string[]) { |
| 47 | 47 | try { |
| 48 | 48 | const formattingFiles = files.filter(x => !x.endsWith(".mdo")); |
| 49 | 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 | 54 | } catch (err) { |
| 53 | 55 | isRunning = false; |
package-lock.json+8-8| ... | ... | @@ -16,7 +16,7 @@ |
| 16 | 16 | "harfbuzzjs": "0.10.1", |
| 17 | 17 | "hls.js": "1.6.5", |
| 18 | 18 | "hono": "4.12.8", |
| 19 | "marko": "5.38.33", | |
| 19 | "marko": "5.38.38", | |
| 20 | 20 | "msgpackr": "1.11.5", |
| 21 | 21 | "playwright": "1.58.2", |
| 22 | 22 | "prettier": "^3.8.1", |
| ... | ... | @@ -694,9 +694,9 @@ |
| 694 | 694 | } |
| 695 | 695 | }, |
| 696 | 696 | "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==", | |
| 700 | 700 | "license": "MIT", |
| 701 | 701 | "dependencies": { |
| 702 | 702 | "@marko/compiler": "^5.39.62", |
| ... | ... | @@ -1974,13 +1974,13 @@ |
| 1974 | 1974 | } |
| 1975 | 1975 | }, |
| 1976 | 1976 | "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==", | |
| 1980 | 1980 | "license": "MIT", |
| 1981 | 1981 | "dependencies": { |
| 1982 | 1982 | "@marko/compiler": "^5.39.62", |
| 1983 | "@marko/runtime-tags": "^6.0.159", | |
| 1983 | "@marko/runtime-tags": "^6.0.169", | |
| 1984 | 1984 | "app-module-path": "^2.2.0", |
| 1985 | 1985 | "argly": "^1.2.0", |
| 1986 | 1986 | "browser-refresh-client": "1.1.4", |
package.json+1-1| ... | ... | @@ -12,7 +12,7 @@ |
| 12 | 12 | "harfbuzzjs": "0.10.1", |
| 13 | 13 | "hls.js": "1.6.5", |
| 14 | 14 | "hono": "4.12.8", |
| 15 | "marko": "5.38.33", | |
| 15 | "marko": "5.38.38", | |
| 16 | 16 | "msgpackr": "1.11.5", |
| 17 | 17 | "playwright": "1.58.2", |
| 18 | 18 | "prettier": "^3.8.1", |
src/file-viewer/backend.ts+3-3| ... | ... | @@ -163,13 +163,13 @@ app.get("/file/*", async (c, next) => { |
| 163 | 163 | if (rangeHeader) { |
| 164 | 164 | const ranges = http.parseRangeHeader(rangeHeader, file.size); |
| 165 | 165 | 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; | |
| 168 | 168 | |
| 169 | 169 | status = 206; |
| 170 | 170 | headers.set( |
| 171 | 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 | 174 | headers.delete("Content-Length"); |
| 175 | 175 | } else { |
src/pages/dream.mdo created+42| ... | ... | @@ -0,0 +1,42 @@ |
| 1 | --- | |
| 2 | meta: | |
| 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 | ||
| 12 | if i could make art (music and videos) full time, forever, i would take that | |
| 13 | life in a heartbeat, never looking back. this has happened once in may 2025 for | |
| 14 | a month (an interlude between jobs). i called this **ART MONTH**, and it was the | |
| 15 | best month of my life. i want to do it again, for longer: **ART DECADE**. or if it was possible, **ART | |
| 16 | FOREVER**. the desire has been in me since age 16 or so. i don't want to work in | |
| 17 | the music industry, and i don't want to be contingent on my music being | |
| 18 | profitable. i want to make my art, the way i envision it. | |
| 19 | ||
| 20 | currently, i have a stupid tech job, which i am exceptionally good at. too good | |
| 21 | at it maybe. my knowledge and experience was built from doing crazy things in | |
| 22 | school and is the reason my colleagues rely on me for accurate technical advice. | |
| 23 | i'm the one to attempt and succeed with large, technically intricate projects. | |
| 24 | because of this, i make a cozy amount of money. this is convenient because it | |
| 25 | costs money to be alive. so, i'm saving and investing that money however | |
| 26 | possible to reach the level i could "retire" and get **ART DECADE** going. | |
| 27 | retire is the wrong word as i'll probably only work more. | |
| 28 | ||
| 29 | until then, the art here is done in my free time, elegantly balancing the time i | |
| 30 | spent on human connection, media consumption, and the art. i've learned so many | |
| 31 | times over that ruining this balance really fucks your head up. | |
| 32 | ||
| 33 | my one other dream is travel. not to places. but people. i love my friends with | |
| 34 | all my heart, and i want to visit all of them, especially the ones that wouldn't | |
| 35 | ever be able to travel to me. i do this a little now, but i only get so much | |
| 36 | time off work. if i could go to everyone. | |
| 37 | ||
| 38 | i will put in everything from my soul to make these two goals a reality. | |
| 39 | eventually, however long it takes. | |
| 40 | ||
| 41 | </main> | |
| 42 | </div> |
src/pages/index.marko+9-6| ... | ... | @@ -20,30 +20,33 @@ export const meta = { |
| 20 | 20 | i am a girl that is adorable and |
| 21 | 21 | <a class="custom cite" href="https://en.wikipedia.org/wiki/Life">alive</a>. |
| 22 | 22 | </p> |
| 23 | <p>this site contains my art.</p> | |
| 24 | <h2>posts</h2> | |
| 23 | <h2>art</h2> | |
| 25 | 24 | <ul class="section-list"> |
| 26 | 25 | <li> |
| 27 | 26 | video collab: |
| 28 | 27 | <a href="/history-of-japan-reanimated">history of japan reanimated</a> |
| 29 | 28 | (2026-02-02) |
| 30 | 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 | 35 | <li> |
| 32 | 36 | blog: |
| 33 | 37 | <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 | |
| 35 | 39 | </a> |
| 36 | 40 | (2025-10-21) |
| 37 | 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 | 42 | </ul> |
| 41 | 43 | <h2>things</h2> |
| 42 | 44 | <ul class="section-list"> |
| 43 | 45 | <li><a href="/q+a">questions and answers</a></li> |
| 44 | 46 | <li><a href="/log">recent activity</a></li> |
| 45 | 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 | 50 | <li><a href="/subscribe">mailing list</a></li> |
| 48 | 51 | </ul> |
| 49 | 52 | <h2>links</h2> |
src/pages/subscribe.marko+1-3| ... | ... | @@ -19,9 +19,7 @@ export const meta = { |
| 19 | 19 | </code> |
| 20 | 20 | <p> |
| 21 | 21 | 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. | |
| 25 | 23 | </p> |
| 26 | 24 | <p>to unsubscribe, send an email asking to unsubscribe.</p> |
| 27 | 25 | <br><br><br> |