diff --git a/dprint.jsonc b/dprint.jsonc index d6156cb9abd972e81724434a6e03a593c2442423..6a4a51f5fc9832eaed98ffcb26fc7e3418a98259 100644 --- a/dprint.jsonc +++ b/dprint.jsonc @@ -1,14 +1,13 @@ { - "typescript": { - }, - "json": { - }, "markdown": { "textWrap": "always", }, - "malva": { - }, - "markup": { + "exec": { + "cwd": "${configDir}", + "commands": [{ + "command": "node_modules/.bin/prettier --plugin prettier-plugin-marko {{file_path}}", + "exts": ["marko"], + }], }, "excludes": [ "**/node_modules", @@ -20,5 +19,6 @@ "https://plugins.dprint.dev/markdown-0.21.1.wasm", "https://plugins.dprint.dev/g-plane/malva-v0.15.2.wasm", "https://plugins.dprint.dev/g-plane/markup_fmt-v0.27.0.wasm", + "https://plugins.dprint.dev/exec-0.6.2.json@df98f54ffd3092b8a841aedd6d098a2651f16d0a796a40535774f1a8b4b9d463", ], } diff --git a/framework/lib/add-script.marko b/framework/lib/add-script.marko index 52a1d9a98330e3a0e0b3ed8410294418393f4fa3..0a67b29542f17c26506228e095e4de2aac0bc5bb 100644 --- a/framework/lib/add-script.marko +++ b/framework/lib/add-script.marko @@ -1 +1 @@ - + diff --git a/framework/sitegen-wrap.marko b/framework/sitegen-wrap.marko index 87a2137357e15fb135531b53bf498356127d9daf..e163bdb59413bbd04c652340b2a062f21acedea4 100644 --- a/framework/sitegen-wrap.marko +++ b/framework/sitegen-wrap.marko @@ -1,2 +1,2 @@ - -<${input.page} ...input.attrs /> + +<${input.page} ...input.attrs/> diff --git a/package-lock.json b/package-lock.json index 1ccbd263d5efa344cb0a579268a0df2eff3ca4ab..cb18fc6a5221f3ec0d7909d2f38e169097c187f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,8 @@ "marko": "5.38.33", "msgpackr": "1.11.5", "playwright": "1.58.2", + "prettier": "^3.8.1", + "prettier-plugin-marko": "^4.0.9", "source-map-support": "0.5.21", "typescript": "6.0.2", "unique-names-generator": "4.7.1", @@ -2178,6 +2180,30 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-marko": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/prettier-plugin-marko/-/prettier-plugin-marko-4.0.9.tgz", + "integrity": "sha512-njqNk0oF6jolUMXNUOSlvRe6Dd50DN4NvQk3EY/6hUrU4tJw/HSwvhDF1uTYVs5KjOY5lH+x5C13C1Av9jajWA==", + "license": "MIT", + "dependencies": { + "htmljs-parser": "^5.10.2" + } + }, "node_modules/raptor-async": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/raptor-async/-/raptor-async-1.1.3.tgz", diff --git a/package.json b/package.json index a4a145bfde4af5eb984eb2c11d408f7c3ee536f0..09442d12a006ff25744562e5ed991da327a728e7 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "marko": "5.38.33", "msgpackr": "1.11.5", "playwright": "1.58.2", + "prettier": "^3.8.1", + "prettier-plugin-marko": "^4.0.9", "source-map-support": "0.5.21", "typescript": "6.0.2", "unique-names-generator": "4.7.1", diff --git a/src/blog/tags/blog-layout.marko b/src/blog/tags/blog-layout.marko index 7ca09bdf9701b898e211691adfacebb124a279f4..2a6c626f4b87c02a07997068ebd9648d6ec8b9bd 100644 --- a/src/blog/tags/blog-layout.marko +++ b/src/blog/tags/blog-layout.marko @@ -6,7 +6,7 @@ export const theme = { }; export const components = { heading: Heading, - codeBlock: CodeBlock + codeBlock: CodeBlock, }; export interface Module { @@ -35,68 +35,67 @@ export interface Context { export const markoOnly = true; - - - - + + + + - x.ext === ".mdo" && x.baseWithoutExt !== "en") - .map((x) => x.baseWithoutExt) /> - + .map((x) => x.baseWithoutExt) +)> + { $global.cloverOutline = input.outline; -})() /> +})()>
-
-${t.return} +
+ ${t.return} +

${title}

+

${description}

-

${title} -

${description}

+ + ${t.writtenByClover}• + + + ${t.translatedBy(translation)} + + • + + ${date} + + + + + ${localization.languages.en.title} + + + + ${UNWRAP(localization.languages[code]).title} + + + ${"+"} + + + + ${tag} + + - - ${t.writtenByClover} - • - - - ${t.translatedBy(translation)} - - • - - ${date} - - - - - ${localization.languages.en.title} - - - - ${UNWRAP(localization.languages[code]).title} - - - ${"+"} - - - - ${tag} - - - -
-

- -<${input.content} /> +
+
+ <${input.content}/>
import * as localization from "../localization.ts"; diff --git a/src/blog/tags/code-block.marko b/src/blog/tags/code-block.marko index 774425d57035a16f2642425c747b3a80618b008a..785737ab663ce93d73f595b18929e1f949df35fc 100644 --- a/src/blog/tags/code-block.marko +++ b/src/blog/tags/code-block.marko @@ -1,15 +1,20 @@ - + - +
-
${filename}
- -
$!{highlighted}
- + +
${filename}
+ + +
$!{
+      highlighted
+    }
+
import * as highlight from "#src/file-viewer/highlight.ts"; diff --git a/src/blog/tags/heading.marko b/src/blog/tags/heading.marko index 681937968af399872bced2cee9cf6a697c18eef7..cb6376dbb6622cd2c321b4c55e9037801aa344d6 100644 --- a/src/blog/tags/heading.marko +++ b/src/blog/tags/heading.marko @@ -1,5 +1,5 @@ - -<${'h' + level} ...attrs> + +<${"h" + level} ...attrs> § - <${content} /> + <${content}/> diff --git a/src/blog/tags/table-of-contents.marko b/src/blog/tags/table-of-contents.marko index 0c58760526fad2cc6fe9415656a5e3abe946f1cc..b9e5d2a31333d1ec8608c82b7983b83552532035 100644 --- a/src/blog/tags/table-of-contents.marko +++ b/src/blog/tags/table-of-contents.marko @@ -5,25 +5,33 @@ export interface Node { children: Node[]; } - - + + - <${input.content} /> + + <${input.content}/> +
    -
  • - +
  • + +
  • +
- - + +

Contents:

-
    -
  • -
+
    + +
  • + +
  • + +
import * as markodown from "@clo/markodown"; diff --git a/src/file-viewer/bin/file-scan.ts b/src/file-viewer/bin/file-scan.ts index 0deb9ad522931ef17dc5701e84333675c51cbe02..7ff63f949ce37d7a9664920c9c2cd0211b8fda00 100644 --- a/src/file-viewer/bin/file-scan.ts +++ b/src/file-viewer/bin/file-scan.ts @@ -15,8 +15,7 @@ const sotToken = process.env.CLOVER_SOT_KEY; export async function main() { const start = performance.now(); using _ = log.startWidget({ - format: ({ now }) => - `paper clover's file scanner [${((now - start) / 1000).toFixed(1)}s]`, + format: ({ now }) => `paper clover's file scanner [${((now - start) / 1000).toFixed(1)}s]`, }); const promises = new async.PromiseAggregator(); @@ -63,10 +62,10 @@ export async function main() { } if ( - !mediaFile || // All processes must be performed if there is no file. + !mediaFile // All processes must be performed if there is no file. // Rerun all processors if it changed - stat.size !== mediaFile.size || - stat.mtime.getTime() !== mediaFile.date.getTime() + || stat.size !== mediaFile.size + || stat.mtime.getTime() !== mediaFile.date.getTime() ) { promises.push(updateMetadata({ path, publicPath, stat, mediaFile })); } else { @@ -101,9 +100,9 @@ export async function main() { }); let date = stat.mtime; if ( - mediaFile && - mediaFile.date.getTime() < stat.mtime.getTime() && - Date.now() - stat.mtime.getTime() < monthMilliseconds + mediaFile + && mediaFile.date.getTime() < stat.mtime.getTime() + && Date.now() - stat.mtime.getTime() < monthMilliseconds ) { date = mediaFile.date; console.warn( @@ -137,7 +136,7 @@ export async function main() { }, () => ({ priority: -1 }), ); - const queueFileProcessor = queue.wrap(async function ({ + const queueFileProcessor = queue.wrap(async function({ path, stat, mediaFile, @@ -190,9 +189,7 @@ export async function main() { node.passive = true; const ext = mediaFile.extensionNonEmpty.toLowerCase(); - let possible = processors.filter((p) => - p.include ? p.include.has(ext) : !p.exclude?.has(ext) - ); + let possible = processors.filter((p) => p.include ? p.include.has(ext) : !p.exclude?.has(ext)); if (possible.length === 0) return; ASSERT(possible.length < 16, "too many bits"); @@ -227,9 +224,7 @@ export async function main() { .join(";"), ); } else { - possible = order.map(({ id }) => - UNWRAP(possible.find((p) => p.id === id)) - ); + possible = order.map(({ id }) => UNWRAP(possible.find((p) => p.id === id))); } // Queue needed processors. @@ -294,14 +289,11 @@ export async function main() { const children = dir.getChildren(); // readme.txt - const readmeContent = children.find((x) => - x.basename === "readme.txt" - )?.contents ?? ""; + const readmeContent = children.find((x) => x.basename === "readme.txt")?.contents ?? ""; // dirsort let dirsort: string[] | null = null; - const dirSortRaw = - children.find((x) => x.basename === ".dirsort")?.contents ?? ""; + const dirSortRaw = children.find((x) => x.basename === ".dirsort")?.contents ?? ""; if (dirSortRaw) { dirsort = dirSortRaw .split("\n") @@ -417,9 +409,9 @@ export async function main() { } else console.warn("Missing SOT token"); console.info( - "Updated file viewer index in \x1b[1m" + - ((performance.now() - start) / 1000).toFixed(1) + - "s\x1b[0m", + "Updated file viewer index in \x1b[1m" + + ((performance.now() - start) / 1000).toFixed(1) + + "s\x1b[0m", ); const { duration, count } = MediaFile.db @@ -442,12 +434,12 @@ export async function main() { console.info(); console.info( - "Global Stats:\n" + - `- File Count: \x1b[1m${count}\x1b[0m\n` + - `- Derived Count: \x1b[1m${derivedCount}\x1b[0m\n` + - `- Media Duration: \x1b[1m${formatDurationLong(duration)}\x1b[0m\n` + - `- Canonical Size: \x1b[1m${formatSize(canonicalSize)}\x1b[0m\n` + - `- Derived Size: \x1b[1m${formatSize(derivedSize)}\x1b[0m\n`, + "Global Stats:\n" + + `- File Count: \x1b[1m${count}\x1b[0m\n` + + `- Derived Count: \x1b[1m${derivedCount}\x1b[0m\n` + + `- Media Duration: \x1b[1m${formatDurationLong(duration)}\x1b[0m\n` + + `- Canonical Size: \x1b[1m${formatSize(canonicalSize)}\x1b[0m\n` + + `- Derived Size: \x1b[1m${formatSize(derivedSize)}\x1b[0m\n`, ); } @@ -837,8 +829,8 @@ const processors = [ crypto .createHash("sha1") .update( - process.run.toString() + - (process.version ? String(process.version) : ""), + process.run.toString() + + (process.version ? String(process.version) : ""), ) .digest().buffer, ).reduce((a, b) => a ^ b), @@ -888,12 +880,12 @@ function skipBasename(basename: string): boolean { if (basename === ".date") return false; return ( - basename.startsWith(".") || + basename.startsWith(".") // basename.startsWith("._") || // basename.startsWith(".tmp") || // basename === ".DS_Store" || - basename.toLowerCase() === "thumbs.db" || - basename.toLowerCase() === "desktop.ini" + || basename.toLowerCase() === "thumbs.db" + || basename.toLowerCase() === "desktop.ini" ); } @@ -946,8 +938,8 @@ async function scrubLocationMetadata( "-s", path.toString(), ]); - hasLocation = videoCheck.includes("GPS") || - videoCheck.includes("Location"); + hasLocation = videoCheck.includes("GPS") + || videoCheck.includes("Location"); args = [ "-gps:all=", "-xmp:all=", @@ -963,10 +955,10 @@ async function scrubLocationMetadata( "-s", path.toString(), ]); - hasLocation = m4aCheck.includes("GPS") || - m4aCheck.includes("Location") || - m4aCheck.includes("Filename") || - m4aCheck.includes("Title"); + hasLocation = m4aCheck.includes("GPS") + || m4aCheck.includes("Location") + || m4aCheck.includes("Filename") + || m4aCheck.includes("Title"); if (hasLocation) { args = [ @@ -1028,13 +1020,13 @@ async function scrubLocationMetadata( const monthMilliseconds = 30 * 24 * 60 * 60 * 1000; -import * as async from "@clo/lib/async"; import * as fs from "#sitegen/fs"; -import * as subprocess from "@clo/lib/subprocess"; import { Path } from "#sitegen/path"; -import * as queue from "@clo/lib/queue"; +import * as async from "@clo/lib/async"; import * as log from "@clo/lib/log"; import * as progress from "@clo/lib/progress"; +import * as queue from "@clo/lib/queue"; +import * as subprocess from "@clo/lib/subprocess"; import * as ts from "@clo/lib/ts"; import * as child_process from "node:child_process"; @@ -1044,19 +1036,15 @@ import * as path from "node:path"; import * as stream from "node:stream"; import * as zlib from "node:zlib"; -import { MediaFile, MediaFileKind } from "#src/file-viewer/models/MediaFile.ts"; -import { FilePermissions } from "#src/file-viewer/models/FilePermissions.ts"; -import { - formatDate, - formatDurationLong, - formatSize, -} from "#src/file-viewer/format.ts"; -import * as rules from "#src/file-viewer/rules.ts"; +import { formatDate, formatDurationLong, formatSize } from "#src/file-viewer/format.ts"; import * as highlight from "#src/file-viewer/highlight.ts"; -import * as ffmpeg from "@clo/lib/subprocess/ffmpeg"; -import * as rsync from "#src/file-viewer/rsync.ts"; import * as derived from "#src/file-viewer/models/derived.ts"; +import { FilePermissions } from "#src/file-viewer/models/FilePermissions.ts"; +import { MediaFile, MediaFileKind } from "#src/file-viewer/models/MediaFile.ts"; +import * as rsync from "#src/file-viewer/rsync.ts"; +import * as rules from "#src/file-viewer/rules.ts"; import * as transcodeRules from "#src/file-viewer/transcode-rules.ts"; +import * as ffmpeg from "@clo/lib/subprocess/ffmpeg"; -import { rawFileRoot as root } from "../paths.ts"; import { ASSERT, UNWRAP } from "@clo/lib/assert"; +import { rawFileRoot as root } from "../paths.ts"; diff --git a/src/file-viewer/bin/file-trim.ts b/src/file-viewer/bin/file-trim.ts index a7780d8195b90c0e4b1ffac7b016a955ccfe1e90..7f11f3378120f254a0f22ab59182ff10de2b33c3 100644 --- a/src/file-viewer/bin/file-trim.ts +++ b/src/file-viewer/bin/file-trim.ts @@ -1,8 +1,7 @@ export async function main() { const start = performance.now(); using _ = log.startWidget({ - format: (now) => - `paper clover's file scanner [${((now - start) / 1000).toFixed(1)}s]`, + format: (now) => `paper clover's file scanner [${((now - start) / 1000).toFixed(1)}s]`, }); const orphaned = derived.findOrphanedRoots(); diff --git a/src/file-viewer/transcode-rules.test.ts b/src/file-viewer/transcode-rules.test.ts index 2e49bafa6581c4941c9e9aee3bc1a55e6046dbb6..81c16d9c04df000f61162eabc8575fe94cd483c3 100644 --- a/src/file-viewer/transcode-rules.test.ts +++ b/src/file-viewer/transcode-rules.test.ts @@ -226,15 +226,10 @@ test("dense sweep never produces mismatched ladder aspect ratios", () => { } }); -import { test } from "vitest"; -import assert from "node:assert"; import { Path } from "#sitegen/path"; -import { - getAv1VideoArgs, - getConstantAspectScaleFilter, - getMpegDashArgs, - videoFormats, -} from "./transcode-rules.ts"; +import assert from "node:assert"; +import { test } from "vitest"; +import { getAv1VideoArgs, getConstantAspectScaleFilter, getMpegDashArgs, videoFormats } from "./transcode-rules.ts"; function assertString(value: string | undefined | null): string { assert.equal(typeof value, "string"); @@ -275,9 +270,7 @@ const EXACT_ERROR_THRESHOLD = 0.0005; function getFeasibleCandidates(source: { width: number; height: number }) { const caps = [ source.height, - ...videoFormats.flatMap((preset) => - preset.maxHeight != null ? [Math.min(preset.maxHeight, source.height)] : [] - ), + ...videoFormats.flatMap((preset) => preset.maxHeight != null ? [Math.min(preset.maxHeight, source.height)] : []), ]; const sourceRatio = source.width / source.height; const maxDenominator = Math.max(1, Math.floor(Math.min(...caps) / 2)); @@ -300,16 +293,14 @@ function getFeasibleCandidates(source: { width: number; height: number }) { if (seen.has(key)) continue; seen.add(key); - const dims = videoFormats.map((preset) => - getExpectedDimensionsForPreset(source, candidate, preset.maxHeight) - ); + const dims = videoFormats.map((preset) => getExpectedDimensionsForPreset(source, candidate, preset.maxHeight)); if (dims.some((value) => value.width <= 0 || value.height <= 0)) continue; const retainedArea = dims .map((value) => value.width * value.height) .reduce((sum, value) => sum + value, 0); - const error = Math.abs((candidate.width / candidate.height) - sourceRatio) / - sourceRatio; + const error = Math.abs((candidate.width / candidate.height) - sourceRatio) + / sourceRatio; candidates.push({ ...candidate, error, retainedArea }); } @@ -337,9 +328,7 @@ function getCandidateScore( source: { width: number; height: number }, ratio: { width: number; height: number }, ) { - const dims = videoFormats.map((preset) => - getExpectedDimensionsForPreset(source, ratio, preset.maxHeight) - ); + const dims = videoFormats.map((preset) => getExpectedDimensionsForPreset(source, ratio, preset.maxHeight)); if (dims.some((value) => value.width <= 0 || value.height <= 0)) return null; const sourceRatio = source.width / source.height; return { diff --git a/src/file-viewer/transcode-rules.ts b/src/file-viewer/transcode-rules.ts index b9a2be4e9e61cb25f22f63579f863f267cf84150..04353989d917128894c53b2a45cc63f611ba7611 100644 --- a/src/file-viewer/transcode-rules.ts +++ b/src/file-viewer/transcode-rules.ts @@ -202,9 +202,7 @@ function getCanonicalSquarePixelAspectRatio(source: Dimensions): Dimensions { if (seen.has(key)) continue; seen.add(key); - const dimensions = caps.map((cap) => - getExactSquarePixelDimensions(source, aspect, cap) - ); + const dimensions = caps.map((cap) => getExactSquarePixelDimensions(source, aspect, cap)); if (dimensions.some((value) => value == null)) continue; const retainedArea = dimensions @@ -215,11 +213,11 @@ function getCanonicalSquarePixelAspectRatio(source: Dimensions): Dimensions { if (error <= exactErrorThreshold) { if ( - !bestWithinThreshold || - retainedArea > bestWithinThreshold.retainedArea || - ( - retainedArea === bestWithinThreshold.retainedArea && - error < bestWithinThreshold.error + !bestWithinThreshold + || retainedArea > bestWithinThreshold.retainedArea + || ( + retainedArea === bestWithinThreshold.retainedArea + && error < bestWithinThreshold.error ) ) { bestWithinThreshold = candidate; @@ -228,9 +226,9 @@ function getCanonicalSquarePixelAspectRatio(source: Dimensions): Dimensions { } if ( - !bestFallback || - error < bestFallback.error || - (error === bestFallback.error && retainedArea > bestFallback.retainedArea) + !bestFallback + || error < bestFallback.error + || (error === bestFallback.error && retainedArea > bestFallback.retainedArea) ) { bestFallback = candidate; } @@ -247,8 +245,8 @@ export function getConstantAspectScaleFilter( const dimensions = getExactSquarePixelDimensions(source, aspect, maxHeight); if (!dimensions) return null; if ( - dimensions.width === source.width && - dimensions.height === source.height + dimensions.width === source.width + && dimensions.height === source.height ) return null; return `scale=${dimensions.width}:${dimensions.height}`; } @@ -336,9 +334,7 @@ export function getMpegDashArgs( if (audios.length > 0) { const audioId = videos.length > 0 ? 1 : 0; const audioIndices = audios.map((_, i) => videos.length + i).join(","); - adaptationSets += `${ - adaptationSets ? " " : "" - }id=${audioId},streams=${audioIndices}`; + adaptationSets += `${adaptationSets ? " " : ""}id=${audioId},streams=${audioIndices}`; } cmd.push("-adaptation_sets", adaptationSets); @@ -378,11 +374,16 @@ export function getH264HlsArgs( const aIdx = i * 2 + 1; // deno-fmt-ignore cmd.push( - `-c:${vIdx}`, "libx264", - `-b:v:${vIdx}`, preset.videoBitrate, - `-filter:${vIdx}`, `scale=-2:min(${preset.maxHeight}\\,ih)`, - `-c:${aIdx}`, "aac", - `-b:a:${aIdx}`, preset.audioBitrate, + `-c:${vIdx}`, + "libx264", + `-b:v:${vIdx}`, + preset.videoBitrate, + `-filter:${vIdx}`, + `scale=-2:min(${preset.maxHeight}\\,ih)`, + `-c:${aIdx}`, + "aac", + `-b:a:${aIdx}`, + preset.audioBitrate, ); }); @@ -399,9 +400,12 @@ export function getH264HlsArgs( hlsPresets.forEach((preset, i) => { // deno-fmt-ignore cmd.push( - `-c:v:${i}`, "libx264", - `-b:v:${i}`, preset.videoBitrate, - `-filter:v:${i}`, `scale=-2:min(${preset.maxHeight}\\,ih)`, + `-c:v:${i}`, + "libx264", + `-b:v:${i}`, + preset.videoBitrate, + `-filter:v:${i}`, + `scale=-2:min(${preset.maxHeight}\\,ih)`, ); }); @@ -413,13 +417,20 @@ export function getH264HlsArgs( // deno-fmt-ignore cmd.push( - "-f", "hls", - "-hls_time", "2", - "-hls_list_size", "0", - "-hls_segment_type", "fmp4", - "-hls_allow_cache", "1", - "-master_pl_name", "master.m3u8", - "-hls_segment_filename", "hls.%v.%03d.ts", + "-f", + "hls", + "-hls_time", + "2", + "-hls_list_size", + "0", + "-hls_segment_type", + "fmp4", + "-hls_allow_cache", + "1", + "-master_pl_name", + "master.m3u8", + "-hls_segment_filename", + "hls.%v.%03d.ts", "-y", outDir.join("hls.%v.m3u8").toString(), ); diff --git a/src/file-viewer/views/canvas.marko b/src/file-viewer/views/canvas.marko index 323f4f6e75f5f15c957dc4f8f452cc9e3793ec29..5830acd60974cdfe01d07656efc852ef570eb126 100644 --- a/src/file-viewer/views/canvas.marko +++ b/src/file-viewer/views/canvas.marko @@ -3,15 +3,16 @@ export interface Input { } export const meta = { title: "canvas" }; - + - - +/> + client import "./canvas.client.ts"; -import { addScript as AddScript } from '#sitegen'; +import { addScript as AddScript } from "#sitegen"; diff --git a/src/file-viewer/views/lofi.marko b/src/file-viewer/views/lofi.marko index 7e33443fc8f19acfae6c013028eecdcf35af2755..64c17e06b87c3082b59d6d8dcc09f7dd57932909 100644 --- a/src/file-viewer/views/lofi.marko +++ b/src/file-viewer/views/lofi.marko @@ -5,66 +5,87 @@ export interface Input { } export { meta, theme } from "./clofi.tsx"; - - - + + + + + + 0 + ? formatDuration(file.duration!) + : null + )> +
  • + + ${formatDate(file.date)}${" "} + ${file.basenameWithoutExt} + ${file.extension} + / +    (${meta}) + +
  • +
    - - - 0 - ? formatDuration(file.duration!) - : null - )/> -
  • - - ${formatDate(file.date)}${" "}${file.basenameWithoutExt}${file.extension}/ -    (${meta}) - -
  • -
    +

    + clo's files + ${fullPath} +

    -

    - clo's files - ${fullPath} -

    - - - -
    - -

    ${key} + + + +
      + +
    + + +

    ${key}

    +
      + + + +
    + + // + //





    + //

    + // would you like to + // dive deeper? + //

    + // + + - - // - //





    - //

    - // would you like to - // dive deeper? - //

    - // - - - - + import * as path from "node:path"; -import { escapeUri, formatDuration, formatSize, formatDate } from "#src/file-viewer/format.ts"; +import { + escapeUri, + formatDuration, + formatSize, + formatDate, +} from "#src/file-viewer/format.ts"; import { MediaFileKind } from "#src/file-viewer/models/MediaFile.ts"; import * as sort from "#src/file-viewer/sort.ts"; diff --git a/src/file-viewer/views/lofideon.marko b/src/file-viewer/views/lofideon.marko index 92fbfe95c210c022601242fa9c493dac36583b81..dbaac8981d73acb064fe24e257730345e8fae3c5 100644 --- a/src/file-viewer/views/lofideon.marko +++ b/src/file-viewer/views/lofideon.marko @@ -1,22 +1,26 @@ export interface Input { - stage: number + stage: number; } -export const meta = { title: 'C O T Y L E D O N' }; +export const meta = { title: "C O T Y L E D O N" }; export const theme = { - bg: '#ff00ff', - fg: '#000000', + bg: "#ff00ff", + fg: "#000000", };

    co
    ty
    le
    don

    - - -

    -this place is sacred, but dangerous. i have to keep visitors to an absolute minimum; you'll get dust on all the artifacts. -

    -by entering our museum, you agree not to use your camera. flash off isn't enough; the bits and bytes are alergic even to a camera's sensor -

    -(in english: please do not store downloads after you're done viewing them) -

    - - + +

    + this place is sacred, but dangerous. i have to keep visitors to an absolute + minimum; you'll get dust on all the artifacts. +

    +

    + by entering our museum, you agree not to use your camera. flash off isn't + enough; the bits and bytes are alergic even to a camera's sensor +

    +

    + + (in english: please do not store downloads after you're done viewing them) + +

    + diff --git a/src/pages/admin/login.fail.marko b/src/pages/admin/login.fail.marko index 0a7315c0e822a9ec4f634a13fb6a9df4f2d5a84e..02232c1f7e5374110ef228f188bcc1e136536cc0 100644 --- a/src/pages/admin/login.fail.marko +++ b/src/pages/admin/login.fail.marko @@ -1,4 +1,4 @@ -export const meta = { title: 'paper clover admin' }; +export const meta = { title: "paper clover admin" };

    login

    diff --git a/src/pages/admin/login.marko b/src/pages/admin/login.marko index 2ebcc7646f0f21f66907e6f1f52a3228634a1971..f425f0512a20be6c9b85007864fc83e81c959755 100644 --- a/src/pages/admin/login.marko +++ b/src/pages/admin/login.marko @@ -1,4 +1,4 @@ -export const meta = { title: 'paper clover admin' }; +export const meta = { title: "paper clover admin" };

    login

    diff --git a/src/pages/friends/auth.fail.marko b/src/pages/friends/auth.fail.marko index 48269abe7f87b1482a3ba8a6b32ae46ac96ead8d..41f58ea14ee59649a40df9ca88dfe1c51b4be764 100644 --- a/src/pages/friends/auth.fail.marko +++ b/src/pages/friends/auth.fail.marko @@ -5,7 +5,7 @@ export const meta = { title: "password required" };

    please contact clover

    try again

    - +
    diff --git a/src/pages/friends/auth.marko b/src/pages/friends/auth.marko index ccfe2d0cb1cd9c7fcc431d68088d078e29722ae9..4c464434c8b938e77bcf611dafc94064481f0569 100644 --- a/src/pages/friends/auth.marko +++ b/src/pages/friends/auth.marko @@ -3,7 +3,7 @@ export const meta = { title: "password required" };

    what's the password

    - +

    (clover will give the password to you if you're a friend)

    diff --git a/src/pages/friends/misconfigure.marko b/src/pages/friends/misconfigure.marko index c7e09fca74a6a9f9502f8cebff50b789380daa2a..7fdf2edd978c134305cae6582d6c12015f6192f3 100644 --- a/src/pages/friends/misconfigure.marko +++ b/src/pages/friends/misconfigure.marko @@ -3,7 +3,7 @@ export const meta = { title: "password required" };

    what's the password

    - +

    this page's password is not configured

    diff --git a/src/pages/history-of-japan-reanimated.marko b/src/pages/history-of-japan-reanimated.marko index 97a913066e6917ca6c73e19b45af6d73485910e7..283ca9b0fbf56e3c02b8519c198884325d05364c 100644 --- a/src/pages/history-of-japan-reanimated.marko +++ b/src/pages/history-of-japan-reanimated.marko @@ -4,97 +4,186 @@ client import "./history-of-japan-reanimated.client.ts"; export const meta = { title: "history of japan reanimated", - description: "17 artists and musicians come together to remake bill wurtz's legendary \"history of japan\" to celebrate its 10 year anniversary.", + description: + '17 artists and musicians come together to remake bill wurtz\'s legendary "history of japan" to celebrate its 10 year anniversary.', embed: { - thumbnail: "https://paperclover.net/file/2026/history%20of%20japan%20reanimated/thumbnail.jpeg" + thumbnail: + "https://paperclover.net/file/2026/history%20of%20japan%20reanimated/thumbnail.jpeg", }, canonical: "/history-of-japan-reanimated", twitter: { - image: "https://paperclover.net/file/2026/history%20of%20japan%20reanimated/thumbnail.jpeg" + image: + "https://paperclover.net/file/2026/history%20of%20japan%20reanimated/thumbnail.jpeg", }, }; export const theme = { fg: "#fff", bg: "#442233", - primary: "#ff8b47" + primary: "#ff8b47", };

    history of japan reanimated

    - me and 16 other artists (music, narration, and animation) have been - spending the past couple months on a recreation of bill wurtz's legendary - "history of japan" to celebrate the 10 year anniversary of his release. i - contributed two animation sections. the canonical release is on - LunaPogi's YouTube page, but i - am providing this ad-free video player. + me and 16 other artists (music, narration, and animation) have been spending + the past couple months on a recreation of bill wurtz's legendary " + history of japan + " to celebrate the 10 year anniversary of his release. i contributed two + animation sections. the canonical release is on + LunaPogi's YouTube page, but i am providing + this ad-free video player.

    - - <@header>history of japan reanimated - + + <@header>history of japan reanimated + -
    +
    - -
    - -

    - the wonderful souls who worked on the project, in order of appearance. -

    +

    the wonderful souls who worked on the project, in order of appearance.

    - the music during the credits is an instrumental by bill wurtz: hey mom i beat the scale! + the music during the credits is an instrumental by bill wurtz: + + hey mom i beat the scale! +

    what's next

    from the success of this project, a much more ambitous project is being - planned: the reanimation for 'history of the entire world, i guess'. i am joining the project as one of the co-hosts. we are currently doing initial planning, - all artists (music, visual, narration) interested should fill out the interest form. by submitting the form, i'll be able to reach out when it's actually time to start cooking. you can also join the project's public discord. + planned: the reanimation for 'history of the entire world, i guess'. i am + joining the project as one of the co-hosts. we are currently doing initial + planning, all artists (music, visual, narration) interested should fill out + the interest form. by + submitting the form, i'll be able to reach out when it's actually time to start + cooking. you can also join the project's + public discord + .

    behind the scenes

    - here is a 55 minute dive into my process, as well as nearly every little trick i used on this project. additionally, for one of the first times from me, a free download for all of the project files. + here is a 55 minute dive into my process, as well as nearly every little + trick i used on this project. additionally, for one of the first times from + me, a free download for + + all of the project files + .

    - - <@header>behind the scenes session with clover - - + + <@header>behind the scenes session with clover +

    more links

    - paper clover q&a page
    + paper clover q&a page
    paper clover's mailing list

    mentions on the q&a

    - +
    diff --git a/src/pages/in-the-summer.marko b/src/pages/in-the-summer.marko index d9af19e337e0123681faf57fba1ebaffbe669687..30c143ca5f0d55e5181ef491dbb8312b07328736 100644 --- a/src/pages/in-the-summer.marko +++ b/src/pages/in-the-summer.marko @@ -1,43 +1,55 @@ -export const meta = { title: 'in the summer' }; +export const meta = { title: "in the summer" }; export const theme = { - bg: '#7DCC8C', - fg: '#000', - primary: '#9b3706', -} + bg: "#7DCC8C", + fg: "#000", + primary: "#9b3706", +}; import "./in-the-summer.css"; -
    - - -
    -

    in the summer

    - -
    +
    + + +
    +

    in the summer

    + +
    -

    +

    this is a song about the summer, and related feelings. written in 2022, produced in 2025. i had a lot of fun playing with a much larger instrument selection, and new 3d workflows. enjoy! -

    - +

    + - - <@header>music video: in the summer - + + <@header>music video: in the summer + - -

    downloads

    - -

    mentions on the q&a

    - - + +

    downloads

    + +

    mentions on the q&a

    + +
    - diff --git a/src/pages/index.marko b/src/pages/index.marko index e4d226671de0cbfe6bbdf83baae8da4005166a24..448626bfdf031548f45d473503bf822256cbcc62 100644 --- a/src/pages/index.marko +++ b/src/pages/index.marko @@ -4,8 +4,11 @@ import "./index.css"; export const theme = { bg: "#fff", fg: "#666" }; export const meta = { title: "paper clover", - description: "and then we knew, just like paper airplanes: that we could fly...", - embed: { /* Default embed */ }, + description: + "and then we knew, just like paper airplanes: that we could fly...", + embed: { + /* Default embed */ + }, alternates: { "application/rss+xml": "rss.xml", }, @@ -15,15 +18,23 @@ export const meta = {

    about

    i am a girl that is adorable and - alive. -

    -

    - this site contains my art. + alive.

    +

    this site contains my art.

    posts

    diff --git a/src/pages/resume.marko b/src/pages/resume.marko index 53e16547875558ccd103b7152790f51bff85db3e..554665003d17f8ef9353eb3f63dedb87d3838067 100644 --- a/src/pages/resume.marko +++ b/src/pages/resume.marko @@ -3,48 +3,47 @@ import "./resume.css"; export const meta = { title: "clover's resume" };
    -

    clover's resume

    -
    last updated: 2025 +

    clover's resume

    +
    last updated: 2025
    - -
    -

    web/backend engineer

    - 2025-now - -
      - (more details added as time goes on...) -
    - + +
    +

    web/backend engineer

    + 2025-now +
    +
      (more details added as time goes on...)
    + - -
    -

    runtime/systems engineer

    - 2023-2025 -

    developer tools company

    - -
      -
    • hardcore engineering, elegant solutions -
    • platform compatibility & stability -
    • debugging and profiling across platforms -
    - + +
    +

    runtime/systems engineer

    + 2023-2025 +

    developer tools company

    +
    +
      +
    • hardcore engineering, elegant solutions
    • +
    • platform compatibility & stability
    • +
    • debugging and profiling across platforms
    • +
    + - -
    -

    technician

    - 2023; part time -

    automotive maintainance company

    - -
      -
    • pressed buttons on a computer -
    - + +
    +

    technician

    + 2023; part time +

    automotive maintainance company

    +
    +
      +
    • pressed buttons on a computer
    • +
    + -
    -

    eduation

    2004-now -

    - my life on earth has taught me more than i expected. i
    - continue to learn new things daily, as if it was magic. -

    -
    +
    +

    eduation

    + ${" "}2004-now +

    + my life on earth has taught me more than i expected. i
    continue to + learn new things daily, as if it was magic. +

    +
    diff --git a/src/pages/subscribe.marko b/src/pages/subscribe.marko index a7bc9dcc571cd0549d8f100bad08fed601160e83..56c99777ed12e756144f53d82461c50a6295b9f3 100644 --- a/src/pages/subscribe.marko +++ b/src/pages/subscribe.marko @@ -1,31 +1,29 @@ -import './subscribe.css'; +import "./subscribe.css"; export const meta = { title: "paper clover mailing list", description: "subscribe to the mailing list", };
    -

    mailing list

    - -

    - the mailing list is used for big project updates. this is about once every - couple of months. to get added, please email anything with the word - "subscribe" in the subject or body to the following address: -

    - - subscribe@paperclover.net - -

    - i read every message, so you can say whatever else you want and i will see - it. there won't be a confirmation email for subscribing. for those who are - close friends with me, add your name and i'll add you to the 'friends' list, - where i send life updates in addition to new content. -

    -

    to unsubscribe, send an email asking to unsubscribe.

    -
    -
    -
    -

    - back to home -

    +

    mailing list

    + +

    + the mailing list is used for big project updates. this is about once every + couple of months. to get added, please email anything with the word + "subscribe" in the subject or body to the following address: +

    + + + subscribe@paperclover.net + + +

    + i read every message, so you can say whatever else you want and i will see + it. there won't be a confirmation email for subscribing. for those who are + close friends with me, add your name and i'll add you to the 'friends' list, + where i send life updates in addition to new content. +

    +

    to unsubscribe, send an email asking to unsubscribe.

    +


    +

    back to home

    diff --git a/src/pages/waterfalls.marko b/src/pages/waterfalls.marko index b5a7c05c0cb52cdaaaf3288f2faa79e5128afe9c..a1279dcc5be92e5f09ed2a05f77f4466d368adde 100644 --- a/src/pages/waterfalls.marko +++ b/src/pages/waterfalls.marko @@ -7,7 +7,7 @@ export const theme = { };
    -
    +
    @@ -22,30 +22,22 @@ export const theme = { file="/2025/waterfalls/waterfalls.mp4" poster="/2025/waterfalls/thumbnail.jpeg" > - <@header>music video: waterfalls - + <@header>music video: waterfalls +
    -

    - it's nice to be free. -

    +

    it's nice to be free.

    like paper airplanes in the wind.

    the stars shine in the sky. it's beautiful.

    +

    love
    –clover caruso

    +

    - love -
    - –clover caruso -

    -
    - -

    - more:
    - fragments -
    - q&a page + more:
    + fragments +
    q&a page

    mentions on the q&a

    - +
    diff --git a/src/q+a/pages/admin.q+a.marko b/src/q+a/pages/admin.q+a.marko index 846d2a2c8d0fad7991baffa034daf315bed85adc..a10907d84ea8778d90be1b37636a8198d374b534 100644 --- a/src/q+a/pages/admin.q+a.marko +++ b/src/q+a/pages/admin.q+a.marko @@ -7,20 +7,19 @@ export const meta: Metadata = { description: "ask clover a question", }; export const regenerate = { - tags: ["q+a", "q+a inbox"] + tags: ["q+a", "q+a inbox"], }; - + 0)>

    - notice: ${inboxSize} items in - inbox! + notice: ${inboxSize} items in inbox!

    - + - + import MainPage from "./q+a.marko"; import { PendingQuestion } from "#src/q+a/models/PendingQuestion.ts"; diff --git a/src/q+a/pages/q+a.404.marko b/src/q+a/pages/q+a.404.marko index bf2138053ab5e4b3e34269440262f062884e57a8..3bd405110a745fa6a196656f48f01881d52de20a 100644 --- a/src/q+a/pages/q+a.404.marko +++ b/src/q+a/pages/q+a.404.marko @@ -4,9 +4,7 @@ export const meta: Metadata = { }; -

    sound the alarms

    -

    this page doesn't exist

    -

    - return to the questions list -

    +

    sound the alarms

    +

    this page doesn't exist

    +

    return to the questions list

    diff --git a/src/q+a/pages/q+a.marko b/src/q+a/pages/q+a.marko index 592a017b918bd060e1e4ee77505dd491f5f75d74..72845fab25ff0cc2b14947f55a028af0943efa45 100644 --- a/src/q+a/pages/q+a.marko +++ b/src/q+a/pages/q+a.marko @@ -9,19 +9,17 @@ export const meta: Metadata = { description: "ask clover a question", }; - - + + - - - - - - -
    - fun fact: clover has answered ${questions.length} questions -
    + + + + + + +
    fun fact: clover has answered ${questions.length} questions
    import { Question } from "#src/q+a/models/Question.ts"; diff --git a/src/q+a/tags/question-form.marko b/src/q+a/tags/question-form.marko index 6f9084772cb9a32ce34de2f0bf8ffa48157cc524..a455da0d24e2f86b213c5de36b336dc78d6ee7f7 100644 --- a/src/q+a/tags/question-form.marko +++ b/src/q+a/tags/question-form.marko @@ -10,4 +10,3 @@
    send
    - diff --git a/src/q+a/tags/question-layout.marko b/src/q+a/tags/question-layout.marko index 52b21c991171c82c9899d277d3a3a4a4cf5c8d3a..93945485dfe8c3e2b266d30ede8e927fea44fa54 100644 --- a/src/q+a/tags/question-layout.marko +++ b/src/q+a/tags/question-layout.marko @@ -8,8 +8,8 @@ client import "./header.client.ts"; & questions

    - - <${input.content} /> + + <${input.content}/> export const theme = { diff --git a/src/q+a/tags/question.marko b/src/q+a/tags/question.marko index f0f22c5b53bd78700e30d4499d873b0852f344bf..2e000f17f9f929abbb0aec6e644e833b4eb40dcb 100644 --- a/src/q+a/tags/question.marko +++ b/src/q+a/tags/question.marko @@ -10,34 +10,31 @@ export interface Input { // 2024-12-31 05:00:00 EST export const transitionDate = 1735639200000; - - + + <${"e-"} f=(date > transitionDate ? true : null) id=admin ? `q${id}` : undefined > - [EDIT] - + [EDIT] + -
    -
    - - $!{markdown.renderMarkdown(question.text)} + +
    + $!{markdown.renderMarkdown(question.text)} // this singleton script will make all the '