website

#astro#js#html#css

git clone https://git.pyrossh.dev/website

木 Personal website of pyrossh. Built with astrojs, shiki, vite.


ada6e53pyrossh 2026-08-04T21:22:32+05:30
feat: switch typography/spacing to a fluid (utopia.fyi-style) scale
.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  node_modules/
2
2
  dist/
3
+ .cache/
3
4
  .env
4
5
  .DS_Store
5
6
  .wrangler/
.mcp.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "elena": {
4
- "command": "npx",
5
- "args": ["@elenajs/mcp"]
6
- }
7
- }
8
- }
LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2024, pyrossh
1
+ Copyright (c) 2026, pyrossh
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
assets/css/markdown.css CHANGED
@@ -14,21 +14,21 @@
14
14
  line-height: 1.5;
15
15
 
16
16
  h1 {
17
- font-size: 1.5rem;
17
+ font-size: var(--fs-heading-md);
18
18
  font-weight: 700;
19
19
  margin-top: 0;
20
20
  margin-bottom: 0.5rem;
21
21
  }
22
22
 
23
23
  h2 {
24
- font-size: 1.5rem;
24
+ font-size: var(--fs-heading-md);
25
25
  font-weight: 700;
26
26
  margin-top: 0;
27
27
  margin-bottom: 0.5rem;
28
28
  }
29
29
 
30
30
  h3 {
31
- font-size: 1.25rem;
31
+ font-size: var(--fs-heading-sm);
32
32
  font-weight: 700;
33
33
  margin-top: 0.5rem;
34
34
  margin-bottom: 0.1rem;
assets/css/repos.css CHANGED
@@ -10,7 +10,7 @@
10
10
  0.9rem — code/monospace content, nav links and sub-headings alike
11
11
  (pre/code-view, file tree, blame, h3, file title)
12
12
  0.95rem — box body text (.repo, .event)
13
- 1.6rem — page-level heading (.repo h1)
13
+ var(--fs-repo-title) — page-level heading (.repo h1), fluid 1.3–1.6rem
14
14
  All of them are rem-based, not em, so nesting depth can't change the
15
15
  rendered size — that's what made the old code-view/blame font sizes
16
16
  inconsistent (em compounds with whatever ancestor happens to wrap it).
@@ -47,7 +47,7 @@
47
47
  }
48
48
 
49
49
  h1 {
50
- font-size: 1.6rem;
50
+ font-size: var(--fs-repo-title);
51
51
  font-weight: 500;
52
52
  line-height: 1;
53
53
  margin-bottom: 0.1rem;
assets/css/shared.css CHANGED
@@ -89,6 +89,24 @@ table { border-collapse: collapse; }
89
89
  color-scheme: light dark;
90
90
  --btn-light: none;
91
91
  --btn-dark: block;
92
+
93
+ /* Fluid type/space scale (utopia.fyi methodology): each token scales
94
+ continuously between its min size at a 320px viewport and its max
95
+ size at a 1280px viewport, replacing what used to be a hard jump at
96
+ a single max-width:720px breakpoint. Structural changes (flex
97
+ direction, grid column counts, sidebar widths) aren't continuous
98
+ properties and stay as ordinary breakpoints elsewhere. */
99
+ --fs-sm: clamp(1rem, calc(0.9667rem + 0.1667vw), 1.1rem);
100
+ --fs-body: clamp(1.1rem, calc(1.05rem + 0.25vw), 1.25rem);
101
+ --fs-md: clamp(1.15rem, calc(1.0667rem + 0.4167vw), 1.4rem);
102
+ --fs-title-lg: clamp(1.6rem, calc(1.5083rem + 0.4583vw), 1.875rem);
103
+ --fs-heading-md: clamp(1.2rem, calc(1.1rem + 0.5vw), 1.5rem);
104
+ --fs-heading-sm: clamp(1rem, calc(0.9167rem + 0.4167vw), 1.25rem);
105
+ --fs-repo-title: clamp(1.3rem, calc(1.2rem + 0.5vw), 1.6rem);
106
+ --fs-hero-xl: clamp(1.65rem, calc(1.5333rem + 0.5833vw), 2rem);
107
+ --fs-features: clamp(0.925rem, calc(0.8583rem + 0.3333vw), 1.125rem);
108
+ --space-gap-fluid: clamp(0rem, calc(-0.6667rem + 3.3333vw), 2rem);
109
+ --space-section-mb: clamp(0rem, calc(-0.3333rem + 1.6667vw), 1rem);
92
110
  }
93
111
 
94
112
  /* data-theme is tri-state: absent = follow OS (color-scheme: light dark
@@ -159,13 +177,9 @@ body {
159
177
  text-rendering: optimizeLegibility;
160
178
  font-variant-ligatures: common-ligatures;
161
179
  font-kerning: normal;
162
- font-size: 1.25rem;
180
+ font-size: var(--fs-body);
163
181
  line-height: 1.5;
164
182
  min-height: 100vh;
165
-
166
- @media (max-width: 720px) {
167
- font-size: 1.1rem;
168
- }
169
183
  }
170
184
 
171
185
  main {
@@ -269,12 +283,6 @@ header {
269
283
  }
270
284
  }
271
285
 
272
- /* Still a real (the only) Elena/custom element left on the site — it has
273
- no box of its own; the real box is the <button> it renders. */
274
- theme-switcher {
275
- display: contents;
276
- }
277
-
278
286
  footer {
279
287
  background: #131618;
280
288
  view-transition-name: site-footer;
assets/css/workers/cv.css CHANGED
@@ -8,7 +8,7 @@
8
8
  display: flex;
9
9
  align-items: flex-start;
10
10
  font-weight: 800;
11
- font-size: 2rem;
11
+ font-size: var(--fs-hero-xl);
12
12
  margin-bottom: 1rem;
13
13
 
14
14
  a {
assets/css/workers/home.css CHANGED
@@ -9,32 +9,19 @@
9
9
  margin-bottom: 2.5rem;
10
10
 
11
11
  .description {
12
- font-size: 1.1rem;
13
-
14
- @media (max-width: 720px) {
15
- font-size: 1rem;
12
+ font-size: var(--fs-sm);
16
- }
17
13
  }
18
14
 
19
15
  h2 {
20
- font-size: 1.4rem;
16
+ font-size: var(--fs-md);
21
17
  font-weight: 600;
22
-
23
- @media (max-width: 720px) {
24
- font-size: 1.15rem;
25
- }
26
18
  }
27
19
 
28
20
  section {
29
21
  display: flex;
30
22
  flex-direction: column;
31
23
  margin-top: 1.5rem;
32
- margin-bottom: 1rem;
24
+ margin-bottom: var(--space-section-mb);
33
-
34
- @media (max-width: 720px) {
35
- margin-top: 1.5rem;
36
- margin-bottom: 0rem;
37
- }
38
25
  }
39
26
 
40
27
  .rounded-md {
@@ -54,14 +41,10 @@
54
41
  }
55
42
 
56
43
  .title {
57
- font-size: 1.875rem;
44
+ font-size: var(--fs-title-lg);
58
45
  line-height: 2.25rem;
59
46
  font-weight: 700;
60
47
  margin-bottom: 1rem;
61
-
62
- @media (max-width: 720px) {
63
- font-size: 1.6rem;
64
- }
65
48
  }
66
49
 
67
50
  .firstSection {
@@ -81,15 +64,10 @@
81
64
  .gridContainer {
82
65
  display: grid;
83
66
  grid-template-columns: repeat(1, minmax(0, 1fr));
84
- gap: 1rem;
85
-
86
- @media (max-width: 720px) {
67
+ gap: var(--space-gap-fluid);
87
- gap: 0rem;
88
- }
89
68
 
90
69
  @media (min-width: 640px) {
91
70
  grid-template-columns: repeat(2, minmax(0, 1fr));
92
- gap: 2rem;
93
71
  }
94
72
  }
95
73
 
@@ -162,10 +140,9 @@
162
140
  align-items: baseline;
163
141
  padding: 0.25rem;
164
142
  background-color: var(--color-box-bg);
165
- font-size: 1.1rem;
143
+ font-size: var(--fs-sm);
166
144
 
167
145
  @media (max-width: 720px) {
168
- font-size: 1rem;
169
146
  flex-direction: column;
170
147
  }
171
148
  }
eleventy.config.js CHANGED
@@ -2,25 +2,16 @@ import { readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight";
4
4
  import pluginRss from "@11ty/eleventy-plugin-rss";
5
- import { register, ssr } from "@elenajs/ssr";
6
- import ThemeSwitcher from "./src/components/theme-switcher.js";
7
5
  import { fileIcon, folderIcon } from "./src/_helpers/fileIcons.js";
8
6
 
9
- // theme-switcher is the only Elena component left — everything else
7
+ // theme-switcher (src/components/theme-switcher.js) used to be a Web
10
- // (site-header, site-footer, repo-layout, file-layout, markdown-view,
11
- // code-view, the cv page) was SSR-only markup-with-props, which plain
12
- // Nunjucks includes/macros already do natively (see src/_includes/
13
- // site-header.njk, repo-layout.njk, file-layout.njk). Keeping those as
14
- // Elena components meant carrying the whole elena-ssr transform, the
15
- // patched fork (patches/@elenajs+ssr+*.patch), @scope workarounds for
8
+ // Component (via @elenajs/core + an @elenajs/ssr build-time transform
9
+ // that expanded it on every generated page). A stateless click-to-toggle
16
- // having no shadow root, and a custom <slot> patch for the one case
10
+ // button doesn't need a component framework for that: its markup is now
17
- // (the page layout) that needed to pass through arbitrary children —
18
- // none of which pulls its weight once htmx is already doing the actual
19
- // dynamic navigation. theme-switcher earns its keep because it's the one
20
- // piece that's genuinely isomorphic: the same source SSRs the initial
21
- // button and, loaded again client-side, drives the real click handler.
11
+ // static HTML baked directly into site-header.njk, and theme-switcher.js
12
+ // is a plain script (loaded with `defer` below) that just wires up the
22
- register(ThemeSwitcher);
13
+ // click handler by id once the DOM is parsed.
23
-
14
+ //
24
15
  // Every page used to pick and choose its own subset of these via `css`/
25
16
  // `extraCss` frontmatter (e.g. repo pages loading repos.css + markdown.css
26
17
  // + diff2html.min.css, the homepage loading just home.css). Now that
@@ -56,14 +47,10 @@ export default function (eleventyConfig) {
56
47
  "node_modules/diff2html/bundles/css/diff2html.min.css": "assets/css/diff2html.min.css",
57
48
  });
58
49
 
59
- // theme-switcher is the one component that ships to the browser (real
50
+ // theme-switcher is the one bit of client-side JS the site ships (real
60
- // click + localStorage behavior). It's plain portable ESM importing
51
+ // click + localStorage behavior) plain vanilla script, no bundler
61
- // "@elenajs/core" by bare specifier — Node resolves that via
62
- // node_modules for SSR; the browser resolves it via the import map
63
- // declared in layout.njk, pointed at Elena's prebuilt browser bundle.
64
- // No bundler needed for a single small component.
52
+ // needed.
65
53
  eleventyConfig.addPassthroughCopy({
66
- "node_modules/@elenajs/core/dist/bundle.js": "assets/vendor/elena-core.js",
67
54
  "src/components/theme-switcher.js": "assets/components/theme-switcher.js",
68
55
  });
69
56
 
@@ -98,13 +85,6 @@ export default function (eleventyConfig) {
98
85
  const value = bytes / 1024 ** i;
99
86
  return `${i === 0 ? value : value.toFixed(1)}${units[i]}`;
100
87
  });
101
- eleventyConfig.addTransform("elena-ssr", (content, outputPath) => {
102
- if (outputPath && outputPath.endsWith(".html")) {
103
- return ssr(content);
104
- }
105
- return content;
106
- });
107
-
108
88
  // Runs after passthrough copy has placed every file in CSS_FILES_TO_MERGE
109
89
  // under dist/, so it reads the already-copied output rather than the
110
90
  // assets/ source tree (that's also where diff2html.min.css lands, copied
package-lock.json CHANGED
@@ -7,8 +7,6 @@
7
7
  "name": "pyrossh-dev",
8
8
  "hasInstallScript": true,
9
9
  "dependencies": {
10
- "@elenajs/core": "^1.0.0",
11
- "@elenajs/ssr": "latest",
12
10
  "diff2html": "^3.4.52",
13
11
  "htmx.org": "^2.0.4",
14
12
  "markdown-it": "^14.1.0",
@@ -18,7 +16,6 @@
18
16
  "@11ty/eleventy": "^3.0.0",
19
17
  "@11ty/eleventy-plugin-rss": "^2.0.0",
20
18
  "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
21
- "@elenajs/mcp": "^1.0.0-alpha.11",
22
19
  "@iconify-json/vscode-icons": "^1.2.68",
23
20
  "patch-package": "^8.0.1"
24
21
  }
@@ -239,55 +236,6 @@
239
236
  "node": ">=18"
240
237
  }
241
238
  },
242
- "node_modules/@elenajs/core": {
243
- "version": "1.0.1",
244
- "resolved": "https://registry.npmjs.org/@elenajs/core/-/core-1.0.1.tgz",
245
- "integrity": "sha512-GJhqGUg+yQ8BPVnZ8hx3icgwfS8yNp7Vd2atfE8WjaQVdeXGpm+SSTV9yBEcMdNgPF3Z9WeXNxES0KcEi15mIA==",
246
- "license": "MIT"
247
- },
248
- "node_modules/@elenajs/mcp": {
249
- "version": "1.0.0-alpha.11",
250
- "resolved": "https://registry.npmjs.org/@elenajs/mcp/-/mcp-1.0.0-alpha.11.tgz",
251
- "integrity": "sha512-pX2cjooZcbGGEuRfrgGPw0IE7AYuI+YbWJgzxHbvpxGSFnxta9+ZrOBxSmiqgY9lWxOW3dYJSoPG4YzxLpZ3+w==",
252
- "dev": true,
253
- "license": "MIT",
254
- "dependencies": {
255
- "@modelcontextprotocol/sdk": "1.29.0",
256
- "zod": "4.3.6"
257
- },
258
- "bin": {
259
- "elena-mcp": "src/cli.js"
260
- },
261
- "engines": {
262
- "node": ">= 20",
263
- "yarn": "please-use-npm"
264
- }
265
- },
266
- "node_modules/@elenajs/ssr": {
267
- "version": "1.0.0-alpha.11",
268
- "resolved": "https://registry.npmjs.org/@elenajs/ssr/-/ssr-1.0.0-alpha.11.tgz",
269
- "integrity": "sha512-P2d9JrX085Jv2mgURVJc+mT8t4acBaf/sbzvNFkmOGPLZveNlKmX/uAq2xsDni0k/kWLR0vKjT9HWLzC6A1K3Q==",
270
- "license": "MIT",
271
- "dependencies": {
272
- "htmlparser2": "12.0.0"
273
- },
274
- "peerDependencies": {
275
- "@elenajs/core": ">=1.0.0-rc.1"
276
- }
277
- },
278
- "node_modules/@hono/node-server": {
279
- "version": "1.19.17",
280
- "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.17.tgz",
281
- "integrity": "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==",
282
- "dev": true,
283
- "license": "MIT",
284
- "engines": {
285
- "node": ">=18.14.1"
286
- },
287
- "peerDependencies": {
288
- "hono": "^4"
289
- }
290
- },
291
239
  "node_modules/@iconify-json/vscode-icons": {
292
240
  "version": "1.2.68",
293
241
  "resolved": "https://registry.npmjs.org/@iconify-json/vscode-icons/-/vscode-icons-1.2.68.tgz",
@@ -305,47 +253,6 @@
305
253
  "dev": true,
306
254
  "license": "MIT"
307
255
  },
308
- "node_modules/@modelcontextprotocol/sdk": {
309
- "version": "1.29.0",
310
- "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
311
- "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==",
312
- "dev": true,
313
- "license": "MIT",
314
- "dependencies": {
315
- "@hono/node-server": "^1.19.9",
316
- "ajv": "^8.17.1",
317
- "ajv-formats": "^3.0.1",
318
- "content-type": "^1.0.5",
319
- "cors": "^2.8.5",
320
- "cross-spawn": "^7.0.5",
321
- "eventsource": "^3.0.2",
322
- "eventsource-parser": "^3.0.0",
323
- "express": "^5.2.1",
324
- "express-rate-limit": "^8.2.1",
325
- "hono": "^4.11.4",
326
- "jose": "^6.1.3",
327
- "json-schema-typed": "^8.0.2",
328
- "pkce-challenge": "^5.0.0",
329
- "raw-body": "^3.0.0",
330
- "zod": "^3.25 || ^4.0",
331
- "zod-to-json-schema": "^3.25.1"
332
- },
333
- "engines": {
334
- "node": ">=18"
335
- },
336
- "peerDependencies": {
337
- "@cfworker/json-schema": "^4.1.1",
338
- "zod": "^3.25 || ^4.0"
339
- },
340
- "peerDependenciesMeta": {
341
- "@cfworker/json-schema": {
342
- "optional": true
343
- },
344
- "zod": {
345
- "optional": false
346
- }
347
- }
348
- },
349
256
  "node_modules/@profoundlogic/hogan": {
350
257
  "version": "3.0.4",
351
258
  "resolved": "https://registry.npmjs.org/@profoundlogic/hogan/-/hogan-3.0.4.tgz",
@@ -411,20 +318,6 @@
411
318
  "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
412
319
  "license": "ISC"
413
320
  },
414
- "node_modules/accepts": {
415
- "version": "2.0.0",
416
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
417
- "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
418
- "dev": true,
419
- "license": "MIT",
420
- "dependencies": {
421
- "mime-types": "^3.0.0",
422
- "negotiator": "^1.0.0"
423
- },
424
- "engines": {
425
- "node": ">= 0.6"
426
- }
427
- },
428
321
  "node_modules/acorn": {
429
322
  "version": "8.18.0",
430
323
  "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
@@ -451,41 +344,6 @@
451
344
  "node": ">=0.4.0"
452
345
  }
453
346
  },
454
- "node_modules/ajv": {
455
- "version": "8.20.0",
456
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
457
- "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
458
- "dev": true,
459
- "license": "MIT",
460
- "dependencies": {
461
- "fast-deep-equal": "^3.1.3",
462
- "fast-uri": "^3.0.1",
463
- "json-schema-traverse": "^1.0.0",
464
- "require-from-string": "^2.0.2"
465
- },
466
- "funding": {
467
- "type": "github",
468
- "url": "https://github.com/sponsors/epoberezkin"
469
- }
470
- },
471
- "node_modules/ajv-formats": {
472
- "version": "3.0.1",
473
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
474
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
475
- "dev": true,
476
- "license": "MIT",
477
- "dependencies": {
478
- "ajv": "^8.0.0"
479
- },
480
- "peerDependencies": {
481
- "ajv": "^8.0.0"
482
- },
483
- "peerDependenciesMeta": {
484
- "ajv": {
485
- "optional": true
486
- }
487
- }
488
- },
489
347
  "node_modules/ansi-styles": {
490
348
  "version": "4.3.0",
491
349
  "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -602,45 +460,6 @@
602
460
  "url": "https://github.com/sponsors/sindresorhus"
603
461
  }
604
462
  },
605
- "node_modules/body-parser": {
606
- "version": "2.3.0",
607
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
608
- "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
609
- "dev": true,
610
- "license": "MIT",
611
- "dependencies": {
612
- "bytes": "^3.1.2",
613
- "content-type": "^2.0.0",
614
- "debug": "^4.4.3",
615
- "http-errors": "^2.0.1",
616
- "iconv-lite": "^0.7.2",
617
- "on-finished": "^2.4.1",
618
- "qs": "^6.15.2",
619
- "raw-body": "^3.0.2",
620
- "type-is": "^2.1.0"
621
- },
622
- "engines": {
623
- "node": ">=18"
624
- },
625
- "funding": {
626
- "type": "opencollective",
627
- "url": "https://opencollective.com/express"
628
- }
629
- },
630
- "node_modules/body-parser/node_modules/content-type": {
631
- "version": "2.0.0",
632
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
633
- "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
634
- "dev": true,
635
- "license": "MIT",
636
- "engines": {
637
- "node": ">=18"
638
- },
639
- "funding": {
640
- "type": "opencollective",
641
- "url": "https://opencollective.com/express"
642
- }
643
- },
644
463
  "node_modules/brace-expansion": {
645
464
  "version": "1.1.18",
646
465
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
@@ -665,16 +484,6 @@
665
484
  "node": ">=8"
666
485
  }
667
486
  },
668
- "node_modules/bytes": {
669
- "version": "3.1.2",
670
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
671
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
672
- "dev": true,
673
- "license": "MIT",
674
- "engines": {
675
- "node": ">= 0.8"
676
- }
677
- },
678
487
  "node_modules/call-bind": {
679
488
  "version": "1.0.9",
680
489
  "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
@@ -820,58 +629,6 @@
820
629
  "dev": true,
821
630
  "license": "MIT"
822
631
  },
823
- "node_modules/content-disposition": {
824
- "version": "1.1.0",
825
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
826
- "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
827
- "dev": true,
828
- "license": "MIT",
829
- "engines": {
830
- "node": ">=18"
831
- },
832
- "funding": {
833
- "type": "opencollective",
834
- "url": "https://opencollective.com/express"
835
- }
836
- },
837
- "node_modules/content-type": {
838
- "version": "1.0.5",
839
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
840
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
841
- "dev": true,
842
- "license": "MIT",
843
- "engines": {
844
- "node": ">= 0.6"
845
- }
846
- },
847
- "node_modules/cookie-signature": {
848
- "version": "1.2.2",
849
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
850
- "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
851
- "dev": true,
852
- "license": "MIT",
853
- "engines": {
854
- "node": ">=6.6.0"
855
- }
856
- },
857
- "node_modules/cors": {
858
- "version": "2.8.6",
859
- "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
860
- "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
861
- "dev": true,
862
- "license": "MIT",
863
- "dependencies": {
864
- "object-assign": "^4",
865
- "vary": "^1"
866
- },
867
- "engines": {
868
- "node": ">= 0.10"
869
- },
870
- "funding": {
871
- "type": "opencollective",
872
- "url": "https://opencollective.com/express"
873
- }
874
- },
875
632
  "node_modules/cross-spawn": {
876
633
  "version": "7.0.6",
877
634
  "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -968,85 +725,6 @@
968
725
  "highlight.js": "11.11.1"
969
726
  }
970
727
  },
971
- "node_modules/dom-serializer": {
972
- "version": "3.1.1",
973
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.1.1.tgz",
974
- "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==",
975
- "license": "MIT",
976
- "dependencies": {
977
- "domelementtype": "^3.0.0",
978
- "domhandler": "^6.0.0",
979
- "entities": "^8.0.0"
980
- },
981
- "engines": {
982
- "node": ">=20.19.0"
983
- },
984
- "funding": {
985
- "type": "github",
986
- "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
987
- }
988
- },
989
- "node_modules/dom-serializer/node_modules/entities": {
990
- "version": "8.0.0",
991
- "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
992
- "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
993
- "license": "BSD-2-Clause",
994
- "engines": {
995
- "node": ">=20.19.0"
996
- },
997
- "funding": {
998
- "url": "https://github.com/fb55/entities?sponsor=1"
999
- }
1000
- },
1001
- "node_modules/domelementtype": {
1002
- "version": "3.0.0",
1003
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-3.0.0.tgz",
1004
- "integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==",
1005
- "funding": [
1006
- {
1007
- "type": "github",
1008
- "url": "https://github.com/sponsors/fb55"
1009
- }
1010
- ],
1011
- "license": "BSD-2-Clause",
1012
- "engines": {
1013
- "node": ">=20.19.0"
1014
- }
1015
- },
1016
- "node_modules/domhandler": {
1017
- "version": "6.0.1",
1018
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-6.0.1.tgz",
1019
- "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==",
1020
- "license": "BSD-2-Clause",
1021
- "dependencies": {
1022
- "domelementtype": "^3.0.0"
1023
- },
1024
- "engines": {
1025
- "node": ">=20.19.0"
1026
- },
1027
- "funding": {
1028
- "type": "github",
1029
- "url": "https://github.com/fb55/domhandler?sponsor=1"
1030
- }
1031
- },
1032
- "node_modules/domutils": {
1033
- "version": "4.0.2",
1034
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-4.0.2.tgz",
1035
- "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==",
1036
- "license": "BSD-2-Clause",
1037
- "dependencies": {
1038
- "dom-serializer": "^3.0.0",
1039
- "domelementtype": "^3.0.0",
1040
- "domhandler": "^6.0.0"
1041
- },
1042
- "engines": {
1043
- "node": ">=20.19.0"
1044
- },
1045
- "funding": {
1046
- "type": "github",
1047
- "url": "https://github.com/fb55/domutils?sponsor=1"
1048
- }
1049
- },
1050
728
  "node_modules/dunder-proto": {
1051
729
  "version": "1.0.1",
1052
730
  "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -1200,125 +878,6 @@
1200
878
  "node": ">= 8"
1201
879
  }
1202
880
  },
1203
- "node_modules/eventsource": {
1204
- "version": "3.0.7",
1205
- "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
1206
- "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
1207
- "dev": true,
1208
- "license": "MIT",
1209
- "dependencies": {
1210
- "eventsource-parser": "^3.0.1"
1211
- },
1212
- "engines": {
1213
- "node": ">=18.0.0"
1214
- }
1215
- },
1216
- "node_modules/eventsource-parser": {
1217
- "version": "3.1.0",
1218
- "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz",
1219
- "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==",
1220
- "dev": true,
1221
- "license": "MIT",
1222
- "engines": {
1223
- "node": ">=18.0.0"
1224
- }
1225
- },
1226
- "node_modules/express": {
1227
- "version": "5.2.1",
1228
- "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
1229
- "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
1230
- "dev": true,
1231
- "license": "MIT",
1232
- "dependencies": {
1233
- "accepts": "^2.0.0",
1234
- "body-parser": "^2.2.1",
1235
- "content-disposition": "^1.0.0",
1236
- "content-type": "^1.0.5",
1237
- "cookie": "^0.7.1",
1238
- "cookie-signature": "^1.2.1",
1239
- "debug": "^4.4.0",
1240
- "depd": "^2.0.0",
1241
- "encodeurl": "^2.0.0",
1242
- "escape-html": "^1.0.3",
1243
- "etag": "^1.8.1",
1244
- "finalhandler": "^2.1.0",
1245
- "fresh": "^2.0.0",
1246
- "http-errors": "^2.0.0",
1247
- "merge-descriptors": "^2.0.0",
1248
- "mime-types": "^3.0.0",
1249
- "on-finished": "^2.4.1",
1250
- "once": "^1.4.0",
1251
- "parseurl": "^1.3.3",
1252
- "proxy-addr": "^2.0.7",
1253
- "qs": "^6.14.0",
1254
- "range-parser": "^1.2.1",
1255
- "router": "^2.2.0",
1256
- "send": "^1.1.0",
1257
- "serve-static": "^2.2.0",
1258
- "statuses": "^2.0.1",
1259
- "type-is": "^2.0.1",
1260
- "vary": "^1.1.2"
1261
- },
1262
- "engines": {
1263
- "node": ">= 18"
1264
- },
1265
- "funding": {
1266
- "type": "opencollective",
1267
- "url": "https://opencollective.com/express"
1268
- }
1269
- },
1270
- "node_modules/express-rate-limit": {
1271
- "version": "8.6.1",
1272
- "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.1.tgz",
1273
- "integrity": "sha512-0D493aP61w0TJ2A0wy27riRsO7FMQ7FK+KUHOKCSfPvYo0R55aiC6emCVgFUeShH0fq0ICPVzNcgoS+BsbXQCA==",
1274
- "dev": true,
1275
- "license": "MIT",
1276
- "dependencies": {
1277
- "debug": "^4.4.3",
1278
- "ip-address": "^10.2.0"
1279
- },
1280
- "engines": {
1281
- "node": ">= 16"
1282
- },
1283
- "funding": {
1284
- "url": "https://github.com/sponsors/express-rate-limit"
1285
- },
1286
- "peerDependencies": {
1287
- "express": ">= 4.11"
1288
- }
1289
- },
1290
- "node_modules/express/node_modules/cookie": {
1291
- "version": "0.7.2",
1292
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
1293
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
1294
- "dev": true,
1295
- "license": "MIT",
1296
- "engines": {
1297
- "node": ">= 0.6"
1298
- }
1299
- },
1300
- "node_modules/express/node_modules/finalhandler": {
1301
- "version": "2.1.1",
1302
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
1303
- "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
1304
- "dev": true,
1305
- "license": "MIT",
1306
- "dependencies": {
1307
- "debug": "^4.4.0",
1308
- "encodeurl": "^2.0.0",
1309
- "escape-html": "^1.0.3",
1310
- "on-finished": "^2.4.1",
1311
- "parseurl": "^1.3.3",
1312
- "statuses": "^2.0.1"
1313
- },
1314
- "engines": {
1315
- "node": ">= 18.0.0"
1316
- },
1317
- "funding": {
1318
- "type": "opencollective",
1319
- "url": "https://opencollective.com/express"
1320
- }
1321
- },
1322
881
  "node_modules/extend-shallow": {
1323
882
  "version": "2.0.1",
1324
883
  "dev": true,
@@ -1330,30 +889,6 @@
1330
889
  "node": ">=0.10.0"
1331
890
  }
1332
891
  },
1333
- "node_modules/fast-deep-equal": {
1334
- "version": "3.1.3",
1335
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
1336
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
1337
- "dev": true,
1338
- "license": "MIT"
1339
- },
1340
- "node_modules/fast-uri": {
1341
- "version": "3.1.5",
1342
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
1343
- "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
1344
- "dev": true,
1345
- "funding": [
1346
- {
1347
- "type": "github",
1348
- "url": "https://github.com/sponsors/fastify"
1349
- },
1350
- {
1351
- "type": "opencollective",
1352
- "url": "https://opencollective.com/fastify"
1353
- }
1354
- ],
1355
- "license": "BSD-3-Clause"
1356
- },
1357
892
  "node_modules/fdir": {
1358
893
  "version": "6.5.0",
1359
894
  "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -1441,16 +976,6 @@
1441
976
  "micromatch": "^4.0.2"
1442
977
  }
1443
978
  },
1444
- "node_modules/forwarded": {
1445
- "version": "0.2.0",
1446
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
1447
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
1448
- "dev": true,
1449
- "license": "MIT",
1450
- "engines": {
1451
- "node": ">= 0.6"
1452
- }
1453
- },
1454
979
  "node_modules/fresh": {
1455
980
  "version": "2.0.0",
1456
981
  "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
@@ -1663,48 +1188,6 @@
1663
1188
  "node": ">=12.0.0"
1664
1189
  }
1665
1190
  },
1666
- "node_modules/hono": {
1667
- "version": "4.12.28",
1668
- "dev": true,
1669
- "license": "MIT",
1670
- "engines": {
1671
- "node": ">=16.9.0"
1672
- }
1673
- },
1674
- "node_modules/htmlparser2": {
1675
- "version": "12.0.0",
1676
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-12.0.0.tgz",
1677
- "integrity": "sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw==",
1678
- "funding": [
1679
- "https://github.com/fb55/htmlparser2?sponsor=1",
1680
- {
1681
- "type": "github",
1682
- "url": "https://github.com/sponsors/fb55"
1683
- }
1684
- ],
1685
- "license": "MIT",
1686
- "dependencies": {
1687
- "domelementtype": "^3.0.0",
1688
- "domhandler": "^6.0.0",
1689
- "domutils": "^4.0.2",
1690
- "entities": "^8.0.0"
1691
- },
1692
- "engines": {
1693
- "node": ">=20.19.0"
1694
- }
1695
- },
1696
- "node_modules/htmlparser2/node_modules/entities": {
1697
- "version": "8.0.0",
1698
- "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
1699
- "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
1700
- "license": "BSD-2-Clause",
1701
- "engines": {
1702
- "node": ">=20.19.0"
1703
- },
1704
- "funding": {
1705
- "url": "https://github.com/fb55/entities?sponsor=1"
1706
- }
1707
- },
1708
1191
  "node_modules/htmx.org": {
1709
1192
  "version": "2.0.4",
1710
1193
  "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.4.tgz",
@@ -1724,35 +1207,18 @@
1724
1207
  "node_modules/http-errors": {
1725
1208
  "version": "2.0.1",
1726
1209
  "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
1727
- "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
1210
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
1728
- "dev": true,
1729
- "license": "MIT",
1730
- "dependencies": {
1731
- "depd": "~2.0.0",
1732
- "inherits": "~2.0.4",
1733
- "setprototypeof": "~1.2.0",
1734
- "statuses": "~2.0.2",
1735
- "toidentifier": "~1.0.1"
1736
- },
1737
- "engines": {
1738
- "node": ">= 0.8"
1739
- },
1740
- "funding": {
1741
- "type": "opencollective",
1742
- "url": "https://opencollective.com/express"
1743
- }
1744
- },
1745
- "node_modules/iconv-lite": {
1746
- "version": "0.7.3",
1747
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
1748
- "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
1749
1211
  "dev": true,
1750
1212
  "license": "MIT",
1751
1213
  "dependencies": {
1214
+ "depd": "~2.0.0",
1215
+ "inherits": "~2.0.4",
1216
+ "setprototypeof": "~1.2.0",
1217
+ "statuses": "~2.0.2",
1752
- "safer-buffer": ">= 2.1.2 < 3.0.0"
1218
+ "toidentifier": "~1.0.1"
1753
1219
  },
1754
1220
  "engines": {
1755
- "node": ">=0.10.0"
1221
+ "node": ">= 0.8"
1756
1222
  },
1757
1223
  "funding": {
1758
1224
  "type": "opencollective",
@@ -1766,26 +1232,6 @@
1766
1232
  "dev": true,
1767
1233
  "license": "ISC"
1768
1234
  },
1769
- "node_modules/ip-address": {
1770
- "version": "10.4.0",
1771
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz",
1772
- "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==",
1773
- "dev": true,
1774
- "license": "MIT",
1775
- "engines": {
1776
- "node": ">= 12"
1777
- }
1778
- },
1779
- "node_modules/ipaddr.js": {
1780
- "version": "1.9.1",
1781
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
1782
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
1783
- "dev": true,
1784
- "license": "MIT",
1785
- "engines": {
1786
- "node": ">= 0.10"
1787
- }
1788
- },
1789
1235
  "node_modules/is-alphabetical": {
1790
1236
  "version": "2.0.1",
1791
1237
  "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
@@ -1900,13 +1346,6 @@
1900
1346
  "node": ">=0.12.0"
1901
1347
  }
1902
1348
  },
1903
- "node_modules/is-promise": {
1904
- "version": "4.0.0",
1905
- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
1906
- "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
1907
- "dev": true,
1908
- "license": "MIT"
1909
- },
1910
1349
  "node_modules/is-wsl": {
1911
1350
  "version": "2.2.0",
1912
1351
  "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@@ -1944,16 +1383,6 @@
1944
1383
  "node": ">=6.0"
1945
1384
  }
1946
1385
  },
1947
- "node_modules/jose": {
1948
- "version": "6.2.8",
1949
- "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.8.tgz",
1950
- "integrity": "sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ==",
1951
- "dev": true,
1952
- "license": "MIT",
1953
- "funding": {
1954
- "url": "https://github.com/sponsors/panva"
1955
- }
1956
- },
1957
1386
  "node_modules/js-yaml": {
1958
1387
  "version": "4.3.1",
1959
1388
  "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
@@ -1977,20 +1406,6 @@
1977
1406
  "js-yaml": "bin/js-yaml.js"
1978
1407
  }
1979
1408
  },
1980
- "node_modules/json-schema-traverse": {
1981
- "version": "1.0.0",
1982
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
1983
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
1984
- "dev": true,
1985
- "license": "MIT"
1986
- },
1987
- "node_modules/json-schema-typed": {
1988
- "version": "8.0.2",
1989
- "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
1990
- "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
1991
- "dev": true,
1992
- "license": "BSD-2-Clause"
1993
- },
1994
1409
  "node_modules/json-stable-stringify": {
1995
1410
  "version": "1.3.0",
1996
1411
  "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz",
@@ -2182,33 +1597,6 @@
2182
1597
  "integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==",
2183
1598
  "license": "MIT"
2184
1599
  },
2185
- "node_modules/media-typer": {
2186
- "version": "1.1.1",
2187
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz",
2188
- "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==",
2189
- "dev": true,
2190
- "license": "MIT",
2191
- "engines": {
2192
- "node": ">= 0.8"
2193
- },
2194
- "funding": {
2195
- "type": "opencollective",
2196
- "url": "https://opencollective.com/express"
2197
- }
2198
- },
2199
- "node_modules/merge-descriptors": {
2200
- "version": "2.0.0",
2201
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
2202
- "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
2203
- "dev": true,
2204
- "license": "MIT",
2205
- "engines": {
2206
- "node": ">=18"
2207
- },
2208
- "funding": {
2209
- "url": "https://github.com/sponsors/sindresorhus"
2210
- }
2211
- },
2212
1600
  "node_modules/micromatch": {
2213
1601
  "version": "4.0.8",
2214
1602
  "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -2328,16 +1716,6 @@
2328
1716
  "dev": true,
2329
1717
  "license": "MIT"
2330
1718
  },
2331
- "node_modules/negotiator": {
2332
- "version": "1.0.0",
2333
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
2334
- "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
2335
- "dev": true,
2336
- "license": "MIT",
2337
- "engines": {
2338
- "node": ">= 0.6"
2339
- }
2340
- },
2341
1719
  "node_modules/node-retrieve-globals": {
2342
1720
  "version": "6.0.1",
2343
1721
  "resolved": "https://registry.npmjs.org/node-retrieve-globals/-/node-retrieve-globals-6.0.1.tgz",
@@ -2411,29 +1789,6 @@
2411
1789
  "node": ">= 6"
2412
1790
  }
2413
1791
  },
2414
- "node_modules/object-assign": {
2415
- "version": "4.1.1",
2416
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
2417
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
2418
- "dev": true,
2419
- "license": "MIT",
2420
- "engines": {
2421
- "node": ">=0.10.0"
2422
- }
2423
- },
2424
- "node_modules/object-inspect": {
2425
- "version": "1.13.4",
2426
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
2427
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
2428
- "dev": true,
2429
- "license": "MIT",
2430
- "engines": {
2431
- "node": ">= 0.4"
2432
- },
2433
- "funding": {
2434
- "url": "https://github.com/sponsors/ljharb"
2435
- }
2436
- },
2437
1792
  "node_modules/object-keys": {
2438
1793
  "version": "1.1.1",
2439
1794
  "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -2457,16 +1812,6 @@
2457
1812
  "node": ">= 0.8"
2458
1813
  }
2459
1814
  },
2460
- "node_modules/once": {
2461
- "version": "1.4.0",
2462
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
2463
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
2464
- "dev": true,
2465
- "license": "ISC",
2466
- "dependencies": {
2467
- "wrappy": "1"
2468
- }
2469
- },
2470
1815
  "node_modules/open": {
2471
1816
  "version": "7.4.2",
2472
1817
  "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
@@ -2564,16 +1909,6 @@
2564
1909
  "url": "https://github.com/sponsors/jonschlinkert"
2565
1910
  }
2566
1911
  },
2567
- "node_modules/pkce-challenge": {
2568
- "version": "5.0.1",
2569
- "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
2570
- "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
2571
- "dev": true,
2572
- "license": "MIT",
2573
- "engines": {
2574
- "node": ">=16.20.0"
2575
- }
2576
- },
2577
1912
  "node_modules/please-upgrade-node": {
2578
1913
  "version": "3.2.0",
2579
1914
  "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
@@ -2748,20 +2083,6 @@
2748
2083
  "node": ">=6"
2749
2084
  }
2750
2085
  },
2751
- "node_modules/proxy-addr": {
2752
- "version": "2.0.7",
2753
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
2754
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
2755
- "dev": true,
2756
- "license": "MIT",
2757
- "dependencies": {
2758
- "forwarded": "0.2.0",
2759
- "ipaddr.js": "1.9.1"
2760
- },
2761
- "engines": {
2762
- "node": ">= 0.10"
2763
- }
2764
- },
2765
2086
  "node_modules/prr": {
2766
2087
  "version": "1.0.1",
2767
2088
  "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
@@ -2778,23 +2099,6 @@
2778
2099
  "node": ">=6"
2779
2100
  }
2780
2101
  },
2781
- "node_modules/qs": {
2782
- "version": "6.15.3",
2783
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
2784
- "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
2785
- "dev": true,
2786
- "license": "BSD-3-Clause",
2787
- "dependencies": {
2788
- "es-define-property": "^1.0.1",
2789
- "side-channel": "^1.1.1"
2790
- },
2791
- "engines": {
2792
- "node": ">=0.6"
2793
- },
2794
- "funding": {
2795
- "url": "https://github.com/sponsors/ljharb"
2796
- }
2797
- },
2798
2102
  "node_modules/range-parser": {
2799
2103
  "version": "1.3.0",
2800
2104
  "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
@@ -2809,22 +2113,6 @@
2809
2113
  "url": "https://opencollective.com/express"
2810
2114
  }
2811
2115
  },
2812
- "node_modules/raw-body": {
2813
- "version": "3.0.2",
2814
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
2815
- "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
2816
- "dev": true,
2817
- "license": "MIT",
2818
- "dependencies": {
2819
- "bytes": "~3.1.2",
2820
- "http-errors": "~2.0.1",
2821
- "iconv-lite": "~0.7.0",
2822
- "unpipe": "~1.0.0"
2823
- },
2824
- "engines": {
2825
- "node": ">= 0.10"
2826
- }
2827
- },
2828
2116
  "node_modules/readdirp": {
2829
2117
  "version": "3.6.0",
2830
2118
  "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -2851,51 +2139,6 @@
2851
2139
  "url": "https://github.com/sponsors/jonschlinkert"
2852
2140
  }
2853
2141
  },
2854
- "node_modules/require-from-string": {
2855
- "version": "2.0.2",
2856
- "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
2857
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
2858
- "dev": true,
2859
- "license": "MIT",
2860
- "engines": {
2861
- "node": ">=0.10.0"
2862
- }
2863
- },
2864
- "node_modules/router": {
2865
- "version": "2.2.0",
2866
- "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
2867
- "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
2868
- "dev": true,
2869
- "license": "MIT",
2870
- "dependencies": {
2871
- "debug": "^4.4.0",
2872
- "depd": "^2.0.0",
2873
- "is-promise": "^4.0.0",
2874
- "parseurl": "^1.3.3",
2875
- "path-to-regexp": "^8.0.0"
2876
- },
2877
- "engines": {
2878
- "node": ">= 18"
2879
- }
2880
- },
2881
- "node_modules/router/node_modules/path-to-regexp": {
2882
- "version": "8.4.2",
2883
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
2884
- "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
2885
- "dev": true,
2886
- "license": "MIT",
2887
- "funding": {
2888
- "type": "opencollective",
2889
- "url": "https://opencollective.com/express"
2890
- }
2891
- },
2892
- "node_modules/safer-buffer": {
2893
- "version": "2.1.2",
2894
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
2895
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
2896
- "dev": true,
2897
- "license": "MIT"
2898
- },
2899
2142
  "node_modules/section-matter": {
2900
2143
  "version": "1.0.0",
2901
2144
  "dev": true,
@@ -2955,26 +2198,6 @@
2955
2198
  "url": "https://opencollective.com/express"
2956
2199
  }
2957
2200
  },
2958
- "node_modules/serve-static": {
2959
- "version": "2.2.1",
2960
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
2961
- "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
2962
- "dev": true,
2963
- "license": "MIT",
2964
- "dependencies": {
2965
- "encodeurl": "^2.0.0",
2966
- "escape-html": "^1.0.3",
2967
- "parseurl": "^1.3.3",
2968
- "send": "^1.2.0"
2969
- },
2970
- "engines": {
2971
- "node": ">= 18"
2972
- },
2973
- "funding": {
2974
- "type": "opencollective",
2975
- "url": "https://opencollective.com/express"
2976
- }
2977
- },
2978
2201
  "node_modules/set-function-length": {
2979
2202
  "version": "1.2.2",
2980
2203
  "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -3023,82 +2246,6 @@
3023
2246
  "node": ">=8"
3024
2247
  }
3025
2248
  },
3026
- "node_modules/side-channel": {
3027
- "version": "1.1.1",
3028
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
3029
- "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
3030
- "dev": true,
3031
- "license": "MIT",
3032
- "dependencies": {
3033
- "es-errors": "^1.3.0",
3034
- "object-inspect": "^1.13.4",
3035
- "side-channel-list": "^1.0.1",
3036
- "side-channel-map": "^1.0.1",
3037
- "side-channel-weakmap": "^1.0.2"
3038
- },
3039
- "engines": {
3040
- "node": ">= 0.4"
3041
- },
3042
- "funding": {
3043
- "url": "https://github.com/sponsors/ljharb"
3044
- }
3045
- },
3046
- "node_modules/side-channel-list": {
3047
- "version": "1.0.1",
3048
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
3049
- "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
3050
- "dev": true,
3051
- "license": "MIT",
3052
- "dependencies": {
3053
- "es-errors": "^1.3.0",
3054
- "object-inspect": "^1.13.4"
3055
- },
3056
- "engines": {
3057
- "node": ">= 0.4"
3058
- },
3059
- "funding": {
3060
- "url": "https://github.com/sponsors/ljharb"
3061
- }
3062
- },
3063
- "node_modules/side-channel-map": {
3064
- "version": "1.0.1",
3065
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
3066
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
3067
- "dev": true,
3068
- "license": "MIT",
3069
- "dependencies": {
3070
- "call-bound": "^1.0.2",
3071
- "es-errors": "^1.3.0",
3072
- "get-intrinsic": "^1.2.5",
3073
- "object-inspect": "^1.13.3"
3074
- },
3075
- "engines": {
3076
- "node": ">= 0.4"
3077
- },
3078
- "funding": {
3079
- "url": "https://github.com/sponsors/ljharb"
3080
- }
3081
- },
3082
- "node_modules/side-channel-weakmap": {
3083
- "version": "1.0.2",
3084
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
3085
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
3086
- "dev": true,
3087
- "license": "MIT",
3088
- "dependencies": {
3089
- "call-bound": "^1.0.2",
3090
- "es-errors": "^1.3.0",
3091
- "get-intrinsic": "^1.2.5",
3092
- "object-inspect": "^1.13.3",
3093
- "side-channel-map": "^1.0.1"
3094
- },
3095
- "engines": {
3096
- "node": ">= 0.4"
3097
- },
3098
- "funding": {
3099
- "url": "https://github.com/sponsors/ljharb"
3100
- }
3101
- },
3102
2249
  "node_modules/slash": {
3103
2250
  "version": "3.0.0",
3104
2251
  "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -3218,39 +2365,6 @@
3218
2365
  "node": ">=0.6"
3219
2366
  }
3220
2367
  },
3221
- "node_modules/type-is": {
3222
- "version": "2.1.0",
3223
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
3224
- "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
3225
- "dev": true,
3226
- "license": "MIT",
3227
- "dependencies": {
3228
- "content-type": "^2.0.0",
3229
- "media-typer": "^1.1.0",
3230
- "mime-types": "^3.0.0"
3231
- },
3232
- "engines": {
3233
- "node": ">= 18"
3234
- },
3235
- "funding": {
3236
- "type": "opencollective",
3237
- "url": "https://opencollective.com/express"
3238
- }
3239
- },
3240
- "node_modules/type-is/node_modules/content-type": {
3241
- "version": "2.0.0",
3242
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
3243
- "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
3244
- "dev": true,
3245
- "license": "MIT",
3246
- "engines": {
3247
- "node": ">=18"
3248
- },
3249
- "funding": {
3250
- "type": "opencollective",
3251
- "url": "https://opencollective.com/express"
3252
- }
3253
- },
3254
2368
  "node_modules/uc.micro": {
3255
2369
  "version": "2.1.0",
3256
2370
  "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
@@ -3284,16 +2398,6 @@
3284
2398
  "dev": true,
3285
2399
  "license": "MIT"
3286
2400
  },
3287
- "node_modules/vary": {
3288
- "version": "1.1.2",
3289
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
3290
- "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
3291
- "dev": true,
3292
- "license": "MIT",
3293
- "engines": {
3294
- "node": ">= 0.8"
3295
- }
3296
- },
3297
2401
  "node_modules/which": {
3298
2402
  "version": "2.0.2",
3299
2403
  "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -3310,13 +2414,6 @@
3310
2414
  "node": ">= 8"
3311
2415
  }
3312
2416
  },
3313
- "node_modules/wrappy": {
3314
- "version": "1.0.2",
3315
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
3316
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
3317
- "dev": true,
3318
- "license": "ISC"
3319
- },
3320
2417
  "node_modules/ws": {
3321
2418
  "version": "8.20.1",
3322
2419
  "dev": true,
@@ -3352,26 +2449,6 @@
3352
2449
  "funding": {
3353
2450
  "url": "https://github.com/sponsors/eemeli"
3354
2451
  }
3355
- },
3356
- "node_modules/zod": {
3357
- "version": "4.3.6",
3358
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
3359
- "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
3360
- "dev": true,
3361
- "license": "MIT",
3362
- "funding": {
3363
- "url": "https://github.com/sponsors/colinhacks"
3364
- }
3365
- },
3366
- "node_modules/zod-to-json-schema": {
3367
- "version": "3.25.2",
3368
- "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
3369
- "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
3370
- "dev": true,
3371
- "license": "ISC",
3372
- "peerDependencies": {
3373
- "zod": "^3.25.28 || ^4"
3374
- }
3375
2452
  }
3376
2453
  }
3377
2454
  }
package.json CHANGED
@@ -7,8 +7,6 @@
7
7
  "postinstall": "patch-package"
8
8
  },
9
9
  "dependencies": {
10
- "@elenajs/core": "^1.0.0",
11
- "@elenajs/ssr": "latest",
12
10
  "diff2html": "^3.4.52",
13
11
  "htmx.org": "^2.0.4",
14
12
  "markdown-it": "^14.1.0",
@@ -18,7 +16,6 @@
18
16
  "@11ty/eleventy": "^3.0.0",
19
17
  "@11ty/eleventy-plugin-rss": "^2.0.0",
20
18
  "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
21
- "@elenajs/mcp": "^1.0.0-alpha.11",
22
19
  "@iconify-json/vscode-icons": "^1.2.68",
23
20
  "patch-package": "^8.0.1"
24
21
  }
patches/@elenajs+ssr+1.0.0-alpha.11.patch DELETED
@@ -1,108 +0,0 @@
1
- diff --git a/node_modules/@elenajs/ssr/src/common/utils.js b/node_modules/@elenajs/ssr/src/common/utils.js
2
- index 2253f72..8351dc2 100644
3
- --- a/node_modules/@elenajs/ssr/src/common/utils.js
4
- +++ b/node_modules/@elenajs/ssr/src/common/utils.js
5
- @@ -10,23 +10,50 @@ export function escapeHtml(str) {
6
- return String(str).replace(/[&<>"']/g, c => Escape[c]);
7
- }
8
-
9
- +/** Tags whose content is whitespace-significant and must survive normalization untouched. */
10
- +const PROTECTED_REGIONS = /<(pre|textarea)\b[^>]*>[\s\S]*?<\/\1>/gi;
11
- +
12
- +// Control characters (not matched by \s, <, > or the word-boundary regexes
13
- +// below) used to fence off protected regions — a plain-space-delimited
14
- +// marker isn't safe here: ">\n\s*" -> ">" would happily eat a marker's
15
- +// leading space whenever a <pre> immediately follows a closing tag +
16
- +// newline, breaking the restore match and silently dropping the whole
17
- +// protected chunk instead of just mis-formatting it.
18
- +const MARK_START = String.fromCharCode(1);
19
- +const MARK_END = String.fromCharCode(2);
20
- +const RESTORE_PATTERN = new RegExp(MARK_START + "(\\d+)" + MARK_END, "g");
21
- +
22
- /**
23
- - * Normalize whitespace to match Elena’s client-side
24
- + * Normalize whitespace to match Elena's client-side
25
- * rendering output.
26
- *
27
- - * Mirrors the core’s collapseWhitespace() pipeline:
28
- + * Mirrors the core's collapseWhitespace() pipeline:
29
- * 1. Strip newline+indent after tag close
30
- * 2. Strip newline+indent before tag open
31
- * 3. Collapse remaining newlines to a space (word boundary)
32
- * 4. Remove whitespace-only gaps between adjacent tags
33
- *
34
- + * Content inside <pre>/<textarea> is whitespace-significant (e.g. a code
35
- + * block injected via unsafeHTML()) and must not have its newlines
36
- + * collapsed, so those regions are protected before normalizing and
37
- + * restored verbatim afterwards.
38
- + *
39
- * @param {string} markup
40
- * @returns {string}
41
- */
42
- export function normalizeWhitespace(markup) {
43
- - return markup
44
- + const protectedChunks = [];
45
- + const withPlaceholders = markup.replace(PROTECTED_REGIONS, (match) => {
46
- + const token = MARK_START + protectedChunks.length + MARK_END;
47
- + protectedChunks.push(match);
48
- + return token;
49
- + });
50
- +
51
- + const normalized = withPlaceholders
52
- .replace(/>\n\s*/g, ">")
53
- .replace(/\n\s*</g, "<")
54
- .replace(/\n\s*/g, " ")
55
- .replace(/>\s+</g, "><");
56
- +
57
- + return normalized.replace(RESTORE_PATTERN, (_, i) => protectedChunks[Number(i)]);
58
- }
59
- diff --git a/node_modules/@elenajs/ssr/src/index.js b/node_modules/@elenajs/ssr/src/index.js
60
- index a134eda..b2080a8 100644
61
- --- a/node_modules/@elenajs/ssr/src/index.js
62
- +++ b/node_modules/@elenajs/ssr/src/index.js
63
- @@ -297,6 +297,9 @@ function serializeAttrs(attrs) {
64
- const WS = /[\t\n\r ]+/g;
65
- const ALL_WS = /[^\t\n\r ]/;
66
-
67
- +/** Matches a default <slot> marker: <slot></slot>, <slot/>, or <slot />. */
68
- +const SLOT_PATTERN = /<slot(?:\s*\/)?\s*>(?:\s*<\/slot>)?/;
69
- +
70
- /**
71
- * Check whether a component class has a meaningful render() method,
72
- * including inherited ones. Walks the prototype chain and distinguishes
73
- @@ -395,10 +398,34 @@ function walk(nodes, preserveWhitespace = false) {
74
- // Mark as hydrated so CSS targeting :scope:not([hydrated]) doesn’t
75
- // double-style the host alongside the already-rendered inner element.
76
- attrs.hydrated = "";
77
- + // A component that wants its light-DOM children projected in place
78
- + // (rather than only available flattened as this._text) writes a
79
- + // literal <slot></slot> marker in its own render() output. Only
80
- + // walk the original children (expanding any components inside
81
- + // them too) when a component actually asked for it — most
82
- + // components don't take children at all, so this stays free for
83
- + // them. This is a plain string substitution done once, before the
84
- + // re-parse below, rather than a real DOM-based <slot> element —
85
- + // there's no shadow root here to make that meaningful, and it
86
- + // avoids the escape/attribute/reparse round trip a "pass content
87
- + // as a prop" approach would need for arbitrary structured HTML.
88
- + if (SLOT_PATTERN.test(innerHTML)) {
89
- + const slotted = walk(children, pre);
90
- + innerHTML = innerHTML.replace(SLOT_PATTERN, slotted);
91
- + }
92
- }
93
- }
94
-
95
- - const content = innerHTML !== null ? innerHTML : walk(children, pre);
96
- + let content;
97
- + if (innerHTML !== null) {
98
- + // Re-parse and re-walk the component's own render() output so any
99
- + // custom elements it introduces (e.g. a header component rendering
100
- + // a nested theme toggle) get their own expansion pass too, instead
101
- + // of being spliced in as an opaque, already-final string.
102
- + content = walk(parseDocument(innerHTML).children, pre);
103
- + } else {
104
- + content = walk(children, pre);
105
- + }
106
- out += `<${tag}${serializeAttrs(attrs)}>${content}</${tag}>`;
107
- }
108
- }
public/.gitkeep DELETED
File without changes
src/_data/repoCommits.js CHANGED
@@ -1,5 +1,9 @@
1
1
  import { html as renderDiffHtml, parse as parseDiff } from "diff2html";
2
+ import { readCache, writeCache } from "../_helpers/dataCache.js";
2
- import { discoverLocalRepos, getCommitDiff } from "../_helpers/gitRepos.js";
3
+ import { discoverLocalRepos, getCommitDiff, getHeadSha } from "../_helpers/gitRepos.js";
4
+ import { mapPool } from "../_helpers/pool.js";
5
+
6
+ const CONCURRENCY = 8;
3
7
 
4
8
  const toDiffHtml = (diffText) => {
5
9
  const idx = diffText.indexOf("diff --git");
@@ -17,10 +21,25 @@ export default async () => {
17
21
  const repos = discoverLocalRepos();
18
22
  const pages = [];
19
23
  for (const repo of repos) {
24
+ // Unlike repoFiles.js, none of this reads the working tree — `git
25
+ // show` only ever sees committed history — so it's safe to cache on
26
+ // HEAD sha alone, with no working-tree-clean requirement. That
27
+ // matters because this repo (website) is usually dirty while it's
28
+ // being actively worked on, and its commit history is the most
29
+ // expensive part of the build.
30
+ const sha = getHeadSha(repo.dir);
20
- for (const commit of repo.commits) {
31
+ const cacheKey = `commits-${repo.id}`;
32
+
33
+ let entries = sha ? readCache(cacheKey, sha) : null;
34
+ if (!entries) {
35
+ entries = await mapPool(repo.commits, CONCURRENCY, async (commit) => {
21
- const diffText = getCommitDiff(repo.dir, commit.hash);
36
+ const diffText = await getCommitDiff(repo.dir, commit.hash);
22
- pages.push({ repoId: repo.id, repo, commit, diffHtml: toDiffHtml(diffText) });
37
+ return { commit, diffHtml: toDiffHtml(diffText) };
38
+ });
39
+ if (sha) writeCache(cacheKey, sha, entries);
23
40
  }
41
+
42
+ for (const entry of entries) pages.push({ repoId: repo.id, repo, ...entry });
24
43
  }
25
44
  return pages;
26
45
  };
src/_data/repoFiles.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { readCache, writeCache } from "../_helpers/dataCache.js";
3
4
  import {
4
5
  BINARY_EXTENSIONS,
5
6
  IMAGE_EXTENSIONS,
@@ -7,41 +8,50 @@ import {
7
8
  discoverLocalRepos,
8
9
  getBlame,
9
10
  getFileHistory,
11
+ getHeadSha,
12
+ isDirty,
10
13
  } from "../_helpers/gitRepos.js";
11
14
  import { highlightCode } from "../_helpers/highlightCode.js";
15
+ import { mapPool } from "../_helpers/pool.js";
16
+
17
+ const CONCURRENCY = 8;
12
18
 
13
19
  const toDataUri = (buf, ext) => {
14
20
  const mime = ext === "svg" ? "image/svg+xml" : `image/${ext}`;
15
21
  return `data:${mime};base64,${buf.toString("base64")}`;
16
22
  };
17
23
 
24
+ const renderFile = (repoDir, file) => {
25
+ const full = path.join(repoDir, file.path);
26
+ if (BINARY_EXTENSIONS.has(file.ext)) return { kind: "binary" };
27
+ if (file.size > LARGE_FILE_SIZE) return { kind: "large" };
28
+ if (IMAGE_EXTENSIONS.has(file.ext)) return { kind: "image", src: toDataUri(fs.readFileSync(full), file.ext) };
29
+ const { html, lang } = highlightCode(fs.readFileSync(full, "utf8"), file.ext);
30
+ return { kind: "text", html, lang };
31
+ };
32
+
18
33
  export default async () => {
19
34
  const repos = discoverLocalRepos();
20
35
  const pages = [];
21
36
  for (const repo of repos) {
37
+ const sha = getHeadSha(repo.dir);
38
+ const cacheable = sha && !isDirty(repo.dir);
22
- for (const file of repo.files) {
39
+ const cacheKey = `files-${repo.id}`;
40
+
41
+ let entries = cacheable ? readCache(cacheKey, sha) : null;
42
+ if (!entries) {
43
+ entries = await mapPool(repo.files, CONCURRENCY, async (file) => {
23
- const full = path.join(repo.dir, file.path);
44
+ const render = renderFile(repo.dir, file);
24
- let render;
25
- if (BINARY_EXTENSIONS.has(file.ext)) {
26
- render = { kind: "binary" };
27
- } else if (file.size > LARGE_FILE_SIZE) {
28
- render = { kind: "large" };
29
- } else if (IMAGE_EXTENSIONS.has(file.ext)) {
30
- render = { kind: "image", src: toDataUri(fs.readFileSync(full), file.ext) };
31
- } else {
32
- const text = fs.readFileSync(full, "utf8");
33
- const { html, lang } = highlightCode(text, file.ext);
45
+ const [history, blame] = await Promise.all([
34
- render = { kind: "text", html, lang };
35
- }
36
- pages.push({
37
- repoId: repo.id,
38
- repo,
39
- file,
40
- render,
41
- history: getFileHistory(repo.dir, file.path),
46
+ getFileHistory(repo.dir, file.path),
42
- blame: render.kind === "text" ? getBlame(repo.dir, file.path) : [],
47
+ render.kind === "text" ? getBlame(repo.dir, file.path) : [],
48
+ ]);
49
+ return { file, render, history, blame };
43
50
  });
51
+ if (cacheable) writeCache(cacheKey, sha, entries);
44
52
  }
53
+
54
+ for (const entry of entries) pages.push({ repoId: repo.id, repo, ...entry });
45
55
  }
46
56
  return pages;
47
57
  };
src/_helpers/dataCache.js ADDED
@@ -0,0 +1,35 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const CACHE_DIR = path.resolve(__dirname, "..", "..", ".cache");
7
+
8
+ const cachePath = (key) => path.join(CACHE_DIR, `${key}.json`);
9
+
10
+ /**
11
+ * Disk cache for the expensive per-commit/per-file git data (diffs,
12
+ * history, blame, syntax highlighting) computed in repoCommits.js/
13
+ * repoFiles.js. Keyed by the repo's current HEAD sha rather than by
14
+ * content hash — cheap to check (one `git rev-parse` per repo) and
15
+ * correct as long as callers only read the cache for a clean working
16
+ * tree (see isDirty() in gitRepos.js): if HEAD hasn't moved and there
17
+ * are no uncommitted changes, none of this data could have changed.
18
+ */
19
+ export const readCache = (key, sha) => {
20
+ try {
21
+ const { sha: cachedSha, data } = JSON.parse(fs.readFileSync(cachePath(key), "utf8"));
22
+ return cachedSha === sha ? data : null;
23
+ } catch {
24
+ return null;
25
+ }
26
+ };
27
+
28
+ export const writeCache = (key, sha, data) => {
29
+ try {
30
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
31
+ fs.writeFileSync(cachePath(key), JSON.stringify({ sha, data }));
32
+ } catch {
33
+ // best-effort — a failed cache write shouldn't fail the build
34
+ }
35
+ };
src/_helpers/gitRepos.js CHANGED
@@ -1,9 +1,12 @@
1
- import { execFileSync } from "node:child_process";
1
+ import { execFile, execFileSync } from "node:child_process";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { promisify } from "node:util";
5
6
  import site from "../_data/site.js";
6
7
 
8
+ const execFileAsync = promisify(execFile);
9
+
7
10
  const { repos: REPOS } = site;
8
11
 
9
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -24,13 +27,26 @@ const SEP = "\x1f";
24
27
  const git = (repoDir, args) =>
25
28
  execFileSync("git", args, { cwd: repoDir, encoding: "utf8", maxBuffer: MAX_BUFFER });
26
29
 
30
+ const gitAsync = async (repoDir, args) =>
31
+ (await execFileAsync("git", args, { cwd: repoDir, encoding: "utf8", maxBuffer: MAX_BUFFER })).stdout;
32
+
33
+ // A repo's own-git-repo-ness can't change mid-build, but this used to be
34
+ // re-checked (spawning its own `git rev-parse` subprocess) on every single
35
+ // commit/file call — doubling the subprocess count across a build with
36
+ // hundreds of commits and files. One check per repoDir is enough.
37
+ const ownRepoCache = new Map();
38
+
27
39
  const isOwnGitRepo = (repoDir) => {
40
+ if (ownRepoCache.has(repoDir)) return ownRepoCache.get(repoDir);
41
+ let result;
28
42
  try {
29
43
  const top = git(repoDir, ["rev-parse", "--show-toplevel"]).trim();
30
- return fs.realpathSync(top) === fs.realpathSync(repoDir);
44
+ result = fs.realpathSync(top) === fs.realpathSync(repoDir);
31
45
  } catch {
32
- return false;
46
+ result = false;
33
47
  }
48
+ ownRepoCache.set(repoDir, result);
49
+ return result;
34
50
  };
35
51
 
36
52
  // Hard safety net independent of git tracking state: never walk into these,
@@ -158,10 +174,10 @@ const getCommits = (repoDir) => {
158
174
  }
159
175
  };
160
176
 
161
- export const getFileHistory = (repoDir, filePath) => {
177
+ export const getFileHistory = async (repoDir, filePath) => {
162
178
  if (!isOwnGitRepo(repoDir)) return [];
163
179
  try {
164
- const out = git(repoDir, [
180
+ const out = await gitAsync(repoDir, [
165
181
  "log",
166
182
  "--follow",
167
183
  "--date=iso-strict",
@@ -175,19 +191,19 @@ export const getFileHistory = (repoDir, filePath) => {
175
191
  }
176
192
  };
177
193
 
178
- export const getCommitDiff = (repoDir, hash) => {
194
+ export const getCommitDiff = async (repoDir, hash) => {
179
195
  if (!isOwnGitRepo(repoDir)) return "";
180
196
  try {
181
- return git(repoDir, ["show", "--no-color", "-p", "--stat", hash]);
197
+ return await gitAsync(repoDir, ["show", "--no-color", "-p", "--stat", hash]);
182
198
  } catch {
183
199
  return "";
184
200
  }
185
201
  };
186
202
 
187
- export const getBlame = (repoDir, filePath) => {
203
+ export const getBlame = async (repoDir, filePath) => {
188
204
  if (!isOwnGitRepo(repoDir)) return [];
189
205
  try {
190
- const out = git(repoDir, ["blame", "--porcelain", "--", filePath]);
206
+ const out = await gitAsync(repoDir, ["blame", "--porcelain", "--", filePath]);
191
207
  const lines = out.split("\n");
192
208
  const commitInfo = new Map();
193
209
  const result = [];
@@ -226,6 +242,30 @@ export const getBlame = (repoDir, filePath) => {
226
242
  }
227
243
  };
228
244
 
245
+ // Cache key material for dataCache.js: the per-commit/per-file data
246
+ // (diffs, history, blame, highlighting) computed in repoCommits.js/
247
+ // repoFiles.js only depends on the repo's committed state, so it's safe
248
+ // to skip recomputing it whenever HEAD hasn't moved — but only when the
249
+ // working tree is also clean, since file content there is read straight
250
+ // off disk rather than out of git's object store.
251
+ export const getHeadSha = (repoDir) => {
252
+ if (!isOwnGitRepo(repoDir)) return null;
253
+ try {
254
+ return git(repoDir, ["rev-parse", "HEAD"]).trim();
255
+ } catch {
256
+ return null;
257
+ }
258
+ };
259
+
260
+ export const isDirty = (repoDir) => {
261
+ if (!isOwnGitRepo(repoDir)) return true;
262
+ try {
263
+ return git(repoDir, ["status", "--porcelain"]).trim().length > 0;
264
+ } catch {
265
+ return true;
266
+ }
267
+ };
268
+
229
269
  const readReadme = (repoDir) => {
230
270
  for (const name of ["README.md", "readme.md", "Readme.md"]) {
231
271
  const p = path.join(repoDir, name);
src/_helpers/pool.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Run `mapper` over `items` with at most `limit` concurrent in-flight
3
+ * calls, preserving input order in the result. The git subprocess calls
4
+ * this feeds are I/O-bound (fork+exec, not CPU), so running them
5
+ * concurrently instead of one-at-a-time cuts wall-clock time roughly
6
+ * proportional to `limit` on a multi-core machine.
7
+ */
8
+ export const mapPool = async (items, limit, mapper) => {
9
+ const results = new Array(items.length);
10
+ let next = 0;
11
+ const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
12
+ while (next < items.length) {
13
+ const i = next++;
14
+ results[i] = await mapper(items[i], i);
15
+ }
16
+ });
17
+ await Promise.all(workers);
18
+ return results;
19
+ };
src/_includes/layout.njk CHANGED
@@ -21,10 +21,7 @@
21
21
  <meta property="og:description" content="{{ description or site.description }}" />
22
22
  <meta property="og:image" content="/assets/icons/icon.svg" />
23
23
  <meta name="twitter:card" content="summary_large_image" />
24
- <script type="importmap">
25
- { "imports": { "@elenajs/core": "/assets/vendor/elena-core.js" } }
26
- </script>
27
- <script type="module" src="/assets/components/theme-switcher.js"></script>
24
+ <script defer src="/assets/components/theme-switcher.js"></script>
28
25
  <script src="/assets/vendor/htmx.min.js"></script>
29
26
  </head>
30
27
  <body>
src/_includes/site-header.njk CHANGED
@@ -3,7 +3,7 @@
3
3
  <nav>
4
4
  <a href="/" class="logo{% if page.url == '/' %} active{% endif %}">木 {{ site.title }}</a>
5
5
  <div class="links">
6
- <span id="theme-switch"><theme-switcher></theme-switcher></span>
6
+ <span id="theme-switch"><button id="theme-change" type="button"><svg style="display:var(--btn-light)" viewBox="0 0 24 24" width="1.2em" height="1.2em"><path fill="currentColor" d="M11.288 4.713Q11 4.425 11 4V2q0-.425.288-.712T12 1t.713.288T13 2v2q0 .425-.288.713T12 5t-.712-.288M16.95 7.05q-.275-.275-.275-.687t.275-.713l1.4-1.425q.3-.3.712-.3t.713.3q.275.275.275.7t-.275.7L18.35 7.05q-.275.275-.7.275t-.7-.275M20 13q-.425 0-.713-.288T19 12t.288-.712T20 11h2q.425 0 .713.288T23 12t-.288.713T22 13zm-8.712 9.713Q11 22.425 11 22v-2q0-.425.288-.712T12 19t.713.288T13 20v2q0 .425-.288.713T12 23t-.712-.288M5.65 7.05l-1.425-1.4q-.3-.3-.3-.725t.3-.7q.275-.275.7-.275t.7.275L7.05 5.65q.275.275.275.7t-.275.7q-.3.275-.7.275t-.7-.275m12.7 12.725l-1.4-1.425q-.275-.3-.275-.712t.275-.688t.688-.275t.712.275l1.425 1.4q.3.275.288.7t-.288.725q-.3.3-.725.3t-.7-.3M2 13q-.425 0-.712-.288T1 12t.288-.712T2 11h2q.425 0 .713.288T5 12t-.288.713T4 13zm2.225 6.775q-.275-.275-.275-.7t.275-.7L5.65 16.95q.275-.275.687-.275t.713.275q.3.3.3.713t-.3.712l-1.4 1.4q-.3.3-.725.3t-.7-.3M7.75 16.25Q6 14.5 6 12t1.75-4.25T12 6t4.25 1.75T18 12t-1.75 4.25T12 18t-4.25-1.75"/></svg><svg style="display:var(--btn-dark)" viewBox="0 0 24 24" width="1.2em" height="1.2em"><path fill="currentColor" d="m15 8l-3-3l3-3l3 3zm5 3l-2-2l2-2l2 2zm-7.925 11q-2.1 0-3.937-.8t-3.2-2.162t-2.163-3.2t-.8-3.938q0-3.65 2.325-6.437T10.225 2q-.45 2.475.275 4.838t2.5 4.137t4.138 2.5t4.837.275q-.65 3.6-3.45 5.925T12.075 22"/></svg></button></span>
7
7
  <div>|</div>
8
8
  {% for item in site.navItems %}
9
9
  <a href="{{ item.href }}" class="{% if page.url == item.href or (item.href != '/' and page.url.startsWith(item.href)) %}active{% endif %}">{{ item.label }}</a><div>|</div>
src/components/theme-switcher.js CHANGED
@@ -1,38 +1,17 @@
1
- import { Elena, html } from "@elenajs/core";
2
-
3
- // The only component in this site that ships to the browser (see
4
- // eleventy.config.js passthrough copy + import map in layout.njk) — every
5
- // other component here is SSR-only and never runs client-side. render()
6
- // produces the static button/icons so it's baked into the HTML at build
7
- // time (no flash of a missing button); firstUpdated() only runs for real
8
- // once this same source is loaded as a client <script type="module">.
9
- export default class ThemeSwitcher extends Elena(HTMLElement) {
10
- static tagName = "theme-switcher";
11
-
12
- render() {
13
- return html`<button id="theme-change" type="button">
14
- <svg style="display:var(--btn-light)" viewBox="0 0 24 24" width="1.2em" height="1.2em"><path fill="currentColor" d="M11.288 4.713Q11 4.425 11 4V2q0-.425.288-.712T12 1t.713.288T13 2v2q0 .425-.288.713T12 5t-.712-.288M16.95 7.05q-.275-.275-.275-.687t.275-.713l1.4-1.425q.3-.3.712-.3t.713.3q.275.275.275.7t-.275.7L18.35 7.05q-.275.275-.7.275t-.7-.275M20 13q-.425 0-.713-.288T19 12t.288-.712T20 11h2q.425 0 .713.288T23 12t-.288.713T22 13zm-8.712 9.713Q11 22.425 11 22v-2q0-.425.288-.712T12 19t.713.288T13 20v2q0 .425-.288.713T12 23t-.712-.288M5.65 7.05l-1.425-1.4q-.3-.3-.3-.725t.3-.7q.275-.275.7-.275t.7.275L7.05 5.65q.275.275.275.7t-.275.7q-.3.275-.7.275t-.7-.275m12.7 12.725l-1.4-1.425q-.275-.3-.275-.712t.275-.688t.688-.275t.712.275l1.425 1.4q.3.275.288.7t-.288.725q-.3.3-.725.3t-.7-.3M2 13q-.425 0-.712-.288T1 12t.288-.712T2 11h2q.425 0 .713.288T5 12t-.288.713T4 13zm2.225 6.775q-.275-.275-.275-.7t.275-.7L5.65 16.95q.275-.275.687-.275t.713.275q.3.3.3.713t-.3.712l-1.4 1.4q-.3.3-.725.3t-.7-.3M7.75 16.25Q6 14.5 6 12t1.75-4.25T12 6t4.25 1.75T18 12t-1.75 4.25T12 18t-4.25-1.75"/></svg>
15
- <svg style="display:var(--btn-dark)" viewBox="0 0 24 24" width="1.2em" height="1.2em"><path fill="currentColor" d="m15 8l-3-3l3-3l3 3zm5 3l-2-2l2-2l2 2zm-7.925 11q-2.1 0-3.937-.8t-3.2-2.162t-2.163-3.2t-.8-3.938q0-3.65 2.325-6.437T10.225 2q-.45 2.475.275 4.838t2.5 4.137t4.138 2.5t4.837.275q-.65 3.6-3.45 5.925T12.075 22"/></svg>
16
- </button>`;
17
- }
18
-
19
- firstUpdated() {
20
- this.addEventListener("click", () => {
21
- const root = document.documentElement;
22
- // data-theme is tri-state: absent means "follow the OS preference"
23
- // (see `color-scheme: light dark` in shared.css). Resolve against
24
- // that OS preference when no explicit value is set yet, so the
25
- // first click always flips to the opposite of what's *currently
26
- // shown* rather than assuming light.
27
- const currentTheme = root.getAttribute("data-theme");
28
- const isDark = currentTheme
29
- ? currentTheme === "dark"
30
- : matchMedia("(prefers-color-scheme: dark)").matches;
31
- const next = isDark ? "light" : "dark";
32
- root.setAttribute("data-theme", next);
33
- localStorage.setItem("theme", next);
34
- });
35
- }
36
- }
37
-
38
- ThemeSwitcher.define();
1
+ // Wires up the theme button rendered as static markup in site-header.njk
2
+ // (no web component, no framework — a stateless click-to-toggle button
3
+ // doesn't need one). Loaded with `defer` so this runs after the DOM is
4
+ // parsed and #theme-change already exists.
5
+ document.getElementById("theme-change").addEventListener("click", () => {
6
+ const root = document.documentElement;
7
+ // data-theme is tri-state: absent means "follow the OS preference" (see
8
+ // `color-scheme: light dark` in shared.css). Resolve against that OS
9
+ // preference when no explicit value is set yet, so the first click
10
+ // always flips to the opposite of what's *currently shown* rather than
11
+ // assuming light.
12
+ const currentTheme = root.getAttribute("data-theme");
13
+ const isDark = currentTheme ? currentTheme === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
14
+ const next = isDark ? "light" : "dark";
15
+ root.setAttribute("data-theme", next);
16
+ localStorage.setItem("theme", next);
17
+ });
src/only-bible-app/index.njk CHANGED
@@ -7,10 +7,10 @@ permalink: /only-bible-app/
7
7
  <div style="display:flex;flex:1;flex-direction:column;padding-bottom:16rem">
8
8
  <div style="display:flex;flex-direction:column;align-items:center">
9
9
  <div style="display:block;margin-bottom:1rem">
10
- <h1 style="display:flex;flex-direction:column;align-items:center;font-size:1.875rem;line-height:2.25rem;font-weight:700;text-align:center">
10
+ <h1 style="display:flex;flex-direction:column;align-items:center;font-size:var(--fs-title-lg);line-height:2.25rem;font-weight:700;text-align:center">
11
11
  The only bible app you will ever need
12
12
  </h1>
13
- <div style="text-align:center;display:flex;flex-direction:column;font-size:1.125rem;line-height:1.75rem">
13
+ <div style="text-align:center;display:flex;flex-direction:column;font-size:var(--fs-features);line-height:1.75rem">
14
14
  <span>No ads,</span>
15
15
  <span>No in-app purchases,</span>
16
16
  <span>No distractions.</span>
@@ -18,7 +18,7 @@ permalink: /only-bible-app/
18
18
  </div>
19
19
  </div>
20
20
  <div style="display:flex;flex:1;flex-direction:column;align-items:center">
21
- <h3 style="margin:0;margin-top:1rem;margin-bottom:1rem;font-weight:700;font-size:1.25rem;line-height:1.75rem">
21
+ <h3 style="margin:0;margin-top:1rem;margin-bottom:1rem;font-weight:700;font-size:var(--fs-heading-sm);line-height:1.75rem">
22
22
  What are you waiting for? Get it now! Quickly.
23
23
  </h3>
24
24
  <div style="display:flex;flex-direction:column">
src/only-bible-app/privacy-policy.njk CHANGED
@@ -4,7 +4,7 @@ title: Privacy Policy | Only Bible App
4
4
  description: Privacy Policy | Only Bible App
5
5
  permalink: /only-bible-app/privacy-policy/
6
6
  ---
7
- <h1 style="font-size:1.875rem;line-height:2.25rem;font-weight:700">Privacy Policy</h1>
7
+ <h1 style="font-size:var(--fs-title-lg);line-height:2.25rem;font-weight:700">Privacy Policy</h1>
8
8
  <p>
9
9
  Only Bible App does not collect any information about you or your usage of the app. It
10
10
  does not send any information about you to anyone. It does not contain ads. It will never
src/only-bible-app/terms-and-conditions.njk CHANGED
@@ -5,7 +5,7 @@ description: Terms and Conditions | Only Bible App
5
5
  permalink: /only-bible-app/terms-and-conditions/
6
6
  ---
7
7
  <div style="padding-bottom:2.5rem;margin-top:2.5rem">
8
- <h1 style="font-weight:700;font-size:1.875rem;line-height:2.25rem">Terms and Conditions</h1>
8
+ <h1 style="font-weight:700;font-size:var(--fs-title-lg);line-height:2.25rem">Terms and Conditions</h1>
9
9
  <p>
10
10
  By downloading or using the app, these terms will automatically apply to you &mdash; you
11
11
  should make sure therefore that you read them carefully before using the app.
src/posts.njk CHANGED
@@ -4,17 +4,17 @@ title: Posts
4
4
  description: Posts
5
5
  permalink: /posts/
6
6
  ---
7
- <h1 style="font-weight:800;font-size:2rem">Posts</h1>
7
+ <h1 style="font-weight:800;font-size:var(--fs-hero-xl)">Posts</h1>
8
8
  <ul style="display:flex;flex-direction:column;list-style:none;padding:0">
9
9
  {% for post in collections.posts | reverse %}
10
10
  <li style="display:grid;grid-template-columns:1fr;gap:0.4rem;margin-top:1.25rem;line-height:1.5rem">
11
11
  <div>
12
- <a href="{{ post.url }}" style="view-transition-name:post-title-{{ post.fileSlug }};font-size:1.1rem;color:var(--color-post-link);text-underline-offset:4px">
12
+ <a href="{{ post.url }}" style="view-transition-name:post-title-{{ post.fileSlug }};font-size:var(--fs-sm);color:var(--color-post-link);text-underline-offset:4px">
13
13
  {{ post.data.title }}
14
14
  </a>
15
15
  <span style="font-size:13pt;display:block">{{ post.data.description }}</span>
16
16
  </div>
17
- <time style="font-size:1.1rem">{{ post.data.pubDate | readableDate }}</time>
17
+ <time style="font-size:var(--fs-sm)">{{ post.data.pubDate | readableDate }}</time>
18
18
  </li>
19
19
  {% endfor %}
20
20
  </ul>
wrangler.jsonc DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "$schema": "node_modules/wrangler/config-schema.json",
3
- "name": "pyrossh-website",
4
- "main": "src/index.ts",
5
- "compatibility_date": "2026-05-28",
6
- "compatibility_flags": ["nodejs_compat"],
7
- "assets": { "directory": "assets" },
8
- "vars": {
9
- "S3_ENDPOINT": "https://dfdb8ca4e419990852eb214c5b50bd5e.r2.cloudflarestorage.com/pyrossh-repos-prd",
10
- "S3_REGION": "auto"
11
- },
12
- "routes": [{ "pattern": "pyrossh.dev", "custom_domain": true }]
13
- }
wrangler.toml DELETED
@@ -1,32 +0,0 @@
1
- name = "pyrossh-website"
2
- account_id = "dfdb8ca4e419990852eb214c5b50bd5e"
3
- compatibility_date = "2026-05-15"
4
- compatibility_flags = ["nodejs_compat"]
5
-
6
- [[routes]]
7
- pattern = "pyrossh.dev"
8
- custom_domain = true
9
-
10
- [observability]
11
- enabled = true
12
-
13
- [vars]
14
- REPOS_R2_BUCKET = "pyrossh-repos-prd"
15
-
16
- [[r2_buckets]]
17
- binding = "REPOS"
18
- bucket_name = "pyrossh-repos-prd"
19
- preview_bucket_name = "pyrossh-repos-prd"
20
- remote = true
21
-
22
- [env.dev]
23
- name = "pyrossh-website-dev"
24
- workers_dev = true
25
-
26
- [env.dev.vars]
27
- REPOS_R2_BUCKET = "pyrossh-repos-dev"
28
-
29
- [[env.dev.r2_buckets]]
30
- binding = "REPOS"
31
- bucket_name = "pyrossh-repos-prd"
32
- preview_bucket_name = "pyrossh-repos-prd"