My personal website

Add syntax highlighting

fruno.win 8593078b 533c0316

verified
+416 -22
+1 -1
.tangled/workflows/publish.yaml
··· 9 9 nixpkgs: 10 10 - coreutils 11 11 - curl 12 + - deno 12 13 github:NixOS/nixpkgs/nixpkgs-unstable: 13 14 - gleam 14 - - beamMinimal28Packages.erlang 15 15 16 16 environment: 17 17 SITE_PATH: 'dist'
+47 -10
assets/style.css
··· 1 1 :root { 2 2 color-scheme: light dark; 3 3 4 - --color-fg: light-dark(#1f1f28, #dcd7ba); 5 - --color-bg: light-dark(#dcd7ba, #1f1f28); 4 + --color-fg: light-dark(#545456, #dcd7ba); 5 + --color-bg: light-dark(#f2ecbc, #1f1f28); 6 6 7 7 --color-fg-muted: light-dark(#363646, #bab28a); 8 8 /* lighter in dark mode, darker in light mode */ 9 9 --color-bg-variant: light-dark(#363646, #bab28a); 10 10 11 - --color-primary: light-dark(#957fb8, #938aa9); 11 + --color-primary: light-dark(#b35b79, #938aa9); 12 12 --color-primary-variant: light-dark(#938aa9, #957fb8); 13 13 --color-secondary: light-dark(#6a9589, #7aa89f); 14 14 --color-select: light-dark(#6693bf, #7e9cd8); ··· 17 17 --font: "Inclusive Sans", sans-serif; 18 18 --font-mono: "Myna", monospace; 19 19 20 + --ratio: 1.5; 21 + --s-6: calc(var(--s-5) / var(--ratio)); 22 + --s-5: calc(var(--s-4) / var(--ratio)); 23 + --s-4: calc(var(--s-3) / var(--ratio)); 24 + --s-3: calc(var(--s-2) / var(--ratio)); 25 + --s-2: calc(var(--s-1) / var(--ratio)); 26 + --s-1: calc(var(--s0) / var(--ratio)); 27 + --s0: 1rem; 28 + --s1: calc(var(--s0) * var(--ratio)); 29 + --s2: calc(var(--s1) * var(--ratio)); 30 + --s3: calc(var(--s2) * var(--ratio)); 31 + --s4: calc(var(--s3) * var(--ratio)); 32 + --s5: calc(var(--s4) * var(--ratio)); 33 + 34 + 20 35 color: var(--color-fg); 21 36 background: var(--color-bg); 22 37 ··· 24 39 font-size: calc(.333vw + 1em); 25 40 } 26 41 42 + /* Shiki dark mode */ 43 + @media (prefers-color-scheme: dark) { 44 + .shiki, 45 + .shiki span { 46 + color: var(--shiki-dark) !important; 47 + background-color: var(--shiki-dark-bg) !important; 48 + /* Optional, if you also want font styles */ 49 + font-style: var(--shiki-dark-font-style) !important; 50 + font-weight: var(--shiki-dark-font-weight) !important; 51 + text-decoration: var(--shiki-dark-text-decoration) !important; 52 + } 53 + } 54 + 27 55 /* Fonts */ 28 56 29 57 @font-face { ··· 53 81 54 82 main { 55 83 view-transition-name: main-content; 56 - padding-bottom: 2em; 84 + padding-bottom: var(--s2); 57 85 } 58 86 59 87 main > * { ··· 68 96 } 69 97 70 98 code { font-family: var(--font-mono) } 99 + pre { margin: 0 auto } 100 + .code pre { 101 + padding: 1em; 102 + 103 + border: var(--s-6) solid var(--color-primary); 104 + border-radius: var(--s-3); 105 + 106 + box-shadow: var(--s-4) var(--s-4) var(--color-primary); 107 + } 71 108 72 109 .heading { 73 110 color: var(--color-primary); ··· 105 142 .icon { 106 143 height: 1em; 107 144 width: 1em; 108 - margin: auto 0.2em; 145 + margin: auto var(--s-3); 109 146 } 110 147 111 148 .cursor::before { ··· 119 156 display: flex; 120 157 flex-wrap: wrap; 121 158 font-family: var(--font-mono); 122 - row-gap: 0.25em; 159 + row-gap: var(--s-3); 123 160 bottom: 0; 124 161 right: 1ch; 125 162 left: 1ch; 126 - padding-bottom: 0.5em; 163 + padding-bottom: var(--s-2); 127 164 background: var(--color-bg); 128 165 129 166 .prompt-pointed-right { ··· 230 267 231 268 a { 232 269 padding: 0 1ch; 233 - border-radius: 0.25em; 270 + border-radius: var(--s-3); 234 271 color: var(--color-fg); 235 272 236 273 &:visited { ··· 242 279 */ 243 280 &.active::before { 244 281 view-transition-name: nav-active-bg; 245 - border-radius: 0.25em; 282 + border-radius: var(--s-3); 246 283 content: ""; 247 284 position: absolute; 248 285 top: 0; ··· 289 326 290 327 /* I wanted to avoid breakpoints, but I can't figure out a way around this one */ 291 328 @media (max-width: 700px) { 292 - main { padding-bottom: 4em } 329 + main { padding-bottom: var(--s3) } 293 330 294 331 #navbar { 295 332 #nav-chevron { display: inherit }
+6
deno.json
··· 1 + { 2 + "imports": { 3 + "@std/assert": "jsr:@std/assert@1", 4 + "shiki": "npm:shiki@^3.21.0" 5 + } 6 + }
+280
deno.lock
··· 1 + { 2 + "version": "5", 3 + "specifiers": { 4 + "jsr:@std/assert@1": "1.0.16", 5 + "jsr:@std/internal@^1.0.12": "1.0.12", 6 + "npm:shiki@^3.21.0": "3.21.0" 7 + }, 8 + "jsr": { 9 + "@std/assert@1.0.16": { 10 + "integrity": "6a7272ed1eaa77defe76e5ff63ca705d9c495077e2d5fd0126d2b53fc5bd6532", 11 + "dependencies": [ 12 + "jsr:@std/internal" 13 + ] 14 + }, 15 + "@std/internal@1.0.12": { 16 + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" 17 + } 18 + }, 19 + "npm": { 20 + "@shikijs/core@3.21.0": { 21 + "integrity": "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==", 22 + "dependencies": [ 23 + "@shikijs/types", 24 + "@shikijs/vscode-textmate", 25 + "@types/hast", 26 + "hast-util-to-html" 27 + ] 28 + }, 29 + "@shikijs/engine-javascript@3.21.0": { 30 + "integrity": "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ==", 31 + "dependencies": [ 32 + "@shikijs/types", 33 + "@shikijs/vscode-textmate", 34 + "oniguruma-to-es" 35 + ] 36 + }, 37 + "@shikijs/engine-oniguruma@3.21.0": { 38 + "integrity": "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==", 39 + "dependencies": [ 40 + "@shikijs/types", 41 + "@shikijs/vscode-textmate" 42 + ] 43 + }, 44 + "@shikijs/langs@3.21.0": { 45 + "integrity": "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==", 46 + "dependencies": [ 47 + "@shikijs/types" 48 + ] 49 + }, 50 + "@shikijs/themes@3.21.0": { 51 + "integrity": "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==", 52 + "dependencies": [ 53 + "@shikijs/types" 54 + ] 55 + }, 56 + "@shikijs/types@3.21.0": { 57 + "integrity": "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==", 58 + "dependencies": [ 59 + "@shikijs/vscode-textmate", 60 + "@types/hast" 61 + ] 62 + }, 63 + "@shikijs/vscode-textmate@10.0.2": { 64 + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==" 65 + }, 66 + "@types/hast@3.0.4": { 67 + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", 68 + "dependencies": [ 69 + "@types/unist" 70 + ] 71 + }, 72 + "@types/mdast@4.0.4": { 73 + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", 74 + "dependencies": [ 75 + "@types/unist" 76 + ] 77 + }, 78 + "@types/unist@3.0.3": { 79 + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" 80 + }, 81 + "@ungap/structured-clone@1.3.0": { 82 + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" 83 + }, 84 + "ccount@2.0.1": { 85 + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" 86 + }, 87 + "character-entities-html4@2.1.0": { 88 + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==" 89 + }, 90 + "character-entities-legacy@3.0.0": { 91 + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==" 92 + }, 93 + "comma-separated-tokens@2.0.3": { 94 + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" 95 + }, 96 + "dequal@2.0.3": { 97 + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" 98 + }, 99 + "devlop@1.1.0": { 100 + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", 101 + "dependencies": [ 102 + "dequal" 103 + ] 104 + }, 105 + "hast-util-to-html@9.0.5": { 106 + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", 107 + "dependencies": [ 108 + "@types/hast", 109 + "@types/unist", 110 + "ccount", 111 + "comma-separated-tokens", 112 + "hast-util-whitespace", 113 + "html-void-elements", 114 + "mdast-util-to-hast", 115 + "property-information", 116 + "space-separated-tokens", 117 + "stringify-entities", 118 + "zwitch" 119 + ] 120 + }, 121 + "hast-util-whitespace@3.0.0": { 122 + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", 123 + "dependencies": [ 124 + "@types/hast" 125 + ] 126 + }, 127 + "html-void-elements@3.0.0": { 128 + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==" 129 + }, 130 + "mdast-util-to-hast@13.2.1": { 131 + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", 132 + "dependencies": [ 133 + "@types/hast", 134 + "@types/mdast", 135 + "@ungap/structured-clone", 136 + "devlop", 137 + "micromark-util-sanitize-uri", 138 + "trim-lines", 139 + "unist-util-position", 140 + "unist-util-visit", 141 + "vfile" 142 + ] 143 + }, 144 + "micromark-util-character@2.1.1": { 145 + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", 146 + "dependencies": [ 147 + "micromark-util-symbol", 148 + "micromark-util-types" 149 + ] 150 + }, 151 + "micromark-util-encode@2.0.1": { 152 + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==" 153 + }, 154 + "micromark-util-sanitize-uri@2.0.1": { 155 + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", 156 + "dependencies": [ 157 + "micromark-util-character", 158 + "micromark-util-encode", 159 + "micromark-util-symbol" 160 + ] 161 + }, 162 + "micromark-util-symbol@2.0.1": { 163 + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==" 164 + }, 165 + "micromark-util-types@2.0.2": { 166 + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==" 167 + }, 168 + "oniguruma-parser@0.12.1": { 169 + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==" 170 + }, 171 + "oniguruma-to-es@4.3.4": { 172 + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", 173 + "dependencies": [ 174 + "oniguruma-parser", 175 + "regex", 176 + "regex-recursion" 177 + ] 178 + }, 179 + "property-information@7.1.0": { 180 + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" 181 + }, 182 + "regex-recursion@6.0.2": { 183 + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", 184 + "dependencies": [ 185 + "regex-utilities" 186 + ] 187 + }, 188 + "regex-utilities@2.3.0": { 189 + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==" 190 + }, 191 + "regex@6.1.0": { 192 + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", 193 + "dependencies": [ 194 + "regex-utilities" 195 + ] 196 + }, 197 + "shiki@3.21.0": { 198 + "integrity": "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w==", 199 + "dependencies": [ 200 + "@shikijs/core", 201 + "@shikijs/engine-javascript", 202 + "@shikijs/engine-oniguruma", 203 + "@shikijs/langs", 204 + "@shikijs/themes", 205 + "@shikijs/types", 206 + "@shikijs/vscode-textmate", 207 + "@types/hast" 208 + ] 209 + }, 210 + "space-separated-tokens@2.0.2": { 211 + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" 212 + }, 213 + "stringify-entities@4.0.4": { 214 + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", 215 + "dependencies": [ 216 + "character-entities-html4", 217 + "character-entities-legacy" 218 + ] 219 + }, 220 + "trim-lines@3.0.1": { 221 + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" 222 + }, 223 + "unist-util-is@6.0.1": { 224 + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", 225 + "dependencies": [ 226 + "@types/unist" 227 + ] 228 + }, 229 + "unist-util-position@5.0.0": { 230 + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", 231 + "dependencies": [ 232 + "@types/unist" 233 + ] 234 + }, 235 + "unist-util-stringify-position@4.0.0": { 236 + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", 237 + "dependencies": [ 238 + "@types/unist" 239 + ] 240 + }, 241 + "unist-util-visit-parents@6.0.2": { 242 + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", 243 + "dependencies": [ 244 + "@types/unist", 245 + "unist-util-is" 246 + ] 247 + }, 248 + "unist-util-visit@5.0.0": { 249 + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", 250 + "dependencies": [ 251 + "@types/unist", 252 + "unist-util-is", 253 + "unist-util-visit-parents" 254 + ] 255 + }, 256 + "vfile-message@4.0.3": { 257 + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", 258 + "dependencies": [ 259 + "@types/unist", 260 + "unist-util-stringify-position" 261 + ] 262 + }, 263 + "vfile@6.0.3": { 264 + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", 265 + "dependencies": [ 266 + "@types/unist", 267 + "vfile-message" 268 + ] 269 + }, 270 + "zwitch@2.0.4": { 271 + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" 272 + } 273 + }, 274 + "workspace": { 275 + "dependencies": [ 276 + "jsr:@std/assert@1", 277 + "npm:shiki@^3.21.0" 278 + ] 279 + } 280 + }
+12
gleam.toml
··· 1 1 name = "webbed_site" 2 2 version = "1.0.0" 3 3 4 + target= "javascript" 5 + 6 + [javascript] 7 + runtime = "deno" 8 + 9 + [javascript.deno] 10 + allow_env = true 11 + allow_read = true 12 + allow_write = true 13 + allow_run = true 14 + 4 15 # Fill out these fields if you intend to generate HTML documentation or publish 5 16 # your project to the Hex package manager. 6 17 # ··· 22 33 shellout = ">= 1.7.0 and < 2.0.0" 23 34 jot = ">= 8.0.0 and < 9.0.0" 24 35 tom = ">= 2.0.0 and < 3.0.0" 36 + gleam_javascript = ">= 1.0.0 and < 2.0.0"
+2
manifest.toml
··· 7 7 { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 8 8 { name = "gleam_crypto", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "50774BAFFF1144E7872814C566C5D653D83A3EBF23ACC3156B757A1B6819086E" }, 9 9 { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, 10 + { name = "gleam_javascript", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "EF6C77A506F026C6FB37941889477CD5E4234FCD4337FF0E9384E297CB8F97EB" }, 10 11 { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, 11 12 { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, 12 13 { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, ··· 24 25 ] 25 26 26 27 [requirements] 28 + gleam_javascript = { version = ">= 1.0.0 and < 2.0.0" } 27 29 gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 28 30 gleam_time = { version = ">= 1.6.0 and < 2.0.0" } 29 31 jot = { version = ">= 8.0.0 and < 9.0.0" }
+6
posts/2026-01-03-initial-commit.djot
··· 5 5 # initial commit 6 6 7 7 This post is gonna detail how I made this site, but for now it's just a placeholder. 8 + 9 + ```css 10 + main { 11 + font-family: Myna; 12 + } 13 + ```
+13
src/highlight.gleam
··· 1 + import gleam/javascript/promise.{type Promise} 2 + 3 + pub type Highlighter 4 + 5 + @external(javascript, "./highlight_ffi.mjs", "createHighlighter") 6 + pub fn highlighter(languages: List(String)) -> Promise(Highlighter) 7 + 8 + @external(javascript, "./highlight_ffi.mjs", "toHtml") 9 + pub fn to_html( 10 + code code: String, 11 + language language: String, 12 + with highlighter: Highlighter, 13 + ) -> String
+19
src/highlight_ffi.mjs
··· 1 + import { createHighlighter as createShiki } from "shiki"; 2 + 3 + const themes = { 4 + dark: 'kanagawa-wave', 5 + light: 'kanagawa-lotus' 6 + }; 7 + 8 + // Not handling errors for now, I'm perfectly content 9 + // if my static site generator just crashes. 10 + export async function createHighlighter(languages) { 11 + return createShiki({ 12 + langs: languages.toArray(), 13 + themes: Object.values(themes), 14 + }); 15 + } 16 + 17 + export function toHtml(code, lang, highlighter) { 18 + return highlighter.codeToHtml(code, { lang: lang, themes: themes }); 19 + }
+15 -3
src/meta.gleam
··· 1 + import gleam/javascript/promise.{type Promise} 1 2 import gleam/result 3 + import highlight.{type Highlighter} 2 4 import shellout 3 5 4 6 pub type SiteMeta { 5 - SiteMeta(commit_hash: String, gleam_version: String) 7 + SiteMeta(commit_hash: String, gleam_version: String, highlighter: Highlighter) 6 8 } 7 9 8 - pub fn fetch() -> Result(SiteMeta, String) { 10 + pub fn fetch() -> Promise(Result(SiteMeta, String)) { 11 + use highlighter <- promise.map(highlight.highlighter(highlight_languages)) 9 12 use commit_hash <- result.try(commit_hash()) 10 13 use gleam_version <- result.map(gleam_version()) 11 - SiteMeta(commit_hash:, gleam_version:) 14 + SiteMeta(commit_hash:, gleam_version:, highlighter:) 12 15 } 13 16 14 17 fn commit_hash() -> Result(String, String) { ··· 24 27 Error(error) -> Error("Failed to fetch gleam version: " <> error.1) 25 28 } 26 29 } 30 + 31 + // We could compute these from the pages, but I don't think it's worth it... 32 + const highlight_languages = [ 33 + "gleam", 34 + "javascript", 35 + "css", 36 + "html", 37 + "toml", 38 + ]
+12 -7
src/page.gleam
··· 4 4 import gleam/list 5 5 import gleam/option.{None, Some} 6 6 import gleam/string 7 + import highlight 7 8 import jot 8 9 import lustre/attribute.{attribute} 9 10 import lustre/element.{type Element} ··· 85 86 } 86 87 87 88 case page { 88 - Index -> "index.djot" |> read_page |> djot.render(renderer()) 89 - Dots -> "dots.djot" |> read_page |> djot.render(renderer()) 89 + Index -> "index.djot" |> read_page |> djot.render(renderer(info.meta)) 90 + Dots -> "dots.djot" |> read_page |> djot.render(renderer(info.meta)) 90 91 91 92 Blog -> blog.list_posts(info.posts) 92 - BlogPost(post) -> djot.render(post.content, renderer()) 93 + BlogPost(post) -> djot.render(post.content, renderer(info.meta)) 93 94 } 94 95 } 95 96 ··· 171 172 ]) 172 173 } 173 174 174 - pub fn renderer() -> djot.Renderer(Element(msg)) { 175 + pub fn renderer(meta: SiteMeta) -> djot.Renderer(Element(msg)) { 175 176 let to_attributes = fn(attrs) { 176 177 use attrs, key, val <- dict.fold(attrs, []) 177 178 [attribute(key, val), ..attrs] ··· 180 181 djot.Renderer( 181 182 codeblock: fn(attrs, lang, code) { 182 183 let lang = option.unwrap(lang, "text") 183 - html.pre(to_attributes(attrs), [ 184 - html.code([attribute("data-lang", lang)], [html.text(code)]), 185 - ]) 184 + let html = highlight.to_html(code, lang, meta.highlighter) 185 + element.unsafe_raw_html( 186 + "", 187 + "div", 188 + [attribute.class("code"), ..to_attributes(attrs)], 189 + html, 190 + ) 186 191 }, 187 192 emphasis: fn(content) { html.em([], content) }, 188 193 heading: fn(attrs, level, content) {
+3 -1
src/webbed_site.gleam
··· 1 1 import blog 2 2 import gleam/dict 3 + import gleam/javascript/promise 3 4 import gleam/list 4 5 import lustre/ssg 5 6 import meta 6 7 import page 7 8 8 9 pub fn main() { 9 - let assert Ok(meta) = meta.fetch() as "Failed to fetch site meta" 10 + use meta <- promise.map(meta.fetch()) 11 + let assert Ok(meta) = meta as "Failed to fetch site meta" 10 12 let posts = blog.posts() 11 13 let info = page.SiteInfo(posts:, meta:) 12 14