+33
deno.lock
+33
deno.lock
···
11
11
"jsr:@eta-dev/eta@^3.5.0": "3.5.0",
12
12
"jsr:@std/async@^1.0.14": "1.0.14",
13
13
"jsr:@std/cli@^1.0.21": "1.0.21",
14
+
"jsr:@std/crypto@^1.0.5": "1.0.5",
14
15
"jsr:@std/encoding@^1.0.10": "1.0.10",
15
16
"jsr:@std/fmt@^1.0.8": "1.0.8",
16
17
"jsr:@std/fmt@~1.0.2": "1.0.8",
···
26
27
"jsr:@std/text@~1.0.7": "1.0.15",
27
28
"npm:@lit/context@^1.1.6": "1.1.6",
28
29
"npm:@markdoc/markdoc@*": "0.5.2",
30
+
"npm:@types/blobshape@^1.0.3": "1.0.3",
29
31
"npm:@types/hast@^3.0.4": "3.0.4",
30
32
"npm:@types/node@*": "22.15.15",
31
33
"npm:esbuild@~0.25.5": "0.25.8",
···
33
35
"npm:esbuild@~0.25.9": "0.25.9",
34
36
"npm:hast-util-is-element@3": "3.0.0",
35
37
"npm:lit@^3.3.1": "3.3.1",
38
+
"npm:mini-svg-data-uri@^1.4.4": "1.4.4",
36
39
"npm:motion@^12.23.12": "12.23.12",
40
+
"npm:polished@^4.3.1": "4.3.1",
37
41
"npm:shiki@^3.8.1": "3.8.1",
38
42
"npm:xstate@^5.20.2": "5.20.2"
39
43
},
···
86
90
"@std/cli@1.0.21": {
87
91
"integrity": "cd25b050bdf6282e321854e3822bee624f07aca7636a3a76d95f77a3a919ca2a"
88
92
},
93
+
"@std/crypto@1.0.5": {
94
+
"integrity": "0dcfbb319fe0bba1bd3af904ceb4f948cde1b92979ec1614528380ed308a3b40"
95
+
},
89
96
"@std/encoding@1.0.10": {
90
97
"integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
91
98
},
···
135
142
}
136
143
},
137
144
"npm": {
145
+
"@babel/runtime@7.28.3": {
146
+
"integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA=="
147
+
},
138
148
"@esbuild/aix-ppc64@0.25.8": {
139
149
"integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
140
150
"os": ["aix"],
···
463
473
"@shikijs/vscode-textmate@10.0.2": {
464
474
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="
465
475
},
476
+
"@types/blobshape@1.0.3": {
477
+
"integrity": "sha512-jbJZ9AIebK77LoeN8PjPyAs88GJCDKO+ZpWCJlLlV2FXkeYScQsvkZmlGHGvSRk/WFmUOdbGJ75umLATGX1lsw=="
478
+
},
466
479
"@types/hast@3.0.4": {
467
480
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
468
481
"dependencies": [
···
688
701
},
689
702
"micromark-util-types@2.0.2": {
690
703
"integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="
704
+
},
705
+
"mini-svg-data-uri@1.4.4": {
706
+
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
707
+
"bin": true
691
708
},
692
709
"motion-dom@12.23.12": {
693
710
"integrity": "sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==",
···
716
733
"regex-recursion"
717
734
]
718
735
},
736
+
"polished@4.3.1": {
737
+
"integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==",
738
+
"dependencies": [
739
+
"@babel/runtime"
740
+
]
741
+
},
719
742
"property-information@7.1.0": {
720
743
"integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="
721
744
},
···
825
848
"https://esm.sh/@types/markdown-it@~14.1.2/lib/token.d.mts": "https://esm.sh/@types/markdown-it@14.1.2/lib/token.d.mts"
826
849
},
827
850
"remote": {
851
+
"https://deno.land/std@0.93.0/hash/sha256.ts": "2a06afd9c27942b87ffc8a93b3270065b5fe4ea144fe0939e5d050bfb86d40db",
852
+
"https://deno.land/x/color_hash@v2.0.1/lib/bkdr-hash.ts": "8005d57742d5d8779d8bafd86e9f142ebdb6f350514f89b451036abf8bff428b",
853
+
"https://deno.land/x/color_hash@v2.0.1/lib/colors.ts": "924fa3b20409325afe408f30ed6de539c08ea66916900642e9c6fa57af1487a5",
854
+
"https://deno.land/x/color_hash@v2.0.1/lib/sha256.ts": "7a1aa66825cb75e2554ff942ff110048fb372cf261512093c2aa79105d44e716",
855
+
"https://deno.land/x/color_hash@v2.0.1/mod.ts": "67d9e365f72652d14b5b723a1eb165297f990757ba1c0ac5d5612e755d76c2ca",
828
856
"https://esm.sh/@markdoc/markdoc@0.5.2": "76be956a8424e9b05c24b594a7714b39730fbd2b8e577db754606a211dfb4b89",
829
857
"https://esm.sh/@markdoc/markdoc@0.5.2/denonext/markdoc.mjs": "3d8780797f37e2a81843a33cea9081ad7c500f49549badc6aee2b1bec5d97149"
830
858
},
···
841
869
"packages/core": {
842
870
"dependencies": [
843
871
"jsr:@eta-dev/eta@^3.5.0",
872
+
"jsr:@std/crypto@^1.0.5",
873
+
"jsr:@std/encoding@^1.0.10",
844
874
"jsr:@std/path@^1.1.1",
875
+
"npm:@types/blobshape@^1.0.3",
845
876
"npm:@types/hast@^3.0.4",
846
877
"npm:hast-util-is-element@3",
878
+
"npm:mini-svg-data-uri@^1.4.4",
879
+
"npm:polished@^4.3.1",
847
880
"npm:shiki@^3.8.1"
848
881
]
849
882
},
+6
packages/core/deno.json
+6
packages/core/deno.json
···
5
5
"imports": {
6
6
"@eta-dev/eta": "jsr:@eta-dev/eta@^3.5.0",
7
7
"@markdoc/markdoc": "https://esm.sh/@markdoc/markdoc@0.5.2",
8
+
"@std/crypto": "jsr:@std/crypto@^1.0.5",
9
+
"@std/encoding": "jsr:@std/encoding@^1.0.10",
8
10
"@std/path": "jsr:@std/path@^1.1.1",
11
+
"@types/blobshape": "npm:@types/blobshape@^1.0.3",
12
+
"color-hash": "https://deno.land/x/color_hash@v2.0.1/mod.ts",
9
13
"hast": "npm:@types/hast@^3.0.4",
10
14
"hast-util-is-element": "npm:hast-util-is-element@^3.0.0",
15
+
"mini-svg-data-uri": "npm:mini-svg-data-uri@^1.4.4",
16
+
"polished": "npm:polished@^4.3.1",
11
17
"shiki": "npm:shiki@^3.8.1"
12
18
}
13
19
}
+46
packages/core/favicon.ts
+46
packages/core/favicon.ts
···
1
+
import { crypto } from "@std/crypto";
2
+
import { encodeHex } from "@std/encoding/hex";
3
+
import ColorHash from "color-hash";
4
+
import svgToUri from "mini-svg-data-uri";
5
+
import { adjustHue, hsl } from "polished";
6
+
7
+
const colorHash = new ColorHash();
8
+
9
+
/**
10
+
* Generates a data URI for a favicon SVG based on content hash
11
+
* @param content - The content to hash for favicon generation
12
+
* @param size - The size of the favicon (default: 16)
13
+
* @returns A data URI string for the generated favicon
14
+
*/
15
+
export async function generateFavicon(
16
+
content: string,
17
+
size: number = 16,
18
+
): Promise<string> {
19
+
const encoder = new TextEncoder();
20
+
const hash = await crypto.subtle.digest("SHA-256", encoder.encode(content));
21
+
const hashStr = encodeHex(hash);
22
+
23
+
// Generate colors
24
+
const baseColor = hsl(...colorHash.hsl(hashStr));
25
+
const complementaryColor = adjustHue(180, baseColor);
26
+
27
+
// Generate shapes and construct SVG directly
28
+
const rectRadius = size * 0.25;
29
+
const center = size / 2;
30
+
const circleRadius = size * 0.25;
31
+
32
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewbox="0 0 ${size} ${size}">` +
33
+
`<path d="M ${rectRadius.toFixed(2)} 0 ` +
34
+
`L ${(size - rectRadius).toFixed(2)} 0 ` +
35
+
`Q ${size} 0 ${size} ${rectRadius.toFixed(2)} ` +
36
+
`L ${size} ${(size - rectRadius).toFixed(2)} ` +
37
+
`Q ${size} ${size} ${(size - rectRadius).toFixed(2)} ${size} ` +
38
+
`L ${rectRadius.toFixed(2)} ${size} ` +
39
+
`Q 0 ${size} 0 ${(size - rectRadius).toFixed(2)} ` +
40
+
`L 0 ${rectRadius.toFixed(2)} ` +
41
+
`Q 0 0 ${rectRadius.toFixed(2)} 0 Z" fill="${baseColor}" />` +
42
+
`<circle cx="${center}" cy="${center}" r="${circleRadius.toFixed(2)}" fill="${complementaryColor}" />` +
43
+
`</svg>`;
44
+
45
+
return svgToUri(svg);
46
+
}
+7
packages/core/renderer.ts
+7
packages/core/renderer.ts
···
1
1
import Markdoc, { Node } from "@markdoc/markdoc";
2
2
import { createMarkdocConfig } from "./markdoc-config.ts";
3
+
import { generateFavicon } from "./favicon.ts";
3
4
import { Eta } from "@eta-dev/eta";
4
5
import { resolve } from "@std/path";
5
6
import type { RenderOptions } from "./types.ts";
···
12
13
path: string,
13
14
options?: RenderOptions,
14
15
) {
16
+
const encoder = new TextEncoder();
15
17
const file = await Deno.readTextFile(path);
16
18
const ast = Markdoc.parse(file);
17
19
···
40
42
includes.add("live-reload");
41
43
}
42
44
45
+
// Extract and/or set title
43
46
const titleMatch = file.match(/^# (.+)$/m);
44
47
const title = titleMatch ? titleMatch[1] : "Presentation";
45
48
49
+
// Generate favicon
50
+
const favicon = await generateFavicon(file, 16);
51
+
46
52
return eta.render("presentation", {
47
53
body: Markdoc.renderers.html(await Promise.resolve(tree)),
48
54
title,
49
55
includes,
56
+
favicon,
50
57
});
51
58
}
+5
packages/core/templates/partials/favicon.eta
+5
packages/core/templates/partials/favicon.eta
+2
packages/core/templates/presentation.eta
+2
packages/core/templates/presentation.eta