astro.config.mjs (3085B)
1 // @ts-check 2 import { defineConfig } from 'astro/config'; 3 import mdx from '@astrojs/mdx'; 4 import sitemap from '@astrojs/sitemap'; 5 6 /** 7 * DÆMONBins deploys to Vercel at the domain root — no base path — so every 8 * internal link resolves from `/`. `src/lib/url.ts`'s `url()` helper stays the 9 * single chokepoint for internal links (with an empty base it is a passthrough 10 * that just guarantees a leading slash), which keeps the components portable if 11 * the site is ever moved back under a sub-path. 12 * 13 * `site` is used for the sitemap, canonical URLs and Open Graph tags. Set it to 14 * the site's real production origin — the Vercel custom domain (or the default 15 * `*.vercel.app` URL) once known. 16 */ 17 const SITE = 'https://lotl.daemon-sec.xyz'; 18 19 /** 20 * The drop cap, matching the cheatsheet/main site: the opening letter of a 21 * prose block floated three lines deep in EB Garamond, split into a real 22 * `<span class="cap">` so it stays selectable and searchable. 23 */ 24 const CAP_MIN_CHARS = 80; 25 function rehypeDropCap() { 26 /** @param {any} node hast node */ 27 function textLength(node) { 28 if (node.type === 'text') return node.value.trim().length; 29 if (!node.children) return 0; 30 return node.children.reduce((/** @type {number} */ n, /** @type {any} */ c) => n + textLength(c), 0); 31 } 32 /** 33 * @param {any} node hast node 34 * @returns {any} the first non-blank hast text node, or null 35 */ 36 function firstText(node) { 37 if (!node.children) return null; 38 for (const child of node.children) { 39 if (child.type === 'text' && child.value.trim()) return child; 40 if (child.type === 'element') { 41 /** @type {any} */ 42 const found = firstText(child); 43 if (found) return found; 44 } 45 } 46 return null; 47 } 48 return (/** @type {any} */ tree) => { 49 const paragraph = tree.children.find( 50 (/** @type {any} */ n) => n.type === 'element' && n.tagName === 'p' && textLength(n) >= CAP_MIN_CHARS, 51 ); 52 if (!paragraph) return; 53 const lead = firstText(paragraph); 54 if (!lead) return; 55 const trimmed = lead.value.trimStart(); 56 const letter = trimmed[0]; 57 if (!letter || !/[A-Za-z0-9]/.test(letter)) return; 58 lead.value = trimmed.slice(1); 59 paragraph.children.unshift({ 60 type: 'element', 61 tagName: 'span', 62 properties: { className: ['cap'] }, 63 children: [{ type: 'text', value: letter }], 64 }); 65 }; 66 } 67 68 export default defineConfig({ 69 site: SITE, 70 // 'never' + the default directory output: URLs (sitemap, Astro.url, canonical) 71 // carry no trailing slash — matching vercel.json's `trailingSlash: false` so 72 // no canonical ever points at a 308 — while files still land as 73 // …/index.html, which keeps Pagefind's result hrefs clean. 74 trailingSlash: 'never', 75 integrations: [mdx(), sitemap()], 76 markdown: { 77 rehypePlugins: [rehypeDropCap], 78 shikiConfig: { 79 // One theme in both modes — code blocks are dark plates everywhere, so 80 // there is no light listing to flip to (see prose.css). 81 theme: 'rose-pine-moon', 82 wrap: false, 83 }, 84 }, 85 });