diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6121075..c94a062 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,46 @@ jobs: permissions: *read-permissions secrets: inherit + preview: + if: ${{ github.event_name == 'pull_request' }} + name: Preview + needs: [build] + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + environment: + name: surge-preview + url: ${{ steps.deploy.outputs.preview-url }} + outputs: + preview-url: ${{ steps.deploy.outputs.preview-url }} + steps: + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: website-build + + - name: Extract artifact + run: | + set -eo pipefail + tar -xzf website.tar.gz + + - name: Install Surge + run: npm install --global surge + + - name: Deploy to Surge + id: deploy + run: | + surge ./dist --preview --domain ${{ vars.SURGE_DOMAIN }} --token ${{ secrets.SURGE_TOKEN }} | tee output.txt + PREVIEW_DOMAIN=$(grep "Live preview" output.txt | grep -oE '[0-9]+-[^[:space:]]+\.surge\.sh') + echo "preview-url=https://$PREVIEW_DOMAIN" >> "$GITHUB_OUTPUT" + + - uses: marocchino/sticky-pull-request-comment@v3 + name: Sticky pull request comment + with: + message: | + Release ${{ github.sha }} to <${{ steps.deploy.outputs.preview-url }}> + status: name: Status runs-on: ubuntu-latest diff --git a/Justfile b/Justfile index 0a41be6..1754121 100644 --- a/Justfile +++ b/Justfile @@ -15,7 +15,7 @@ audit: pnpm audit dev: - pnpm dev + pnpm dev --force build: pnpm build diff --git a/astro.config.mjs b/astro.config.mjs index ccdddf0..94f2625 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,37 +1,41 @@ import mdx from "@astrojs/mdx"; import svelte from "@astrojs/svelte"; -import { - transformerNotationDiff, - transformerNotationErrorLevel, - transformerNotationFocus, - transformerNotationHighlight, - transformerNotationWordHighlight, -} from "@shikijs/transformers"; +import astroExpressiveCode from "astro-expressive-code"; import pagefind from "astro-pagefind"; import { defineConfig } from "astro/config"; +import expressiveConfig from "./plugins/expressive-code/config.ts"; import { rehypeExternalLinks, rehypeHeadingAnchors } from "./plugins/rehype.mjs"; import { remarkReadingTime } from "./plugins/remark.mjs"; -import { transformerCopyButton } from "./plugins/shiki.mjs"; + +function restartOnEcPluginChange() { + return { + name: "restart-on-ec-plugin-change", + configureServer(server) { + server.watcher.add("./plugins/expressive-code/**"); + server.watcher.on("change", (path) => { + if (path.includes("plugins/expressive-code")) { + console.log(`\n[expressive-code] Plugin changed: ${path}, restarting...`); + server.restart(); + } + }); + }, + }; +} export default defineConfig({ site: "https://troymoder.dev", - integrations: [mdx(), svelte(), pagefind()], + integrations: [ + astroExpressiveCode(expressiveConfig), + mdx(), + svelte(), + pagefind(), + ], markdown: { - shikiConfig: { - theme: "github-light", - transformers: [ - transformerNotationDiff(), - transformerNotationHighlight(), - transformerNotationFocus(), - transformerNotationWordHighlight(), - transformerNotationErrorLevel(), - transformerCopyButton(), - ], - }, remarkPlugins: [remarkReadingTime], rehypePlugins: [rehypeHeadingAnchors, rehypeExternalLinks], }, vite: { + plugins: [restartOnEcPluginChange()], server: { watch: { ignored: ["**/.direnv/**"], diff --git a/eslint.config.js b/eslint.config.js index 5f112d2..47ee7fa 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -18,6 +18,14 @@ export default defineConfig([ }, }, }, + { + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], + }, + }, { files: ["**/*.svelte"], languageOptions: { diff --git a/package.json b/package.json index c7768d8..a8ca814 100644 --- a/package.json +++ b/package.json @@ -10,21 +10,23 @@ }, "devDependencies": { "@astrojs/check": "^0.9.0", - "@astrojs/mdx": "^4.0.0", - "@astrojs/rss": "^4.0.15", - "@astrojs/svelte": "^7.2.5", + "@astrojs/mdx": "^5.0.0", + "@astrojs/rss": "4.0.15-beta.4", + "@astrojs/svelte": "^8.0.0", "@eslint/js": "^10.0.1", "@fontsource/jetbrains-mono": "^5.2.8", - "@shikijs/transformers": "^4.0.1", + "@types/hast": "^3.0.4", "@types/markdown-it": "^14.1.2", "@types/node": "^25.3.5", "@types/sanitize-html": "^2.16.1", - "astro": "^5.0.0", + "astro": "^6.0.4", + "astro-expressive-code": "^0.41.7", "astro-pagefind": "^1.8.5", "eslint": "^10.0.3", "eslint-plugin-astro": "^1.3.1", "eslint-plugin-svelte": "^3.9.0", "globals": "^17.4.0", + "hast-util-from-html": "^2.0.3", "markdown-it": "^14.1.1", "mdast-util-to-string": "^4.0.0", "reading-time": "^1.5.0", @@ -36,6 +38,7 @@ "typescript": "^5.7.0", "typescript-eslint": "^8.34.0", "unist-util-visit": "^5.1.0", - "vfile": "^6.0.3" + "vfile": "^6.0.3", + "vite": "^8.0.0" } } diff --git a/plugins/expressive-code/anchors.client.js b/plugins/expressive-code/anchors.client.js new file mode 100644 index 0000000..11dac21 --- /dev/null +++ b/plugins/expressive-code/anchors.client.js @@ -0,0 +1,244 @@ +(() => { + function parseAnchorHash(hash) { + const decoded = decodeURIComponent(hash); + const colonIdx = decoded.indexOf(":"); + if (colonIdx === -1) return null; + + const blockId = decoded.slice(0, colonIdx); + let range = decoded.slice(colonIdx + 1); + + let diffMode = null; + const diffSuffix = range.match(/!(\w+)$/); + if (diffSuffix) { + diffMode = diffSuffix[1]; + range = range.slice(0, -diffSuffix[0].length); + } + + const regexMatch = range.match(/^(\d+)\/(.+)\/$/); + if (regexMatch) { + const line = parseInt(regexMatch[1], 10); + return { + blockId, + startLine: line, + endLine: line, + pattern: regexMatch[2], + diffMode, + }; + } + + const lineRangeMatch = range.match(/^(\d+)-(\d+)$/); + if (lineRangeMatch) { + return { + blockId, + startLine: parseInt(lineRangeMatch[1], 10), + endLine: parseInt(lineRangeMatch[2], 10), + pattern: null, + diffMode, + }; + } + + const singleLineMatch = range.match(/^(\d+)$/); + if (singleLineMatch) { + const line = parseInt(singleLineMatch[1], 10); + return { blockId, startLine: line, endLine: line, pattern: null, diffMode }; + } + + return null; + } + + function clearHighlights() { + document.querySelectorAll(".ec-line[data-anchor-highlight]").forEach(el => { + el.removeAttribute("data-anchor-highlight"); + }); + document.querySelectorAll(".anchor-highlight-overlay").forEach(el => { + el.remove(); + }); + } + + function expandFoldedRegions(block, startLine, endLine) { + for (let i = startLine; i <= endLine; i++) { + const line = block.querySelector(`.ec-line[data-line-num="${i - 1}"]`); + if (!line || !line.hasAttribute("data-folded")) continue; + + let parent = line.previousElementSibling; + while (parent) { + if (parent.hasAttribute("data-collapsed")) { + const foldStart = parseInt(parent.dataset.foldStart, 10); + const foldEnd = parseInt(parent.dataset.foldEnd, 10); + + if (!isNaN(foldStart) && !isNaN(foldEnd) && i - 1 > foldStart && i - 1 <= foldEnd) { + parent.removeAttribute("data-collapsed"); + + for (let j = foldStart + 1; j <= foldEnd; j++) { + const foldedLine = block.querySelector(`.ec-line[data-line-num="${j}"]`); + if (foldedLine && !foldedLine.hasAttribute("data-collapsed")) { + foldedLine.removeAttribute("data-folded"); + } + } + break; + } + } + parent = parent.previousElementSibling; + } + } + } + + function getCodeText(node) { + let text = ""; + for (const child of node.childNodes) { + if (child.nodeType === Node.TEXT_NODE) { + text += child.textContent; + } else if (child.nodeType === Node.ELEMENT_NODE) { + if (child.hasAttribute("data-no-copy")) { + continue; + } + text += getCodeText(child); + } + } + return text; + } + + function findTextMatch(codeDiv, pattern) { + const text = getCodeText(codeDiv); + const regex = new RegExp(pattern); + const match = text.match(regex); + if (!match) return null; + + const startIdx = match.index; + const endIdx = startIdx + match[0].length; + return { startIdx, endIdx }; + } + + function findTextPosition(codeDiv, charIndex) { + let currentChar = 0; + + function walk(node) { + if (node.nodeType === Node.TEXT_NODE) { + const len = node.textContent.length; + if (currentChar + len >= charIndex) { + return { node, offset: charIndex - currentChar }; + } + currentChar += len; + } else if (node.nodeType === Node.ELEMENT_NODE) { + if (node.hasAttribute("data-no-copy")) { + return null; + } + for (const child of node.childNodes) { + const result = walk(child); + if (result) return result; + } + } + return null; + } + + return walk(codeDiv); + } + + function createOverlay(codeDiv, startIdx, endIdx) { + const startPos = findTextPosition(codeDiv, startIdx); + const endPos = findTextPosition(codeDiv, endIdx); + + if (!startPos || !endPos) return null; + + const range = document.createRange(); + range.setStart(startPos.node, startPos.offset); + range.setEnd(endPos.node, endPos.offset); + + const rects = range.getClientRects(); + if (rects.length === 0) return null; + + const codeDivRect = codeDiv.getBoundingClientRect(); + const firstRect = rects[0]; + const lastRect = rects[rects.length - 1]; + + const left = firstRect.left - codeDivRect.left; + const width = (lastRect.right - codeDivRect.left) - left; + + const overlay = document.createElement("span"); + overlay.className = "anchor-highlight-overlay"; + overlay.style.left = `${left}px`; + overlay.style.width = `${width}px`; + codeDiv.appendChild(overlay); + return overlay; + } + + function switchToTabIfNeeded(block) { + const tabsContent = block.closest(".tabs-content"); + if (!tabsContent) return; + + const fileTabs = tabsContent.closest(".file-tabs"); + if (!fileTabs) return; + + const codeBlocks = tabsContent.querySelectorAll(".expressive-code"); + const index = Array.from(codeBlocks).indexOf(block.closest(".expressive-code")); + if (index === -1) return; + + const button = fileTabs.querySelector(`button[data-tab-index="${index}"]`); + if (!button) return; + + button.click(); + } + + function setDiffMode(block, diffMode) { + if (!diffMode) return; + const diffBlock = block.querySelector(".diff-block"); + if (!diffBlock) return; + if (diffMode === "diff" || diffMode === "plain") { + diffBlock.dataset.diffMode = diffMode; + } + } + + function highlightRange(parsed) { + const { blockId, startLine, endLine, pattern, diffMode } = parsed; + const block = document.getElementById(blockId); + if (!block) return false; + + switchToTabIfNeeded(block); + setDiffMode(block, diffMode); + expandFoldedRegions(block, startLine, endLine); + + window.requestAnimationFrame(() => { + for (let i = startLine; i <= endLine; i++) { + const line = block.querySelector(`.ec-line[data-line-num="${i - 1}"]`); + if (!line) continue; + + const codeDiv = line.querySelector(".code"); + if (!codeDiv) continue; + + if (pattern) { + const match = findTextMatch(codeDiv, pattern); + if (match) { + line.setAttribute("data-anchor-highlight", "partial"); + createOverlay(codeDiv, match.startIdx, match.endIdx); + } + } else { + line.setAttribute("data-anchor-highlight", "full"); + } + } + + const target = startLine > 0 ? block.querySelector(`.ec-line[data-line-num="${startLine - 1}"]`) : block; + if (target) { + target.scrollIntoView({ behavior: "smooth", block: "nearest" }); + } + }); + } + + function handleLinkClick(e) { + const link = e.target.closest("a[href^='#']"); + if (!link) return; + + const hash = link.getAttribute("href").slice(1); + const parsed = parseAnchorHash(hash); + if (!parsed) return; + + e.preventDefault(); + + const block = document.getElementById(parsed.blockId); + if (!block) return; + + clearHighlights(); + window.requestAnimationFrame(() => highlightRange(parsed)); + } + + document.addEventListener("click", handleLinkClick); +})(); diff --git a/plugins/expressive-code/anchors.ts b/plugins/expressive-code/anchors.ts new file mode 100644 index 0000000..bcd36ed --- /dev/null +++ b/plugins/expressive-code/anchors.ts @@ -0,0 +1,115 @@ +import { type ExpressiveCodePlugin, PluginStyleSettings } from "astro-expressive-code"; +import anchorsClient from "./anchors.client.js?raw"; +import { getSection, pluginBlockConfigData } from "./block-config.ts"; + +declare module "astro-expressive-code" { + export interface StyleSettings { + anchors: AnchorsStyleSettings; + } +} + +interface AnchorsStyleSettings { + highlightBackgroundStart: string; + highlightBackgroundMid: string; +} + +export const anchorsStyleSettings = new PluginStyleSettings({ + defaultValues: { + anchors: { + highlightBackgroundStart: "rgba(255, 213, 0, 0.3)", + highlightBackgroundMid: "rgba(255, 213, 0, 0.15)", + }, + }, +}); + +interface AnchorConfig { + blockId?: string; + lineAnchors: Map; +} + +function parseAnchorSection(lines: string[]): AnchorConfig { + const config: AnchorConfig = { lineAnchors: new Map() }; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + + const match = trimmed.match(/^(\d+):\s*(.+)$/); + if (match) { + const lineNum = parseInt(match[1]!, 10) - 1; + const anchorName = match[2]!.trim(); + config.lineAnchors.set(lineNum, anchorName); + } else if (trimmed.startsWith("id:")) { + config.blockId = trimmed.slice(3).trim(); + } else if (!trimmed.includes(":")) { + config.blockId = trimmed; + } + } + + return config; +} + +export function pluginAnchors(): ExpressiveCodePlugin { + return { + name: "anchors", + styleSettings: anchorsStyleSettings, + baseStyles: ({ cssVar }) => ` + .ec-line[data-anchor-highlight] { + position: relative; + } + + .ec-line[data-anchor-highlight="full"]::after { + content: ""; + inset: 0; + } + + .ec-line[data-anchor-highlight="full"]::after, + .anchor-highlight-overlay { + position: absolute; + top: 0; + bottom: 0; + animation: anchor-highlight-fade 3s ease-out forwards; + pointer-events: none; + z-index: 10; + } + + @keyframes anchor-highlight-fade { + 0% { background: transparent; } + 5% { background: ${cssVar("anchors.highlightBackgroundStart")}; } + 70% { background: ${cssVar("anchors.highlightBackgroundMid")}; } + 100% { background: transparent; } + } + `, + jsModules: [anchorsClient], + hooks: { + postprocessRenderedBlock: ({ codeBlock, renderData }) => { + const data = pluginBlockConfigData.getOrCreateFor(codeBlock); + const anchorLines = getSection(data.sections, "anchor"); + if (!anchorLines) return; + + const config = parseAnchorSection(anchorLines); + if (!config.blockId) return; + + renderData.blockAst.properties = renderData.blockAst.properties || {}; + renderData.blockAst.properties.id = config.blockId; + }, + postprocessRenderedLine: ({ codeBlock, lineIndex, renderData }) => { + const data = pluginBlockConfigData.getOrCreateFor(codeBlock); + const anchorLines = getSection(data.sections, "anchor"); + if (!anchorLines) return; + + const config = parseAnchorSection(anchorLines); + if (!config.blockId) return; + + const lineAst = renderData.lineAst; + lineAst.properties = lineAst.properties || {}; + lineAst.properties["data-line-num"] = String(lineIndex); + + const lineAnchor = config.lineAnchors.get(lineIndex); + if (lineAnchor) { + lineAst.properties.id = lineAnchor; + } + }, + }, + }; +} diff --git a/plugins/expressive-code/block-config.ts b/plugins/expressive-code/block-config.ts new file mode 100644 index 0000000..11d6acf --- /dev/null +++ b/plugins/expressive-code/block-config.ts @@ -0,0 +1,82 @@ +import { AttachedPluginData, type ExpressiveCodePlugin } from "astro-expressive-code"; + +export interface BlockConfigSection { + name: string; + lines: string[]; +} + +interface PluginBlockConfigData { + sections: BlockConfigSection[]; + configLineCount: number; +} + +export const pluginBlockConfigData = new AttachedPluginData( + () => ({ sections: [], configLineCount: 0 }), +); + +export function getSection(sections: BlockConfigSection[], name: string): string[] | undefined { + return sections.find(s => s.name === name)?.lines; +} + +function parseConfigBlock(lines: string[]): { sections: BlockConfigSection[]; configLineCount: number } { + const firstLine = lines[0]?.trim(); + if (!firstLine?.startsWith(":::")) { + return { sections: [], configLineCount: 0 }; + } + + const sections: BlockConfigSection[] = []; + let currentSection: string | null = null; + let currentContent: string[] = []; + let endIndex = -1; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]!; + const trimmed = line.trim(); + + if (trimmed === ":::") { + if (currentSection) { + sections.push({ name: currentSection, lines: currentContent }); + } + endIndex = i; + break; + } + + if (trimmed.startsWith("::: ")) { + if (currentSection) { + sections.push({ name: currentSection, lines: currentContent }); + } + currentSection = trimmed.slice(4).trim(); + currentContent = []; + } else if (currentSection) { + currentContent.push(trimmed); + } + } + + if (endIndex === -1) { + return { sections: [], configLineCount: 0 }; + } + + return { sections, configLineCount: endIndex + 1 }; +} + +export function pluginBlockConfig(): ExpressiveCodePlugin { + return { + name: "block-config", + hooks: { + preprocessCode: ({ codeBlock }) => { + const lines = codeBlock.getLines().map((line) => line.text); + const { sections, configLineCount } = parseConfigBlock(lines); + + if (configLineCount === 0) return; + + const data = pluginBlockConfigData.getOrCreateFor(codeBlock); + data.sections = sections; + data.configLineCount = configLineCount; + + for (let i = 0; i < configLineCount; i++) { + codeBlock.deleteLine(0); + } + }, + }, + }; +} diff --git a/plugins/expressive-code/callouts.ts b/plugins/expressive-code/callouts.ts new file mode 100644 index 0000000..1d7b0ab --- /dev/null +++ b/plugins/expressive-code/callouts.ts @@ -0,0 +1,281 @@ +import { type ExpressiveCodePlugin, PluginStyleSettings } from "astro-expressive-code"; +import type { Element, Text } from "hast"; +import { fromHtml } from "hast-util-from-html"; +import MarkdownIt from "markdown-it"; +import { getSection, pluginBlockConfigData } from "./block-config.ts"; + +declare module "astro-expressive-code" { + export interface StyleSettings { + callouts: CalloutsStyleSettings; + } +} + +interface CalloutsStyleSettings { + errorBackground: string; + errorBorder: string; + errorIcon: string; + errorUnderline: string; + warnBackground: string; + warnBorder: string; + warnIcon: string; + warnUnderline: string; + infoBackground: string; + infoBorder: string; + infoIcon: string; + okBackground: string; + okBorder: string; + okIcon: string; + noteBackground: string; + noteBorder: string; + noteIcon: string; + textColor: string; + codeBackground: string; + padding: string; + fontFamily: string; + fontSize: string; +} + +export const calloutsStyleSettings = new PluginStyleSettings({ + defaultValues: { + callouts: { + errorBackground: "#fef2f2", + errorBorder: "#fca5a5", + errorIcon: "'🚫'", + errorUnderline: "#dc2626", + warnBackground: "#fffbeb", + warnBorder: "#fcd34d", + warnIcon: "'⚠️'", + warnUnderline: "#d97706", + infoBackground: "#eff6ff", + infoBorder: "#93c5fd", + infoIcon: "'ℹ️'", + okBackground: "#f0fdf4", + okBorder: "#86efac", + okIcon: "'✅'", + noteBackground: "#f5f5f5", + noteBorder: "#d4d4d4", + noteIcon: "'📝'", + textColor: "#27272a", + codeBackground: "rgba(0, 0, 0, 0.08)", + padding: "0.5rem 0.75rem", + fontFamily: "system-ui, -apple-system, sans-serif", + fontSize: "0.8125rem", + }, + }, +}); + +const md = new MarkdownIt({ html: true }); + +type CalloutType = "error" | "warn" | "info" | "ok" | "note"; + +interface Callout { + line: number; + type: CalloutType; + message: string; + html: string; + colStart?: number | undefined; + colEnd?: number | undefined; +} + +function parseLogsSection(lines: string[]): Callout[] { + const callouts: Callout[] = []; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + + const match = trimmed.match(/^(error|warn|info|ok|note)@(\d+)(?:\[(\d+):(\d+)\])?:\s*(.+)$/); + if (!match) continue; + + const type = match[1] as CalloutType; + const lineNum = parseInt(match[2]!, 10) - 1; + const colStart = match[3] ? parseInt(match[3], 10) - 1 : undefined; + const colEnd = match[4] ? parseInt(match[4], 10) : undefined; + const message = match[5]!.trim(); + const html = md.renderInline(message); + + callouts.push({ line: lineNum, type, message, html, colStart, colEnd }); + } + + return callouts; +} + +function wrapColumnRange(node: Element, callout: Callout): void { + if (callout.colStart === undefined || callout.colEnd === undefined) return; + + let currentCol = 0; + const start = callout.colStart; + const end = callout.colEnd; + + function processChildren(children: (Element | Text)[]): (Element | Text)[] { + const result: (Element | Text)[] = []; + + for (const child of children) { + if (child.type === "text") { + const text = child.value; + const textStart = currentCol; + const textEnd = currentCol + text.length; + + if (textEnd <= start || textStart >= end) { + result.push(child); + currentCol = textEnd; + continue; + } + + const overlapStart = Math.max(start, textStart); + const overlapEnd = Math.min(end, textEnd); + + const beforeLen = overlapStart - textStart; + const afterStart = overlapEnd - textStart; + + if (beforeLen > 0) { + result.push({ type: "text", value: text.slice(0, beforeLen) }); + } + + const spanText = text.slice(beforeLen, afterStart); + const span: Element = { + type: "element", + tagName: "span", + properties: { className: ["callout-underline", `callout-underline-${callout.type}`] }, + children: [{ type: "text", value: spanText }], + }; + result.push(span); + + if (afterStart < text.length) { + result.push({ type: "text", value: text.slice(afterStart) }); + } + + currentCol = textEnd; + } else if (child.type === "element") { + child.children = processChildren(child.children as (Element | Text)[]); + result.push(child); + } + } + + return result; + } + + const codeDiv = node.children.find( + (child): child is Element => + child.type === "element" + && child.tagName === "div" + && Array.isArray(child.properties?.className) + && child.properties.className.includes("code"), + ); + + if (codeDiv) { + codeDiv.children = processChildren(codeDiv.children as (Element | Text)[]); + } +} + +export function pluginCallouts(): ExpressiveCodePlugin { + return { + name: "callouts", + styleSettings: calloutsStyleSettings, + baseStyles: ({ cssVar }) => ` + .ec-line { + &[data-callout-error], + &[data-callout-warn], + &[data-callout-info], + &[data-callout-ok], + &[data-callout-note] { + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: auto auto; + + > .gutter { grid-row: 1; grid-column: 1; } + > .code { grid-row: 1; grid-column: 2; } + > .line-callout { grid-row: 2; grid-column: 1 / -1; } + } + } + + .line-callout { + padding: ${cssVar("callouts.padding")}; + font-family: ${cssVar("callouts.fontFamily")}; + font-size: ${cssVar("callouts.fontSize")}; + line-height: 1.4; + color: ${cssVar("callouts.textColor")}; + border-left-width: 3px; + border-left-style: solid; + + &::before { margin-right: 0.5rem; } + + code { + padding: 0.125rem 0.375rem; + font-family: "JetBrains Mono", monospace; + font-size: 0.75rem; + background: ${cssVar("callouts.codeBackground")}; + border-radius: 3px; + } + } + + .line-callout-error { + background: ${cssVar("callouts.errorBackground")}; + border-left-color: ${cssVar("callouts.errorBorder")}; + &::before { content: ${cssVar("callouts.errorIcon")}; } + } + + .line-callout-warn { + background: ${cssVar("callouts.warnBackground")}; + border-left-color: ${cssVar("callouts.warnBorder")}; + &::before { content: ${cssVar("callouts.warnIcon")}; } + } + + .line-callout-info { + background: ${cssVar("callouts.infoBackground")}; + border-left-color: ${cssVar("callouts.infoBorder")}; + &::before { content: ${cssVar("callouts.infoIcon")}; } + } + + .line-callout-ok { + background: ${cssVar("callouts.okBackground")}; + border-left-color: ${cssVar("callouts.okBorder")}; + &::before { content: ${cssVar("callouts.okIcon")}; } + } + + .line-callout-note { + background: ${cssVar("callouts.noteBackground")}; + border-left-color: ${cssVar("callouts.noteBorder")}; + &::before { content: ${cssVar("callouts.noteIcon")}; } + } + + .callout-underline { + text-decoration: wavy underline; + text-decoration-skip-ink: none; + text-underline-offset: 2px; + } + + .callout-underline-error { text-decoration-color: ${cssVar("callouts.errorUnderline")}; } + .callout-underline-warn { text-decoration-color: ${cssVar("callouts.warnUnderline")}; } + `, + hooks: { + postprocessRenderedLine: ({ codeBlock, lineIndex, renderData }) => { + const data = pluginBlockConfigData.getOrCreateFor(codeBlock); + const logsLines = getSection(data.sections, "logs"); + if (!logsLines) return; + + const callouts = parseLogsSection(logsLines); + const lineCallouts = callouts.filter(c => c.line === lineIndex); + if (lineCallouts.length === 0) return; + + const lineAst = renderData.lineAst; + lineAst.properties = lineAst.properties || {}; + + for (const callout of lineCallouts) { + lineAst.properties[`data-callout-${callout.type}`] = ""; + wrapColumnRange(lineAst, callout); + + const htmlTree = fromHtml(callout.html, { fragment: true }); + const calloutEl: Element = { + type: "element", + tagName: "div", + properties: { className: ["line-callout", `line-callout-${callout.type}`] }, + children: htmlTree.children as Element[], + }; + + lineAst.children.push(calloutEl); + } + }, + }, + }; +} diff --git a/plugins/expressive-code/config.ts b/plugins/expressive-code/config.ts new file mode 100644 index 0000000..1939f21 --- /dev/null +++ b/plugins/expressive-code/config.ts @@ -0,0 +1,35 @@ +import { defineEcConfig } from "astro-expressive-code"; +import { pluginAnchors } from "./anchors.ts"; +import { pluginBlockConfig } from "./block-config.ts"; +import { pluginCallouts } from "./callouts.ts"; +import { pluginCopy } from "./copy.ts"; +import { pluginDiff } from "./diff.ts"; +import { pluginFolding } from "./folding.ts"; +import { pluginLineNumbers } from "./line-numbers.ts"; +import { pluginLinks } from "./links.ts"; +import { troyModerTheme } from "./theme.ts"; +import { pluginTooltips } from "./tooltips.ts"; + +export default defineEcConfig({ + themes: [troyModerTheme], + frames: false, + textMarkers: false, + plugins: [ + pluginBlockConfig(), + pluginLineNumbers(), + pluginLinks(), + pluginAnchors(), + pluginDiff(), + pluginTooltips(), + pluginFolding(), + pluginCallouts(), + pluginCopy(), + ], + styleOverrides: { + borderRadius: "5px", + borderWidth: "1px", + codeFontFamily: "'JetBrains Mono', monospace, serif", + codeFontSize: "1em", + codeLineHeight: "1.4", + }, +}); diff --git a/plugins/expressive-code/copy.client.js b/plugins/expressive-code/copy.client.js new file mode 100644 index 0000000..0907777 --- /dev/null +++ b/plugins/expressive-code/copy.client.js @@ -0,0 +1,44 @@ +(() => { + function getCodeText(container) { + const clone = container.cloneNode(true); + clone.querySelectorAll("[data-no-copy]").forEach((el) => el.remove()); + + const lines = []; + for (const line of clone.querySelectorAll(".ec-line")) { + const codeEl = line.querySelector(".code"); + if (codeEl) { + lines.push(codeEl.textContent?.replace(/\n$/, "") || ""); + } + } + return lines.join("\n"); + } + + async function handleCopyClick(e) { + e.preventDefault(); + const btn = e.currentTarget; + const container = btn.closest(".expressive-code"); + if (!container) return; + + try { + await navigator.clipboard.writeText(getCodeText(container)); + } catch { + return; + } + + const feedback = btn.parentNode?.querySelector(".copy-feedback"); + if (!feedback || feedback.classList.contains("show")) return; + + feedback.classList.add("show"); + setTimeout(() => feedback.classList.remove("show"), 1500); + } + + function init() { + for (const btn of document.querySelectorAll(".expressive-code .copy-button")) { + btn.removeEventListener("click", handleCopyClick); + btn.addEventListener("click", handleCopyClick); + } + } + + init(); + document.addEventListener("astro:page-load", init); +})(); diff --git a/plugins/expressive-code/copy.svg b/plugins/expressive-code/copy.svg new file mode 100644 index 0000000..7697291 --- /dev/null +++ b/plugins/expressive-code/copy.svg @@ -0,0 +1 @@ + diff --git a/plugins/expressive-code/copy.ts b/plugins/expressive-code/copy.ts new file mode 100644 index 0000000..259f595 --- /dev/null +++ b/plugins/expressive-code/copy.ts @@ -0,0 +1,148 @@ +import { type ExpressiveCodePlugin, PluginStyleSettings } from "astro-expressive-code"; +import type { Element } from "hast"; +import { fromHtml } from "hast-util-from-html"; +import { getSection, pluginBlockConfigData } from "./block-config.ts"; +import copyClient from "./copy.client.js?raw"; +import copyIcon from "./copy.svg?raw"; + +declare module "astro-expressive-code" { + export interface StyleSettings { + copy: CopyStyleSettings; + } +} + +interface CopyStyleSettings { + buttonBackground: string; + buttonBackgroundHover: string; + buttonBorder: string; + buttonBorderHover: string; + buttonColor: string; + buttonColorHover: string; + feedbackBackground: string; + feedbackColor: string; +} + +export const copyStyleSettings = new PluginStyleSettings({ + defaultValues: { + copy: { + buttonBackground: "#f5f5f5", + buttonBackgroundHover: "#e5e5e5", + buttonBorder: "#d4d4d4", + buttonBorderHover: "#a3a3a3", + buttonColor: "#737373", + buttonColorHover: "#404040", + feedbackBackground: "#22c55e", + feedbackColor: "#fff", + }, + }, +}); + +export function pluginCopy(): ExpressiveCodePlugin { + return { + name: "copy", + styleSettings: copyStyleSettings, + baseStyles: ({ cssVar }) => ` + .expressive-code { + position: relative; + } + + .expressive-code .copy-button-wrapper { + position: absolute; + top: 0.5rem; + right: 0.5rem; + z-index: 1; + opacity: 0; + transition: opacity 0.15s ease; + } + + .expressive-code:hover .copy-button-wrapper, + .expressive-code:focus-within .copy-button-wrapper { + opacity: 1; + } + + .expressive-code .copy-button { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + padding: 0; + border: 1px solid ${cssVar("copy.buttonBorder")}; + border-radius: 4px; + background: ${cssVar("copy.buttonBackground")}; + color: ${cssVar("copy.buttonColor")}; + cursor: pointer; + transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease; + } + + .expressive-code .copy-button:hover { + background: ${cssVar("copy.buttonBackgroundHover")}; + border-color: ${cssVar("copy.buttonBorderHover")}; + color: ${cssVar("copy.buttonColorHover")}; + } + + .expressive-code .copy-button svg { + width: 1rem; + height: 1rem; + pointer-events: none; + } + + .expressive-code .copy-feedback { + position: absolute; + top: 0; + right: 2.5rem; + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + background: ${cssVar("copy.feedbackBackground")}; + color: ${cssVar("copy.feedbackColor")}; + border-radius: 4px; + white-space: nowrap; + opacity: 0; + transform: translateX(0.5rem); + transition: opacity 0.15s ease, transform 0.15s ease; + pointer-events: none; + } + + .expressive-code .copy-feedback.show { + opacity: 1; + transform: translateX(0); + } + `, + jsModules: [copyClient], + hooks: { + postprocessRenderedBlock: ({ codeBlock, renderData }) => { + const data = pluginBlockConfigData.getOrCreateFor(codeBlock); + if (getSection(data.sections, "no-copy")) return; + + const button: Element = { + type: "element", + tagName: "button", + properties: { + className: ["copy-button"], + type: "button", + title: "Copy code", + "aria-label": "Copy code to clipboard", + }, + children: fromHtml(copyIcon).children as Element[], + }; + + const feedback: Element = { + type: "element", + tagName: "div", + properties: { className: ["copy-feedback"] }, + children: [{ type: "text", value: "Copied!" }], + }; + + const wrapper: Element = { + type: "element", + tagName: "div", + properties: { className: ["copy-button-wrapper"] }, + children: [button, feedback], + }; + + const figureAst = renderData.blockAst; + figureAst.children.push(wrapper); + }, + }, + }; +} diff --git a/plugins/expressive-code/diff.client.js b/plugins/expressive-code/diff.client.js new file mode 100644 index 0000000..ea9a46b --- /dev/null +++ b/plugins/expressive-code/diff.client.js @@ -0,0 +1,17 @@ +(() => { + function handleToggleClick(e) { + const diffBlock = e.currentTarget.closest(".diff-block"); + if (!diffBlock) return; + diffBlock.dataset.diffMode = diffBlock.dataset.diffMode === "diff" ? "plain" : "diff"; + } + + function init() { + for (const btn of document.querySelectorAll(".diff-toggle-btn")) { + btn.removeEventListener("click", handleToggleClick); + btn.addEventListener("click", handleToggleClick); + } + } + + init(); + document.addEventListener("astro:page-load", init); +})(); diff --git a/plugins/expressive-code/diff.ts b/plugins/expressive-code/diff.ts new file mode 100644 index 0000000..d21438c --- /dev/null +++ b/plugins/expressive-code/diff.ts @@ -0,0 +1,234 @@ +import { type ExpressiveCodePlugin, PluginStyleSettings } from "astro-expressive-code"; +import type { Element } from "hast"; +import { getSection, pluginBlockConfigData } from "./block-config.ts"; +import diffClient from "./diff.client.js?raw"; + +declare module "astro-expressive-code" { + export interface StyleSettings { + diff: DiffStyleSettings; + } +} + +interface DiffStyleSettings { + lineInsertedBackground: string; + lineInsertedBorder: string; + lineInsertedIcon: string; + lineInsertedIconColor: string; + lineDeletedBackground: string; + lineDeletedBorder: string; + lineDeletedIcon: string; + lineDeletedIconColor: string; + toggleColor: string; + toggleActiveColor: string; +} + +export const diffStyleSettings = new PluginStyleSettings({ + defaultValues: { + diff: { + lineInsertedBackground: "rgba(34, 134, 58, 0.1)", + lineInsertedBorder: "#22863a", + lineInsertedIcon: `"+"`, + lineInsertedIconColor: "#22863a", + lineDeletedBackground: "rgba(215, 58, 73, 0.1)", + lineDeletedBorder: "#d73a49", + lineDeletedIcon: `"-"`, + lineDeletedIconColor: "#d73a49", + toggleColor: "#737373", + toggleActiveColor: "#171717", + }, + }, +}); + +interface DiffConfig { + ins: Set; + del: Set; + defaultMode: "diff" | "plain"; +} + +function parseLineRanges(value: string): Set { + const result = new Set(); + for (const part of value.split(",")) { + const trimmed = part.trim(); + if (!trimmed) continue; + + const rangeMatch = trimmed.match(/^(\d+)-(\d+)$/); + if (rangeMatch) { + const start = parseInt(rangeMatch[1]!, 10); + const end = parseInt(rangeMatch[2]!, 10); + for (let i = start; i <= end; i++) { + result.add(i); + } + } else { + const num = parseInt(trimmed, 10); + if (!isNaN(num)) { + result.add(num); + } + } + } + return result; +} + +export function parseDiffSection(lines: string[]): DiffConfig { + const config: DiffConfig = { ins: new Set(), del: new Set(), defaultMode: "diff" }; + + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.startsWith("ins:")) { + config.ins = parseLineRanges(trimmed.slice(4)); + } else if (trimmed.startsWith("del:")) { + config.del = parseLineRanges(trimmed.slice(4)); + } else if (trimmed.startsWith("mode:")) { + config.defaultMode = trimmed.slice(5).trim() as "diff" | "plain"; + } + } + + return config; +} + +export function pluginDiff(): ExpressiveCodePlugin { + return { + name: "diff", + styleSettings: diffStyleSettings, + jsModules: [diffClient], + baseStyles: ({ cssVar }) => ` + .diff-block[data-diff-mode="diff"] { + .ec-line.ins { + background: ${cssVar("diff.lineInsertedBackground")}; + border-left: 3px solid ${cssVar("diff.lineInsertedBorder")}; + .code::before { + content: ${cssVar("diff.lineInsertedIcon")}; + color: ${cssVar("diff.lineInsertedIconColor")}; + } + } + + .ec-line.del { + background: ${cssVar("diff.lineDeletedBackground")}; + border-left: 3px solid ${cssVar("diff.lineDeletedBorder")}; + .code::before { + content: ${cssVar("diff.lineDeletedIcon")}; + color: ${cssVar("diff.lineDeletedIconColor")}; + } + } + + .ec-line.ins, .ec-line.del { + .gutter { + margin-inline-start: -3px; + } + .code { + &::before { + position: absolute; + left: 0; + font-family: monospace; + text-align: center; + } + } + } + } + + .diff-block[data-diff-mode="plain"] .ec-line.del { + display: none; + } + + .diff-block { + flex-grow: 1; + } + + .diff-toggle-wrapper { + display: inline-flex; + padding: 1em; + font-size: 0.7rem; + margin-bottom: 0.25rem; + position: sticky; + left: 0; + } + + .diff-toggle-btn { + font-size: inherit; + font-family: inherit; + border: 1px solid ${cssVar("diff.toggleColor")}; + border-radius: 4px; + background: none; + color: ${cssVar("diff.toggleColor")}; + cursor: pointer; + transition: all 0.15s ease; + padding: 0.25em 0.5em; + + &::before { + content: "Show diff"; + } + + &:hover { + color: ${cssVar("diff.toggleActiveColor")}; + border-color: ${cssVar("diff.toggleActiveColor")}; + } + } + + .diff-block[data-diff-mode="diff"] .diff-toggle-btn::before { + content: "Hide diff"; + } + `, + hooks: { + postprocessRenderedLine: ({ codeBlock, lineIndex, renderData }) => { + const data = pluginBlockConfigData.getOrCreateFor(codeBlock); + const diffLines = getSection(data.sections, "diff"); + if (!diffLines) return; + + const config = parseDiffSection(diffLines); + const lineNum = lineIndex + 1; + + const classes = renderData.lineAst.properties.className as string[] || []; + + let noCopy = false; + if (config.ins.has(lineNum)) { + classes.push("ins"); + } + if (config.del.has(lineNum)) { + classes.push("del"); + noCopy = true; + } + + renderData.lineAst.properties.className = classes; + renderData.lineAst.properties.dataNoCopy = noCopy; + }, + postprocessRenderedBlock: ({ codeBlock, renderData }) => { + const data = pluginBlockConfigData.getOrCreateFor(codeBlock); + const diffLines = getSection(data.sections, "diff"); + if (!diffLines) return; + + const config = parseDiffSection(diffLines); + if (config.del.size === 0) return; + + const toggleBtn: Element = { + type: "element", + tagName: "button", + properties: { + className: ["diff-toggle-btn"], + type: "button", + }, + children: [], + }; + + const toggleWrapper: Element = { + type: "element", + tagName: "div", + properties: { className: ["diff-toggle-wrapper"] }, + children: [toggleBtn], + }; + + const figureAst = renderData.blockAst; + + const wrapper: Element = { + type: "element", + tagName: "div", + properties: { + className: ["diff-block"], + "data-diff-mode": config.defaultMode, + }, + children: [toggleWrapper, ...figureAst.children], + }; + + figureAst.children = [wrapper]; + }, + }, + }; +} diff --git a/plugins/expressive-code/folding.client.js b/plugins/expressive-code/folding.client.js new file mode 100644 index 0000000..aebe388 --- /dev/null +++ b/plugins/expressive-code/folding.client.js @@ -0,0 +1,70 @@ +(() => { + function toggleFold(line) { + const start = parseInt(line.dataset.foldStart, 10); + const end = parseInt(line.dataset.foldEnd, 10); + if (isNaN(start) || isNaN(end)) return; + + const codeBlock = line.closest(".expressive-code"); + if (!codeBlock) return; + + const isCollapsed = line.hasAttribute("data-collapsed"); + + if (isCollapsed) { + line.removeAttribute("data-collapsed"); + + const collapsedRegions = []; + for (let i = start + 1; i <= end; i++) { + const foldedLine = codeBlock.querySelector(`.ec-line[data-line-num="${i}"]`); + if (foldedLine?.hasAttribute("data-collapsed")) { + const innerStart = parseInt(foldedLine.dataset.foldStart, 10); + const innerEnd = parseInt(foldedLine.dataset.foldEnd, 10); + if (!isNaN(innerStart) && !isNaN(innerEnd)) { + collapsedRegions.push({ start: innerStart, end: innerEnd }); + } + } + } + + for (let i = start + 1; i <= end; i++) { + const isInsideCollapsed = collapsedRegions.some( + r => i > r.start && i <= r.end, + ); + if (isInsideCollapsed) continue; + + const foldedLine = codeBlock.querySelector(`.ec-line[data-line-num="${i}"]`); + if (foldedLine) { + foldedLine.removeAttribute("data-folded"); + } + } + } else { + line.setAttribute("data-collapsed", ""); + for (let i = start + 1; i <= end; i++) { + const foldedLine = codeBlock.querySelector(`.ec-line[data-line-num="${i}"]`); + if (foldedLine) { + foldedLine.setAttribute("data-folded", ""); + } + } + } + } + + function handleFoldClick(e) { + const target = e.target; + if (!(target instanceof HTMLElement)) return; + + const chevron = target.closest(".fold-chevron"); + const ellipsis = target.closest(".fold-ellipsis"); + + if (chevron) { + const line = chevron.closest(".ec-line"); + if (line) toggleFold(line); + } else if (ellipsis) { + const line = ellipsis.closest(".ec-line"); + if (line) toggleFold(line); + } + } + + document.addEventListener("click", handleFoldClick); + document.addEventListener("astro:page-load", () => { + document.removeEventListener("click", handleFoldClick); + document.addEventListener("click", handleFoldClick); + }); +})(); diff --git a/plugins/expressive-code/folding.ts b/plugins/expressive-code/folding.ts new file mode 100644 index 0000000..6c38d56 --- /dev/null +++ b/plugins/expressive-code/folding.ts @@ -0,0 +1,272 @@ +import { AttachedPluginData, type ExpressiveCodePlugin, PluginStyleSettings } from "astro-expressive-code"; +import type { Element } from "hast"; +import { getSection, pluginBlockConfigData } from "./block-config.ts"; +import foldingClient from "./folding.client.js?raw"; + +declare module "astro-expressive-code" { + export interface StyleSettings { + folding: FoldingStyleSettings; + } +} + +interface FoldingStyleSettings { + ellipsisColor: string; + ellipsisBackground: string; + ellipsisColorHover: string; + ellipsisBackgroundHover: string; +} + +export const foldingStyleSettings = new PluginStyleSettings({ + defaultValues: { + folding: { + ellipsisColor: "#6b6b6b", + ellipsisBackground: "rgba(249, 200, 155, 0.3)", + ellipsisColorHover: "#232333", + ellipsisBackgroundHover: "rgba(249, 200, 155, 0.9)", + }, + }, +}); + +interface FoldRegion { + start: number; + end: number; + collapsed: boolean; +} + +function getIndent(text: string): number { + const match = text.match(/^(\s*)/); + return match?.[1]?.length ?? 0; +} + +function parseCollapseSection(lines: string[]): Set { + const collapsed = new Set(); + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + + const rangeMatch = trimmed.match(/^(\d+)-(\d+)$/); + if (rangeMatch) { + const start = parseInt(rangeMatch[1]!, 10); + const end = parseInt(rangeMatch[2]!, 10); + for (let i = start; i <= end; i++) { + collapsed.add(i); + } + } else { + const num = parseInt(trimmed, 10); + if (!isNaN(num)) { + collapsed.add(num); + } + } + } + + return collapsed; +} + +function parseRegions(lines: string[], collapsedLines: Set): FoldRegion[] { + const regions: FoldRegion[] = []; + const n = lines.length; + const indents: number[] = []; + + for (let i = 0; i < n; i++) { + const line = lines[i]; + if (line === undefined) { + indents.push(-1); + continue; + } + const isEmpty = line.trim() === ""; + indents.push(isEmpty ? -1 : getIndent(line)); + } + + const stack: { index: number; indent: number }[] = []; + + for (let i = 0; i <= n; i++) { + const indent = i < n ? (indents[i] ?? -Infinity) : -Infinity; + if (indent === -1) continue; + + while (stack.length > 0) { + const top = stack[stack.length - 1]; + if (!top || indent > top.indent) break; + const { index } = stack.pop()!; + if (i - 1 > index) { + regions.push({ start: index, end: i - 1, collapsed: collapsedLines.has(index + 1) }); + } + } + + if (i < n && indent !== -Infinity) { + stack.push({ index: i, indent }); + } + } + + const nextNonEmptyIndent: number[] = new Array(n).fill(-Infinity); + let lastSeen: number = -Infinity; + for (let i = n - 1; i >= 0; i--) { + nextNonEmptyIndent[i] = lastSeen; + const ind = indents[i]; + if (ind !== undefined && ind !== -1) { + lastSeen = ind; + } + } + + return regions.filter((r) => { + const startIndent = indents[r.start]; + const nextIndent = nextNonEmptyIndent[r.start]; + return startIndent !== undefined && startIndent !== -1 && nextIndent !== undefined && nextIndent > startIndent; + }); +} + +interface PluginFoldingData { + foldRegions: FoldRegion[]; +} + +export const pluginFoldingData = new AttachedPluginData( + () => ({ foldRegions: [] }), +); + +export function pluginFolding(): ExpressiveCodePlugin { + return { + name: "folding", + styleSettings: foldingStyleSettings, + baseStyles: ({ cssVar }) => ` + .ec-line[data-foldable] .fold-chevron { + display: inline-flex; + width: 1.2em; + height: 1lh; + align-items: center; + justify-content: center; + cursor: pointer; + pointer-events: auto !important; + opacity: 0; + transition: opacity 0.15s ease, color 0.15s ease; + color: ${cssVar("gutterForeground")}; + user-select: none; + position: absolute; + transform: translateX(-1px); + right: 0.25em; + top: 0; + + &::before { + content: ""; + width: 0; + height: 0; + border-left: 5px solid currentColor; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + transition: transform 0.15s ease; + transform: rotate(90deg); + } + + &:hover { color: ${cssVar("codeForeground")}; } + } + + .expressive-code:hover .ec-line[data-foldable] .fold-chevron { opacity: 1; } + + .ec-line[data-collapsed] .fold-chevron { + opacity: 1; + &::before { transform: rotate(0deg); } + } + + .ec-line[data-folded] { display: none !important; } + + .fold-ellipsis { + display: none; + color: ${cssVar("folding.ellipsisColor")}; + font-style: normal; + cursor: pointer; + user-select: none; + border: none; + background: ${cssVar("folding.ellipsisBackground")}; + border-radius: 3px; + margin-left: 0.5em; + padding: 0 0.4em; + font: inherit; + transition: background 0.15s ease, color 0.15s ease; + + &:hover { + color: ${cssVar("folding.ellipsisColorHover")}; + background: ${cssVar("folding.ellipsisBackgroundHover")}; + } + } + + .ec-line[data-collapsed] .fold-ellipsis { display: inline; } + `, + jsModules: [foldingClient], + hooks: { + preprocessCode: ({ codeBlock }) => { + const blockConfigData = pluginBlockConfigData.getOrCreateFor(codeBlock); + const collapseLines = getSection(blockConfigData.sections, "collapse"); + const collapsedLines = collapseLines ? parseCollapseSection(collapseLines) : new Set(); + + const lines = codeBlock.getLines().map((line) => line.text); + const regions = parseRegions(lines, collapsedLines); + + if (regions.length === 0) return; + const data = pluginFoldingData.getOrCreateFor(codeBlock); + data.foldRegions = regions; + }, + postprocessRenderedLine: ({ codeBlock, lineIndex, renderData }) => { + const data = pluginFoldingData.getOrCreateFor(codeBlock); + const foldRegions = data.foldRegions; + if (!foldRegions) return; + + const region = foldRegions.find(r => r.start === lineIndex); + + const lineAst = renderData.lineAst; + lineAst.properties = lineAst.properties || {}; + lineAst.properties["data-line-num"] = String(lineIndex); + + const isInsideCollapsedRegion = foldRegions.some( + r => r.collapsed && lineIndex > r.start && lineIndex <= r.end, + ); + if (isInsideCollapsedRegion) { + lineAst.properties["data-folded"] = ""; + } + + if (region) { + lineAst.properties["data-foldable"] = ""; + lineAst.properties["data-fold-start"] = String(region.start); + lineAst.properties["data-fold-end"] = String(region.end); + if (region.collapsed) { + lineAst.properties["data-collapsed"] = ""; + } + + const chevron: Element = { + type: "element", + tagName: "span", + properties: { className: ["fold-chevron"] }, + children: [], + }; + + const ellipsis: Element = { + type: "element", + tagName: "button", + properties: { className: ["fold-ellipsis"], dataNoCopy: true }, + children: [{ type: "text", value: "..." }], + }; + + const gutterDiv = lineAst.children.find( + (child): child is Element => + child.type === "element" + && child.tagName === "div" + && Array.isArray(child.properties?.className) + && child.properties.className.includes("gutter"), + ); + if (gutterDiv) { + gutterDiv.children.push(chevron); + } + + const codeDiv = lineAst.children.find( + (child): child is Element => + child.type === "element" + && child.tagName === "div" + && Array.isArray(child.properties?.className) + && child.properties.className.includes("code"), + ); + if (codeDiv) { + codeDiv.children.push(ellipsis); + } + } + }, + }, + }; +} diff --git a/plugins/expressive-code/line-numbers.ts b/plugins/expressive-code/line-numbers.ts new file mode 100644 index 0000000..af2c602 --- /dev/null +++ b/plugins/expressive-code/line-numbers.ts @@ -0,0 +1,96 @@ +import { type ExpressiveCodePlugin } from "astro-expressive-code"; +import { h, setInlineStyle } from "astro-expressive-code/hast"; +import { type BlockConfigSection, getSection, pluginBlockConfigData } from "./block-config.ts"; +import { parseDiffSection } from "./diff.ts"; + +function getDeletedLines(sections: BlockConfigSection[]): Set { + const diffLines = getSection(sections, "diff"); + if (!diffLines) return new Set(); + + const config = parseDiffSection(diffLines); + + return config.del; +} + +export function pluginLineNumbers(): ExpressiveCodePlugin { + return { + name: "line-numbers", + baseStyles: ({ cssVar }) => ` + .gutter { + position: sticky; + left: 0; + z-index: 1; + background: inherit; + &::before { + content: ""; + position: absolute; + z-index: -1; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: ${cssVar("codeBackground")}; + } + &::after { + content: ""; + position: absolute; + inset: 0; + z-index: -1; + background: inherit; + } + + .code { + border-inline-start: 0; + } + } + + .gutter .ln { + display: inline-flex; + justify-content: flex-end; + align-items: flex-start; + box-sizing: content-box; + min-width: var(--lnWidth); + padding-inline: 1.5ch 2.5ch; + color: ${cssVar("gutterForeground")}; + + .highlight & { color: ${cssVar("gutterHighlightForeground")}; } + } + + pre { + line-height: ${cssVar("codeLineHeight")}; + } + `, + hooks: { + preprocessCode: ({ codeBlock, addGutterElement }) => { + const data = pluginBlockConfigData.getOrCreateFor(codeBlock); + const deletedLines = getDeletedLines(data.sections); + + let displayNum = 0; + + addGutterElement({ + renderPhase: "earlier", + renderLine: ({ lineIndex }) => { + const lineNum = lineIndex + 1; + const isDeleted = deletedLines.has(lineNum); + + if (!isDeleted) { + displayNum++; + } + + return h("div.ln", { "aria-hidden": "true" }, isDeleted ? "" : `${displayNum}`); + }, + renderPlaceholder: () => h("div.ln"), + }); + }, + postprocessRenderedBlock: ({ codeBlock, renderData }) => { + const data = pluginBlockConfigData.getOrCreateFor(codeBlock); + const deletedLines = getDeletedLines(data.sections); + + const totalLines = codeBlock.getLines().length; + const endLineNumber = totalLines - deletedLines.size; + const lnWidth = endLineNumber.toString().length; + + setInlineStyle(renderData.blockAst, "--lnWidth", `${Math.max(lnWidth, 2)}ch`); + }, + }, + }; +} diff --git a/plugins/expressive-code/links.ts b/plugins/expressive-code/links.ts new file mode 100644 index 0000000..8cf64ae --- /dev/null +++ b/plugins/expressive-code/links.ts @@ -0,0 +1,166 @@ +import { type ExpressiveCodePlugin, PluginStyleSettings } from "astro-expressive-code"; +import type { Element, Text } from "hast"; +import { getSection, pluginBlockConfigData } from "./block-config.ts"; + +declare module "astro-expressive-code" { + export interface StyleSettings { + links: LinksStyleSettings; + } +} + +interface LinksStyleSettings { + underlineColor: string; + underlineColorHover: string; +} + +export const linksStyleSettings = new PluginStyleSettings({ + defaultValues: { + links: { + underlineColor: "color-mix(in srgb, currentColor 40%, transparent)", + underlineColorHover: "currentColor", + }, + }, +}); + +interface Link { + target: string; + href: string; + lines?: number[]; +} + +function parseLinksSection(lines: string[], anchor?: string): Link[] { + const links: Link[] = []; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + + const arrowIdx = trimmed.lastIndexOf(" -> "); + let targetPart: string; + let href: string; + + if (arrowIdx !== -1) { + targetPart = trimmed.slice(0, arrowIdx).trim(); + href = trimmed.slice(arrowIdx + 4).trim(); + } else { + const colonMatch = trimmed.match(/^(.+?):\s+(.+)$/); + if (!colonMatch) continue; + targetPart = colonMatch[1]!.trim(); + href = colonMatch[2]!.trim(); + } + + if (href.startsWith(":") && anchor) { + href = `#${anchor}${href}`; + } + + const lineMatch = targetPart.match(/^(.+?)@([\d,]+)$/); + + if (lineMatch) { + const target = lineMatch[1]!; + const lineNums = lineMatch[2]!.split(",").map(n => parseInt(n.trim(), 10) - 1); + links.push({ target, href, lines: lineNums }); + } else { + links.push({ target: targetPart, href }); + } + } + + return links; +} + +function wrapTextWithLink(node: Element, lineIndex: number, link: Link): boolean { + if (link.lines !== undefined && !link.lines.includes(lineIndex)) { + return false; + } + + let found = false; + + function processChildren(children: (Element | Text)[]): (Element | Text)[] { + const result: (Element | Text)[] = []; + + for (const child of children) { + if (child.type === "text") { + const text = child.value; + const idx = text.indexOf(link.target); + + if (idx !== -1 && !found) { + found = true; + const before = text.slice(0, idx); + const after = text.slice(idx + link.target.length); + + if (before) { + result.push({ type: "text", value: before }); + } + + const isExternal = link.href.startsWith("http://") || link.href.startsWith("https://"); + const anchor: Element = { + type: "element", + tagName: "a", + properties: { + className: ["code-link"], + href: link.href, + ...(isExternal ? { target: "_blank", rel: "noopener noreferrer" } : {}), + }, + children: [{ type: "text", value: link.target }], + }; + result.push(anchor); + + if (after) { + result.push({ type: "text", value: after }); + } + } else { + result.push(child); + } + } else if (child.type === "element") { + if (!found && child.children) { + child.children = processChildren(child.children as (Element | Text)[]); + } + result.push(child); + } + } + + return result; + } + + node.children = processChildren(node.children as (Element | Text)[]); + return found; +} + +export function pluginLinks(): ExpressiveCodePlugin { + return { + name: "links", + styleSettings: linksStyleSettings, + baseStyles: ({ cssVar }) => ` + .code-link { + color: inherit; + text-decoration: none; + border-bottom: 1px dashed ${cssVar("links.underlineColor")}; + cursor: pointer; + transition: border-color 0.15s ease; + + &:hover { + border-bottom-style: solid; + border-bottom-color: ${cssVar("links.underlineColorHover")}; + } + } + `, + hooks: { + postprocessRenderedLine: ({ codeBlock, lineIndex, renderData }) => { + const data = pluginBlockConfigData.getOrCreateFor(codeBlock); + const linkLines = getSection(data.sections, "links"); + if (!linkLines) return; + + const anchorLines = getSection(data.sections, "anchor"); + const anchor = anchorLines?.[0]?.trim(); + + const links = parseLinksSection(linkLines, anchor); + if (links.length === 0) return; + + const lineAst = renderData.lineAst; + + for (const link of links) { + wrapTextWithLink(lineAst, lineIndex, link); + } + }, + }, + }; +} diff --git a/plugins/expressive-code/theme.ts b/plugins/expressive-code/theme.ts new file mode 100644 index 0000000..6aa8156 --- /dev/null +++ b/plugins/expressive-code/theme.ts @@ -0,0 +1,90 @@ +import { ExpressiveCodeTheme } from "astro-expressive-code"; + +const colors = { + bg: "#fffcfa", + fg: "#1a1a2e", + muted: "#888888", + border: "#e8e4e0", + primary: "#f0e6dc", +}; + +export const troyModerTheme = new ExpressiveCodeTheme({ + name: "troy-moder", + type: "light", + colors: { + "editor.background": colors.bg, + "editor.foreground": colors.fg, + "editorLineNumber.foreground": colors.muted, + "editorLineNumber.activeForeground": colors.fg, + "editor.selectionBackground": colors.primary, + "editor.lineHighlightBackground": colors.primary + "4d", + "editorBracketMatch.background": colors.primary + "80", + "editorBracketMatch.border": colors.border, + "focusBorder": colors.border, + "titleBar.border": colors.border, + "scrollbarSlider.background": colors.border + "80", + "scrollbarSlider.hoverBackground": colors.border, + }, + tokenColors: [ + { + scope: ["comment", "punctuation.definition.comment"], + settings: { foreground: "#a0a0a0", fontStyle: "italic" }, + }, + { + scope: ["string", "string.quoted"], + settings: { foreground: "#22863a" }, + }, + { + scope: ["constant.numeric", "constant.language"], + settings: { foreground: "#005cc5" }, + }, + { + scope: ["keyword", "storage.type", "storage.modifier"], + settings: { foreground: "#AB3AD7" }, + }, + { + scope: ["entity.name.function", "support.function"], + settings: { foreground: "#425EC1" }, + }, + { + scope: ["entity.name.type", "entity.name.class", "support.type", "support.class"], + settings: { foreground: "#c23a3a" }, + }, + { + scope: ["variable", "variable.other"], + settings: { foreground: colors.fg }, + }, + { + scope: ["variable.parameter"], + settings: { foreground: "#2e6b8a" }, + }, + { + scope: ["entity.name.tag"], + settings: { foreground: "#22863a" }, + }, + { + scope: ["entity.other.attribute-name"], + settings: { foreground: "#6f42c1" }, + }, + { + scope: ["punctuation", "meta.brace"], + settings: { foreground: "#444d56" }, + }, + { + scope: ["constant.other.placeholder", "variable.interpolation"], + settings: { foreground: "#005cc5" }, + }, + { + scope: ["meta.diff.header", "meta.diff.index"], + settings: { foreground: "#6f42c1" }, + }, + { + scope: ["markup.inserted"], + settings: { foreground: "#22863a", background: "#e6ffed" }, + }, + { + scope: ["markup.deleted"], + settings: { foreground: "#d73a49", background: "#ffeef0" }, + }, + ], +}); diff --git a/plugins/expressive-code/tooltips.ts b/plugins/expressive-code/tooltips.ts new file mode 100644 index 0000000..25ab405 --- /dev/null +++ b/plugins/expressive-code/tooltips.ts @@ -0,0 +1,241 @@ +import { type ExpressiveCodePlugin, PluginStyleSettings } from "astro-expressive-code"; +import type { Element, Text } from "hast"; +import { fromHtml } from "hast-util-from-html"; +import MarkdownIt from "markdown-it"; +import { getSection, pluginBlockConfigData } from "./block-config.ts"; + +declare module "astro-expressive-code" { + export interface StyleSettings { + tooltips: TooltipsStyleSettings; + } +} + +interface TooltipsStyleSettings { + underlineColor: string; + underlineColorHover: string; + background: string; + textColor: string; + borderColor: string; + shadowColor: string; + codeBackground: string; + borderRadius: string; + padding: string; + fontSize: string; + maxWidth: string; +} + +export const tooltipsStyleSettings = new PluginStyleSettings({ + defaultValues: { + tooltips: { + underlineColor: "color-mix(in srgb, currentColor 50%, transparent)", + underlineColorHover: "currentColor", + background: "#ffffff", + textColor: "#27272a", + borderColor: "#e4e4e7", + shadowColor: "rgba(0, 0, 0, 0.1)", + codeBackground: "rgba(0, 0, 0, 0.05)", + borderRadius: "6px", + padding: "0.75rem 1rem", + fontSize: "0.875rem", + maxWidth: "400px", + }, + }, +}); + +const md = new MarkdownIt({ html: true }); + +interface Tooltip { + target: string; + content: string; + html: string; + line?: number; +} + +function parseInlineMarkdown(content: string): string { + return md.renderInline(content); +} + +function parseHoverSection(lines: string[]): Tooltip[] { + const tooltips: Tooltip[] = []; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + + const match = trimmed.match(/^(.+?):\s*(.+)$/); + if (!match) continue; + + const target = match[1]!.trim(); + const content = match[2]!.trim(); + const html = parseInlineMarkdown(content); + const lineMatch = target.match(/^(.+?)@(\d+)$/); + + if (lineMatch) { + tooltips.push({ + target: lineMatch[1]!, + content, + html, + line: parseInt(lineMatch[2]!, 10) - 1, + }); + } else { + tooltips.push({ target, content, html }); + } + } + + return tooltips; +} + +function wrapTextWithTooltip(node: Element, lineIndex: number, tooltip: Tooltip): boolean { + if (tooltip.line !== undefined && tooltip.line !== lineIndex) { + return false; + } + + let found = false; + + function processChildren(children: (Element | Text)[]): (Element | Text)[] { + const result: (Element | Text)[] = []; + + for (const child of children) { + if (child.type === "text") { + const text = child.value; + const idx = text.indexOf(tooltip.target); + + if (idx !== -1 && !found) { + found = true; + const before = text.slice(0, idx); + const after = text.slice(idx + tooltip.target.length); + + if (before) { + result.push({ type: "text", value: before }); + } + + const htmlTree = fromHtml(tooltip.html, { fragment: true }); + const tooltipEl: Element = { + type: "element", + tagName: "span", + properties: { className: ["tooltip-content"], dataNoCopy: true }, + children: htmlTree.children as (Element | Text)[], + }; + const wrapper: Element = { + type: "element", + tagName: "span", + properties: { className: ["tooltip-trigger"], tabIndex: -1 }, + children: [ + { type: "text", value: tooltip.target }, + tooltipEl, + ], + }; + result.push(wrapper); + + if (after) { + result.push({ type: "text", value: after }); + } + } else { + result.push(child); + } + } else if (child.type === "element") { + if (!found && child.children) { + child.children = processChildren(child.children as (Element | Text)[]); + } + result.push(child); + } + } + + return result; + } + + node.children = processChildren(node.children as (Element | Text)[]); + return found; +} + +export function pluginTooltips(): ExpressiveCodePlugin { + return { + name: "tooltips", + styleSettings: tooltipsStyleSettings, + baseStyles: ({ cssVar }) => ` + .tooltip-trigger { + position: relative; + cursor: help; + border-bottom: 1px dashed ${cssVar("tooltips.underlineColor")}; + + &:hover, &.pinned { + border-bottom-style: solid; + border-bottom-color: ${cssVar("tooltips.underlineColorHover")}; + } + + &:hover .tooltip-content { + opacity: 1; + visibility: visible; + } + } + + .tooltip-content { + position: absolute; + top: calc(100% + 8px); + left: 0; + z-index: 100; + max-width: ${cssVar("tooltips.maxWidth")}; + width: max-content; + padding: ${cssVar("tooltips.padding")}; + font-family: ${cssVar("codeFontFamily")}; + font-size: ${cssVar("tooltips.fontSize")}; + line-height: 1.5; + white-space: normal; + color: ${cssVar("tooltips.textColor")}; + background: ${cssVar("tooltips.background")}; + border: 1px solid ${cssVar("tooltips.borderColor")}; + border-radius: ${cssVar("tooltips.borderRadius")}; + box-shadow: 0 4px 12px ${cssVar("tooltips.shadowColor")}; + opacity: 0; + visibility: hidden; + transition: opacity 0.2s ease, visibility 0.2s ease; + + &::before { + content: ""; + position: absolute; + top: -6px; + left: 12px; + width: 10px; + height: 10px; + background: ${cssVar("tooltips.background")}; + border-left: 1px solid ${cssVar("tooltips.borderColor")}; + border-top: 1px solid ${cssVar("tooltips.borderColor")}; + transform: rotate(45deg); + } + + &::after { + content: ""; + position: absolute; + top: -8px; + left: 0; + right: 0; + height: 8px; + } + + code { + padding: 0.125rem 0.375rem; + font-family: ${cssVar("codeFontFamily")}; + font-size: 0.8125rem; + background: ${cssVar("tooltips.codeBackground")}; + border-radius: 3px; + } + } + `, + hooks: { + postprocessRenderedLine: ({ codeBlock, lineIndex, renderData }) => { + const data = pluginBlockConfigData.getOrCreateFor(codeBlock); + const hoverLines = getSection(data.sections, "hover"); + if (!hoverLines) return; + + const tooltips = parseHoverSection(hoverLines); + if (tooltips.length === 0) return; + + const lineAst = renderData.lineAst; + + for (const tooltip of tooltips) { + wrapTextWithTooltip(lineAst, lineIndex, tooltip); + } + }, + }, + }; +} diff --git a/plugins/shiki.mjs b/plugins/shiki.mjs deleted file mode 100644 index 44f23a8..0000000 --- a/plugins/shiki.mjs +++ /dev/null @@ -1,8 +0,0 @@ -export function transformerCopyButton() { - return { - name: "copy-button", - pre(node) { - node.properties["data-copy"] = ""; - }, - }; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1041edd..02aaeb5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,23 +17,23 @@ importers: specifier: ^0.9.0 version: 0.9.6(prettier@3.8.1)(typescript@5.9.3) '@astrojs/mdx': - specifier: ^4.0.0 - version: 4.3.13(astro@5.18.0(@types/node@25.3.5)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2)) + specifier: ^5.0.0 + version: 5.0.0(astro@6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2)) '@astrojs/rss': - specifier: ^4.0.15 - version: 4.0.15 + specifier: 4.0.15-beta.4 + version: 4.0.15-beta.4 '@astrojs/svelte': - specifier: ^7.2.5 - version: 7.2.5(@types/node@25.3.5)(astro@5.18.0(@types/node@25.3.5)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2))(svelte@5.53.7)(typescript@5.9.3)(yaml@2.8.2) + specifier: ^8.0.0 + version: 8.0.0(@types/node@25.3.5)(astro@6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2))(lightningcss@1.32.0)(svelte@5.53.7)(typescript@5.9.3)(yaml@2.8.2) '@eslint/js': specifier: ^10.0.1 version: 10.0.1(eslint@10.0.3) '@fontsource/jetbrains-mono': specifier: ^5.2.8 version: 5.2.8 - '@shikijs/transformers': - specifier: ^4.0.1 - version: 4.0.1 + '@types/hast': + specifier: ^3.0.4 + version: 3.0.4 '@types/markdown-it': specifier: ^14.1.2 version: 14.1.2 @@ -44,11 +44,14 @@ importers: specifier: ^2.16.1 version: 2.16.1 astro: - specifier: ^5.0.0 - version: 5.18.0(@types/node@25.3.5)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2) + specifier: ^6.0.4 + version: 6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2) + astro-expressive-code: + specifier: ^0.41.7 + version: 0.41.7(astro@6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2)) astro-pagefind: specifier: ^1.8.5 - version: 1.8.5(astro@5.18.0(@types/node@25.3.5)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 1.8.5(astro@6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2)) eslint: specifier: ^10.0.3 version: 10.0.3 @@ -61,6 +64,9 @@ importers: globals: specifier: ^17.4.0 version: 17.4.0 + hast-util-from-html: + specifier: ^2.0.3 + version: 2.0.3 markdown-it: specifier: ^14.1.1 version: 14.1.1 @@ -97,6 +103,9 @@ importers: vfile: specifier: ^6.0.3 version: 6.0.3 + vite: + specifier: ^8.0.0 + version: 8.0.0(@types/node@25.3.5)(esbuild@0.27.3)(yaml@2.8.2) packages: @@ -109,8 +118,11 @@ packages: '@astrojs/compiler@2.13.1': resolution: {integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==} - '@astrojs/internal-helpers@0.7.5': - resolution: {integrity: sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==} + '@astrojs/compiler@3.0.0': + resolution: {integrity: sha512-MwAbDE5mawZ1SS+D8qWiHdprdME5Tlj2e0YjxnEICvcOpbSukNS7Sa7hA5PK+6RrmUr/t6Gi5YgrdZKjbO/WPQ==} + + '@astrojs/internal-helpers@0.8.0': + resolution: {integrity: sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w==} '@astrojs/language-server@2.16.3': resolution: {integrity: sha512-yO5K7RYCMXUfeDlnU6UnmtnoXzpuQc0yhlaCNZ67k1C/MiwwwvMZz+LGa+H35c49w5QBfvtr4w4Zcf5PcH8uYA==} @@ -124,28 +136,28 @@ packages: prettier-plugin-astro: optional: true - '@astrojs/markdown-remark@6.3.10': - resolution: {integrity: sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==} + '@astrojs/markdown-remark@7.0.0': + resolution: {integrity: sha512-jTAXHPy45L7o1ljH4jYV+ShtOHtyQUa1mGp3a5fJp1soX8lInuTJQ6ihmldHzVM4Q7QptU4SzIDIcKbBJO7sXQ==} - '@astrojs/mdx@4.3.13': - resolution: {integrity: sha512-IHDHVKz0JfKBy3//52JSiyWv089b7GVSChIXLrlUOoTLWowG3wr2/8hkaEgEyd/vysvNQvGk+QhysXpJW5ve6Q==} - engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + '@astrojs/mdx@5.0.0': + resolution: {integrity: sha512-J4rW6eT+qgVw7+RXdBYO4vYyWGeXXQp8wop9dXsOlLzIsVSxyttMCgkGCWvIR2ogBqKqeYgI6YDW93PaDHkCaA==} + engines: {node: ^20.19.1 || >=22.12.0} peerDependencies: - astro: ^5.0.0 + astro: ^6.0.0-alpha.0 - '@astrojs/prism@3.3.0': - resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} - engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + '@astrojs/prism@4.0.0': + resolution: {integrity: sha512-NndtNPpxaGinRpRytljGBvYHpTOwHycSZ/c+lQi5cHvkqqrHKWdkPEhImlODBNmbuB+vyQUNUDXyjzt66CihJg==} + engines: {node: ^20.19.1 || >=22.12.0} - '@astrojs/rss@4.0.15': - resolution: {integrity: sha512-uXO/k6AhRkIDXmRoc6xQpoPZrimQNUmS43X4+60yunfuMNHtSRN5e/FiSi7NApcZqmugSMc5+cJi8ovqgO+qIg==} + '@astrojs/rss@4.0.15-beta.4': + resolution: {integrity: sha512-BeVo8kgXu29HMYhmAX7MbEQXKEOUMga4XouYWAYLvtQMVnvgEtQhpAqxauljfCCQ1S2tRvKeu/fiZ6GAGxrtEQ==} - '@astrojs/svelte@7.2.5': - resolution: {integrity: sha512-Tl5aF/dYbzzd7sLpxMBX6pRz3yJ1B4pilt9G3GJbj0I0/doJHIEmerNQsnlxX0/InNKUhMXXN8wyyet9VhA+Zw==} - engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + '@astrojs/svelte@8.0.0': + resolution: {integrity: sha512-jX5t7BLAzS72An/1ci1gl5pliyMCjoC2KgTJrw3jbMmZgCaeZQtkzMy7jARjME5bDutPZX2WeaII0C7NOC2SSA==} + engines: {node: ^20.19.1 || >=22.12.0} peerDependencies: - astro: ^5.0.0 - svelte: ^5.1.16 + astro: ^6.0.0-alpha.0 + svelte: ^5.43.6 typescript: ^5.3.3 '@astrojs/telemetry@3.3.0': @@ -176,6 +188,16 @@ packages: resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==} engines: {node: '>=18'} + '@clack/core@1.1.0': + resolution: {integrity: sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA==} + + '@clack/prompts@1.1.0': + resolution: {integrity: sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==} + + '@ctrl/tinycolor@4.2.0': + resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} + engines: {node: '>=14'} + '@emmetio/abbreviation@2.3.3': resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} @@ -197,14 +219,14 @@ packages: '@emmetio/stream-reader@2.2.0': resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} + '@emnapi/core@1.9.0': + resolution: {integrity: sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==} + '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] + '@emnapi/wasi-threads@1.2.0': + resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} '@esbuild/aix-ppc64@0.27.3': resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} @@ -212,300 +234,150 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.27.3': resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.27.3': resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.27.3': resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.27.3': resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.27.3': resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.27.3': resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.27.3': resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.27.3': resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.27.3': resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.27.3': resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.27.3': resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.27.3': resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.27.3': resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.27.3': resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.27.3': resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.27.3': resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.12': - resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.27.3': resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.27.3': resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.12': - resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.27.3': resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.27.3': resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.12': - resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.27.3': resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.27.3': resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.27.3': resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.27.3': resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.27.3': resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} engines: {node: '>=18'} @@ -551,6 +423,18 @@ packages: resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@expressive-code/core@0.41.7': + resolution: {integrity: sha512-ck92uZYZ9Wba2zxkiZLsZGi9N54pMSAVdrI9uW3Oo9AtLglD5RmrdTwbYPCT2S/jC36JGB2i+pnQtBm/Ib2+dg==} + + '@expressive-code/plugin-frames@0.41.7': + resolution: {integrity: sha512-diKtxjQw/979cTglRFaMCY/sR6hWF0kSMg8jsKLXaZBSfGS0I/Hoe7Qds3vVEgeoW+GHHQzMcwvgx/MOIXhrTA==} + + '@expressive-code/plugin-shiki@0.41.7': + resolution: {integrity: sha512-DL605bLrUOgqTdZ0Ot5MlTaWzppRkzzqzeGEu7ODnHF39IkEBbFdsC7pbl3LbUQ1DFtnfx6rD54k/cdofbW6KQ==} + + '@expressive-code/plugin-text-markers@0.41.7': + resolution: {integrity: sha512-Ewpwuc5t6eFdZmWlFyeuy3e1PTQC0jFvw2Q+2bpcWXbOZhPLsT7+h8lsSIJxb5mS7wZko7cKyQ2RLYDyK6Fpmw==} + '@fontsource/jetbrains-mono@5.2.8': resolution: {integrity: sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==} @@ -726,6 +610,9 @@ packages: '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -741,6 +628,13 @@ packages: '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + '@oxc-project/runtime@0.115.0': + resolution: {integrity: sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@oxc-project/types@0.115.0': + resolution: {integrity: sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==} + '@pagefind/darwin-arm64@1.4.0': resolution: {integrity: sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==} cpu: [arm64] @@ -781,6 +675,98 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@rolldown/binding-android-arm64@1.0.0-rc.9': + resolution: {integrity: sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.9': + resolution: {integrity: sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.9': + resolution: {integrity: sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.9': + resolution: {integrity: sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': + resolution: {integrity: sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': + resolution: {integrity: sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': + resolution: {integrity: sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': + resolution: {integrity: sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': + resolution: {integrity: sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': + resolution: {integrity: sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': + resolution: {integrity: sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': + resolution: {integrity: sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': + resolution: {integrity: sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': + resolution: {integrity: sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': + resolution: {integrity: sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-rc.9': + resolution: {integrity: sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==} + '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -954,10 +940,6 @@ packages: resolution: {integrity: sha512-FW41C/D6j/yKQkzVdjrRPiJCtgeDaYRJFEyCKFCINuRJRj9WcmubhP4KQHPZ4+9eT87jruSrYPyoblNRyDFzvA==} engines: {node: '>=20'} - '@shikijs/transformers@4.0.1': - resolution: {integrity: sha512-oE46W2eHpvD06+C0MBthd2YrDM6cktvJDFl764tOEXxfr3dAJhxMc0uNZ2tQXp+bkMgl4E7IL88Mj9RnSqiayw==} - engines: {node: '>=20'} - '@shikijs/types@3.23.0': resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} @@ -973,20 +955,23 @@ packages: peerDependencies: acorn: ^8.9.0 - '@sveltejs/vite-plugin-svelte-inspector@4.0.1': - resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22} + '@sveltejs/vite-plugin-svelte-inspector@5.0.2': + resolution: {integrity: sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==} + engines: {node: ^20.19 || ^22.12 || >=24} peerDependencies: - '@sveltejs/vite-plugin-svelte': ^5.0.0 + '@sveltejs/vite-plugin-svelte': ^6.0.0-next.0 svelte: ^5.0.0 - vite: ^6.0.0 + vite: ^6.3.0 || ^7.0.0 - '@sveltejs/vite-plugin-svelte@5.1.1': - resolution: {integrity: sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22} + '@sveltejs/vite-plugin-svelte@6.2.4': + resolution: {integrity: sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==} + engines: {node: ^20.19 || ^22.12 || >=24} peerDependencies: svelte: ^5.0.0 - vite: ^6.0.0 + vite: ^6.3.0 || ^7.0.0 + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -1154,25 +1139,14 @@ packages: ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - ansi-align@3.0.1: - resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1199,14 +1173,19 @@ packages: resolution: {integrity: sha512-aOLc/aDR7lTWAHlytEefwn4Y6qs6uMr69DZvUx2A1AOAZsWhGB/paiRWPtVchh9wzMvLeqr+DkbENhVreVr9AQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + astro-expressive-code@0.41.7: + resolution: {integrity: sha512-hUpogGc6DdAd+I7pPXsctyYPRBJDK7Q7d06s4cyP0Vz3OcbziP3FNzN0jZci1BpCvLn9675DvS7B9ctKKX64JQ==} + peerDependencies: + astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta + astro-pagefind@1.8.5: resolution: {integrity: sha512-CVhKKA9bTQ7hLsHk9KTNDzOdgR4EI04gn0mjDGfnXzaHx7rL92YkNpFM5AoFl9NWmOUbaIFC2DN7Yvs/ZFPRdA==} peerDependencies: astro: ^2.0.4 || ^3 || ^4 || ^5 - astro@5.18.0: - resolution: {integrity: sha512-CHiohwJIS4L0G6/IzE1Fx3dgWqXBCXus/od0eGUfxrZJD2um2pE7ehclMmgL/fXqbU7NfE1Ze2pq34h2QaA6iQ==} - engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} + astro@6.0.4: + resolution: {integrity: sha512-1piLJCPTL/x7AMO2cjVFSTFyRqKuC3W8sSEySCt1aJio+p/wGs5H3K+Xr/rE9ftKtknLUtjxCqCE7/0NsXfGpQ==} + engines: {node: ^20.19.1 || >=22.12.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} hasBin: true astrojs-compiler-sync@1.1.1: @@ -1226,16 +1205,12 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - base-64@1.0.0: - resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} + bcp-47-match@2.0.3: + resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - boxen@8.0.1: - resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} - engines: {node: '>=18'} - brace-expansion@5.0.4: resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} engines: {node: 18 || 20 || >=22} @@ -1244,17 +1219,9 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - camelcase@8.0.0: - resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} - engines: {node: '>=16'} - ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -1279,10 +1246,6 @@ packages: resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} engines: {node: '>=8'} - cli-boxes@3.0.0: - resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} - engines: {node: '>=10'} - cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1308,8 +1271,9 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} - common-ancestor-path@1.0.1: - resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} + common-ancestor-path@2.0.0: + resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==} + engines: {node: '>= 18'} cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} @@ -1328,6 +1292,9 @@ packages: css-select@5.2.2: resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + css-selector-parser@3.3.0: + resolution: {integrity: sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==} + css-tree@2.2.1: resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} @@ -1385,10 +1352,6 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - deterministic-object-hash@2.0.2: - resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} - engines: {node: '>=18'} - devalue@5.6.4: resolution: {integrity: sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==} @@ -1399,6 +1362,10 @@ packages: resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} engines: {node: '>=0.3.1'} + direction@2.0.1: + resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==} + hasBin: true + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -1422,9 +1389,6 @@ packages: emmet@2.4.11: resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} - emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1440,8 +1404,8 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -1449,11 +1413,6 @@ packages: esast-util-from-js@2.0.1: resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} - esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.27.3: resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} engines: {node: '>=18'} @@ -1580,6 +1539,9 @@ packages: eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + expressive-code@0.41.7: + resolution: {integrity: sha512-2wZjC8OQ3TaVEMcBtYY4Va3lo6J+Ai9jf3d4dbhURMJcU4Pbqe6EcHe424MIZI0VHUA1bR6xdpoHYi3yxokWqA==} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -1657,10 +1619,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.5.0: - resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} - engines: {node: '>=18'} - github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} @@ -1689,6 +1647,9 @@ packages: hast-util-from-parse5@8.0.3: resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + hast-util-has-property@3.0.0: + resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==} + hast-util-is-element@3.0.0: resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} @@ -1698,6 +1659,9 @@ packages: hast-util-raw@9.1.0: resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + hast-util-select@6.0.4: + resolution: {integrity: sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==} + hast-util-to-estree@3.1.3: resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} @@ -1710,6 +1674,9 @@ packages: hast-util-to-parse5@8.0.1: resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} + hast-util-to-text@4.0.2: resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} @@ -1742,9 +1709,6 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - import-meta-resolve@4.2.0: - resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -1836,10 +1800,6 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} @@ -1851,6 +1811,76 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -2107,6 +2137,9 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + ofetch@1.5.1: resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} @@ -2127,21 +2160,21 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - p-limit@6.2.0: - resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} - engines: {node: '>=18'} + p-limit@7.3.0: + resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==} + engines: {node: '>=20'} p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-queue@8.1.1: - resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==} - engines: {node: '>=18'} + p-queue@9.1.0: + resolution: {integrity: sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==} + engines: {node: '>=20'} - p-timeout@6.1.4: - resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} - engines: {node: '>=14.16'} + p-timeout@7.0.1: + resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==} + engines: {node: '>=20'} package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} @@ -2199,6 +2232,12 @@ packages: ts-node: optional: true + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + postcss-safe-parser@7.0.1: resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} engines: {node: '>=18.0'} @@ -2211,6 +2250,10 @@ packages: peerDependencies: postcss: ^8.4.29 + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + postcss-selector-parser@7.1.1: resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} @@ -2232,10 +2275,6 @@ packages: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} @@ -2287,6 +2326,9 @@ packages: regex@6.1.0: resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + rehype-expressive-code@0.41.7: + resolution: {integrity: sha512-25f8ZMSF1d9CMscX7Cft0TSQIqdwjce2gDOvQ+d/w0FovsMwrSt3ODP4P3Z7wO1jsIJ4eYyaDRnIR/27bd/EMQ==} + rehype-parse@9.0.1: resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} @@ -2354,6 +2396,11 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rolldown@1.0.0-rc.9: + resolution: {integrity: sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup@4.59.0: resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2422,10 +2469,6 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -2433,10 +2476,6 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - strnum@2.2.0: resolution: {integrity: sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==} @@ -2477,6 +2516,10 @@ packages: tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + tinyclip@0.1.12: + resolution: {integrity: sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA==} + engines: {node: ^16.14.0 || >= 17.3.0} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -2522,10 +2565,6 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - typesafe-path@0.2.2: resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} @@ -2672,19 +2711,19 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite@6.4.1: - resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@types/node': ^20.19.0 || >=22.12.0 jiti: '>=1.21.0' - less: '*' + less: ^4.0.0 lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 @@ -2712,6 +2751,49 @@ packages: yaml: optional: true + vite@8.0.0: + resolution: {integrity: sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.0.0-alpha.31 + esbuild: ^0.27.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitefu@1.1.2: resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} peerDependencies: @@ -2824,10 +2906,6 @@ packages: engines: {node: '>= 8'} hasBin: true - widest-line@5.0.0: - resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} - engines: {node: '>=18'} - word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -2836,10 +2914,6 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrap-ansi@9.0.2: - resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} - engines: {node: '>=18'} - xxhash-wasm@1.1.0: resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} @@ -2869,6 +2943,10 @@ packages: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} @@ -2881,30 +2959,11 @@ packages: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} - yocto-spinner@0.2.3: - resolution: {integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==} - engines: {node: '>=18.19'} - - yoctocolors@2.1.2: - resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} - engines: {node: '>=18'} - zimmerframe@1.1.4: resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} - zod-to-json-schema@3.25.1: - resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} - peerDependencies: - zod: ^3.25 || ^4 - - zod-to-ts@1.2.0: - resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==} - peerDependencies: - typescript: ^4.9.4 || ^5.0.2 - zod: ^3 - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -2924,7 +2983,11 @@ snapshots: '@astrojs/compiler@2.13.1': {} - '@astrojs/internal-helpers@0.7.5': {} + '@astrojs/compiler@3.0.0': {} + + '@astrojs/internal-helpers@0.8.0': + dependencies: + picomatch: 4.0.3 '@astrojs/language-server@2.16.3(prettier@3.8.1)(typescript@5.9.3)': dependencies: @@ -2951,14 +3014,13 @@ snapshots: transitivePeerDependencies: - typescript - '@astrojs/markdown-remark@6.3.10': + '@astrojs/markdown-remark@7.0.0': dependencies: - '@astrojs/internal-helpers': 0.7.5 - '@astrojs/prism': 3.3.0 + '@astrojs/internal-helpers': 0.8.0 + '@astrojs/prism': 4.0.0 github-slugger: 2.0.0 hast-util-from-html: 2.0.3 hast-util-to-text: 4.0.2 - import-meta-resolve: 4.2.0 js-yaml: 4.1.1 mdast-util-definitions: 6.0.0 rehype-raw: 7.0.0 @@ -2967,7 +3029,7 @@ snapshots: remark-parse: 11.0.0 remark-rehype: 11.1.2 remark-smartypants: 3.0.2 - shiki: 3.23.0 + shiki: 4.0.1 smol-toml: 1.6.0 unified: 11.0.5 unist-util-remove-position: 5.0.0 @@ -2977,13 +3039,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@4.3.13(astro@5.18.0(@types/node@25.3.5)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2))': + '@astrojs/mdx@5.0.0(astro@6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2))': dependencies: - '@astrojs/markdown-remark': 6.3.10 + '@astrojs/markdown-remark': 7.0.0 '@mdx-js/mdx': 3.1.1 acorn: 8.16.0 - astro: 5.18.0(@types/node@25.3.5)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2) - es-module-lexer: 1.7.0 + astro: 6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2) + es-module-lexer: 2.0.0 estree-util-visit: 2.0.0 hast-util-to-html: 9.0.5 piccolore: 0.1.3 @@ -2996,23 +3058,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/prism@3.3.0': + '@astrojs/prism@4.0.0': dependencies: prismjs: 1.30.0 - '@astrojs/rss@4.0.15': + '@astrojs/rss@4.0.15-beta.4': dependencies: fast-xml-parser: 5.4.2 piccolore: 0.1.3 + zod: 4.3.6 - '@astrojs/svelte@7.2.5(@types/node@25.3.5)(astro@5.18.0(@types/node@25.3.5)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2))(svelte@5.53.7)(typescript@5.9.3)(yaml@2.8.2)': + '@astrojs/svelte@8.0.0(@types/node@25.3.5)(astro@6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2))(lightningcss@1.32.0)(svelte@5.53.7)(typescript@5.9.3)(yaml@2.8.2)': dependencies: - '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.53.7)(vite@6.4.1(@types/node@25.3.5)(yaml@2.8.2)) - astro: 5.18.0(@types/node@25.3.5)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2) + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.7)(vite@8.0.0(@types/node@25.3.5)(esbuild@0.27.3)(yaml@2.8.2)) + astro: 6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2) svelte: 5.53.7 svelte2tsx: 0.7.51(svelte@5.53.7)(typescript@5.9.3) typescript: 5.9.3 - vite: 6.4.1(@types/node@25.3.5)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.5)(lightningcss@1.32.0)(yaml@2.8.2) transitivePeerDependencies: - '@types/node' - jiti @@ -3022,7 +3085,6 @@ snapshots: - sass-embedded - stylus - sugarss - - supports-color - terser - tsx - yaml @@ -3060,6 +3122,17 @@ snapshots: dependencies: fontkitten: 1.0.3 + '@clack/core@1.1.0': + dependencies: + sisteransi: 1.0.5 + + '@clack/prompts@1.1.0': + dependencies: + '@clack/core': 1.1.0 + sisteransi: 1.0.5 + + '@ctrl/tinycolor@4.2.0': {} + '@emmetio/abbreviation@2.3.3': dependencies: '@emmetio/scanner': 1.0.4 @@ -3083,164 +3156,97 @@ snapshots: '@emmetio/stream-reader@2.2.0': {} - '@emnapi/runtime@1.8.1': + '@emnapi/core@1.9.0': dependencies: + '@emnapi/wasi-threads': 1.2.0 tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.25.12': + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.27.3': + '@emnapi/wasi-threads@1.2.0': + dependencies: + tslib: 2.8.1 optional: true - '@esbuild/android-arm64@0.25.12': + '@esbuild/aix-ppc64@0.27.3': optional: true '@esbuild/android-arm64@0.27.3': optional: true - '@esbuild/android-arm@0.25.12': - optional: true - '@esbuild/android-arm@0.27.3': optional: true - '@esbuild/android-x64@0.25.12': - optional: true - '@esbuild/android-x64@0.27.3': optional: true - '@esbuild/darwin-arm64@0.25.12': - optional: true - '@esbuild/darwin-arm64@0.27.3': optional: true - '@esbuild/darwin-x64@0.25.12': - optional: true - '@esbuild/darwin-x64@0.27.3': optional: true - '@esbuild/freebsd-arm64@0.25.12': - optional: true - '@esbuild/freebsd-arm64@0.27.3': optional: true - '@esbuild/freebsd-x64@0.25.12': - optional: true - '@esbuild/freebsd-x64@0.27.3': optional: true - '@esbuild/linux-arm64@0.25.12': - optional: true - '@esbuild/linux-arm64@0.27.3': optional: true - '@esbuild/linux-arm@0.25.12': - optional: true - '@esbuild/linux-arm@0.27.3': optional: true - '@esbuild/linux-ia32@0.25.12': - optional: true - '@esbuild/linux-ia32@0.27.3': optional: true - '@esbuild/linux-loong64@0.25.12': - optional: true - '@esbuild/linux-loong64@0.27.3': optional: true - '@esbuild/linux-mips64el@0.25.12': - optional: true - '@esbuild/linux-mips64el@0.27.3': optional: true - '@esbuild/linux-ppc64@0.25.12': - optional: true - '@esbuild/linux-ppc64@0.27.3': optional: true - '@esbuild/linux-riscv64@0.25.12': - optional: true - '@esbuild/linux-riscv64@0.27.3': optional: true - '@esbuild/linux-s390x@0.25.12': - optional: true - '@esbuild/linux-s390x@0.27.3': optional: true - '@esbuild/linux-x64@0.25.12': - optional: true - '@esbuild/linux-x64@0.27.3': optional: true - '@esbuild/netbsd-arm64@0.25.12': - optional: true - '@esbuild/netbsd-arm64@0.27.3': optional: true - '@esbuild/netbsd-x64@0.25.12': - optional: true - '@esbuild/netbsd-x64@0.27.3': optional: true - '@esbuild/openbsd-arm64@0.25.12': - optional: true - '@esbuild/openbsd-arm64@0.27.3': optional: true - '@esbuild/openbsd-x64@0.25.12': - optional: true - '@esbuild/openbsd-x64@0.27.3': optional: true - '@esbuild/openharmony-arm64@0.25.12': - optional: true - '@esbuild/openharmony-arm64@0.27.3': optional: true - '@esbuild/sunos-x64@0.25.12': - optional: true - '@esbuild/sunos-x64@0.27.3': optional: true - '@esbuild/win32-arm64@0.25.12': - optional: true - '@esbuild/win32-arm64@0.27.3': optional: true - '@esbuild/win32-ia32@0.25.12': - optional: true - '@esbuild/win32-ia32@0.27.3': optional: true - '@esbuild/win32-x64@0.25.12': - optional: true - '@esbuild/win32-x64@0.27.3': optional: true @@ -3278,6 +3284,31 @@ snapshots: '@eslint/core': 1.1.1 levn: 0.4.1 + '@expressive-code/core@0.41.7': + dependencies: + '@ctrl/tinycolor': 4.2.0 + hast-util-select: 6.0.4 + hast-util-to-html: 9.0.5 + hast-util-to-text: 4.0.2 + hastscript: 9.0.1 + postcss: 8.5.8 + postcss-nested: 6.2.0(postcss@8.5.8) + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 + + '@expressive-code/plugin-frames@0.41.7': + dependencies: + '@expressive-code/core': 0.41.7 + + '@expressive-code/plugin-shiki@0.41.7': + dependencies: + '@expressive-code/core': 0.41.7 + shiki: 3.23.0 + + '@expressive-code/plugin-text-markers@0.41.7': + dependencies: + '@expressive-code/core': 0.41.7 + '@fontsource/jetbrains-mono@5.2.8': {} '@humanfs/core@0.19.1': {} @@ -3437,6 +3468,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@napi-rs/wasm-runtime@1.1.1': + dependencies: + '@emnapi/core': 1.9.0 + '@emnapi/runtime': 1.8.1 + '@tybys/wasm-util': 0.10.1 + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3451,6 +3489,10 @@ snapshots: '@oslojs/encoding@1.1.0': {} + '@oxc-project/runtime@0.115.0': {} + + '@oxc-project/types@0.115.0': {} + '@pagefind/darwin-arm64@1.4.0': optional: true @@ -3475,6 +3517,55 @@ snapshots: '@polka/url@1.0.0-next.29': {} + '@rolldown/binding-android-arm64@1.0.0-rc.9': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.9': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.9': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.9': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.9': {} + '@rollup/pluginutils@5.3.0(rollup@4.59.0)': dependencies: '@types/estree': 1.0.8 @@ -3617,11 +3708,6 @@ snapshots: dependencies: '@shikijs/types': 4.0.1 - '@shikijs/transformers@4.0.1': - dependencies: - '@shikijs/core': 4.0.1 - '@shikijs/types': 4.0.1 - '@shikijs/types@3.23.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 @@ -3638,27 +3724,27 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.7)(vite@6.4.1(@types/node@25.3.5)(yaml@2.8.2)))(svelte@5.53.7)(vite@6.4.1(@types/node@25.3.5)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(lightningcss@1.32.0)(yaml@2.8.2)))(svelte@5.53.7)(vite@8.0.0(@types/node@25.3.5)(esbuild@0.27.3)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.53.7)(vite@6.4.1(@types/node@25.3.5)(yaml@2.8.2)) - debug: 4.4.3 + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.7)(vite@8.0.0(@types/node@25.3.5)(esbuild@0.27.3)(yaml@2.8.2)) + obug: 2.1.1 svelte: 5.53.7 - vite: 6.4.1(@types/node@25.3.5)(yaml@2.8.2) - transitivePeerDependencies: - - supports-color + vite: 8.0.0(@types/node@25.3.5)(esbuild@0.27.3)(yaml@2.8.2) - '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.7)(vite@6.4.1(@types/node@25.3.5)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@8.0.0(@types/node@25.3.5)(esbuild@0.27.3)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.7)(vite@6.4.1(@types/node@25.3.5)(yaml@2.8.2)))(svelte@5.53.7)(vite@6.4.1(@types/node@25.3.5)(yaml@2.8.2)) - debug: 4.4.3 + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(lightningcss@1.32.0)(yaml@2.8.2)))(svelte@5.53.7)(vite@8.0.0(@types/node@25.3.5)(esbuild@0.27.3)(yaml@2.8.2)) deepmerge: 4.3.1 - kleur: 4.1.5 magic-string: 0.30.21 + obug: 2.1.1 svelte: 5.53.7 - vite: 6.4.1(@types/node@25.3.5)(yaml@2.8.2) - vitefu: 1.1.2(vite@6.4.1(@types/node@25.3.5)(yaml@2.8.2)) - transitivePeerDependencies: - - supports-color + vite: 8.0.0(@types/node@25.3.5)(esbuild@0.27.3)(yaml@2.8.2) + vitefu: 1.1.2(vite@8.0.0(@types/node@25.3.5)(esbuild@0.27.3)(yaml@2.8.2)) + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true '@types/debug@4.1.12': dependencies: @@ -3880,20 +3966,12 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - ansi-align@3.0.1: - dependencies: - string-width: 4.2.3 - ansi-regex@5.0.1: {} - ansi-regex@6.2.2: {} - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - ansi-styles@6.2.3: {} - anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -3926,62 +4004,62 @@ snapshots: transitivePeerDependencies: - supports-color - astro-pagefind@1.8.5(astro@5.18.0(@types/node@25.3.5)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2)): + astro-expressive-code@0.41.7(astro@6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2)): + dependencies: + astro: 6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2) + rehype-expressive-code: 0.41.7 + + astro-pagefind@1.8.5(astro@6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: '@pagefind/default-ui': 1.4.0 - astro: 5.18.0(@types/node@25.3.5)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2) + astro: 6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2) pagefind: 1.4.0 sirv: 3.0.2 - astro@5.18.0(@types/node@25.3.5)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2): + astro@6.0.4(@types/node@25.3.5)(lightningcss@1.32.0)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2): dependencies: - '@astrojs/compiler': 2.13.1 - '@astrojs/internal-helpers': 0.7.5 - '@astrojs/markdown-remark': 6.3.10 + '@astrojs/compiler': 3.0.0 + '@astrojs/internal-helpers': 0.8.0 + '@astrojs/markdown-remark': 7.0.0 '@astrojs/telemetry': 3.3.0 '@capsizecss/unpack': 4.0.0 + '@clack/prompts': 1.1.0 '@oslojs/encoding': 1.1.0 '@rollup/pluginutils': 5.3.0(rollup@4.59.0) - acorn: 8.16.0 aria-query: 5.3.2 axobject-query: 4.1.0 - boxen: 8.0.1 ci-info: 4.4.0 clsx: 2.1.1 - common-ancestor-path: 1.0.1 + common-ancestor-path: 2.0.0 cookie: 1.1.1 - cssesc: 3.0.0 - debug: 4.4.3 - deterministic-object-hash: 2.0.2 devalue: 5.6.4 diff: 8.0.3 dlv: 1.1.3 dset: 3.1.4 - es-module-lexer: 1.7.0 + es-module-lexer: 2.0.0 esbuild: 0.27.3 - estree-walker: 3.0.3 flattie: 1.1.1 fontace: 0.4.1 github-slugger: 2.0.0 html-escaper: 3.0.3 http-cache-semantics: 4.2.0 - import-meta-resolve: 4.2.0 js-yaml: 4.1.1 magic-string: 0.30.21 magicast: 0.5.2 mrmime: 2.0.1 neotraverse: 0.6.18 - p-limit: 6.2.0 - p-queue: 8.1.1 + obug: 2.1.1 + p-limit: 7.3.0 + p-queue: 9.1.0 package-manager-detector: 1.6.0 piccolore: 0.1.3 picomatch: 4.0.3 - prompts: 2.4.2 rehype: 13.0.2 semver: 7.7.4 - shiki: 3.23.0 + shiki: 4.0.1 smol-toml: 1.6.0 svgo: 4.0.1 + tinyclip: 0.1.12 tinyexec: 1.0.2 tinyglobby: 0.2.15 tsconfck: 3.1.6(typescript@5.9.3) @@ -3990,14 +4068,11 @@ snapshots: unist-util-visit: 5.1.0 unstorage: 1.17.4 vfile: 6.0.3 - vite: 6.4.1(@types/node@25.3.5)(yaml@2.8.2) - vitefu: 1.1.2(vite@6.4.1(@types/node@25.3.5)(yaml@2.8.2)) + vite: 7.3.1(@types/node@25.3.5)(lightningcss@1.32.0)(yaml@2.8.2) + vitefu: 1.1.2(vite@7.3.1(@types/node@25.3.5)(lightningcss@1.32.0)(yaml@2.8.2)) xxhash-wasm: 1.1.0 - yargs-parser: 21.1.1 - yocto-spinner: 0.2.3 - zod: 3.25.76 - zod-to-json-schema: 3.25.1(zod@3.25.76) - zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76) + yargs-parser: 22.0.0 + zod: 4.3.6 optionalDependencies: sharp: 0.34.5 transitivePeerDependencies: @@ -4046,21 +4121,10 @@ snapshots: balanced-match@4.0.4: {} - base-64@1.0.0: {} + bcp-47-match@2.0.3: {} boolbase@1.0.0: {} - boxen@8.0.1: - dependencies: - ansi-align: 3.0.1 - camelcase: 8.0.0 - chalk: 5.6.2 - cli-boxes: 3.0.0 - string-width: 7.2.0 - type-fest: 4.41.0 - widest-line: 5.0.0 - wrap-ansi: 9.0.2 - brace-expansion@5.0.4: dependencies: balanced-match: 4.0.4 @@ -4069,12 +4133,8 @@ snapshots: dependencies: fill-range: 7.1.1 - camelcase@8.0.0: {} - ccount@2.0.1: {} - chalk@5.6.2: {} - character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -4093,8 +4153,6 @@ snapshots: ci-info@4.4.0: {} - cli-boxes@3.0.0: {} - cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -4115,7 +4173,7 @@ snapshots: commander@11.1.0: {} - common-ancestor-path@1.0.1: {} + common-ancestor-path@2.0.0: {} cookie-es@1.2.2: {} @@ -4139,6 +4197,8 @@ snapshots: domutils: 3.2.2 nth-check: 2.1.1 + css-selector-parser@3.3.0: {} + css-tree@2.2.1: dependencies: mdn-data: 2.0.28 @@ -4177,12 +4237,7 @@ snapshots: destr@2.0.5: {} - detect-libc@2.1.2: - optional: true - - deterministic-object-hash@2.0.2: - dependencies: - base-64: 1.0.0 + detect-libc@2.1.2: {} devalue@5.6.4: {} @@ -4192,6 +4247,8 @@ snapshots: diff@8.0.3: {} + direction@2.0.1: {} + dlv@1.1.3: {} dom-serializer@2.0.0: @@ -4219,8 +4276,6 @@ snapshots: '@emmetio/abbreviation': 2.3.3 '@emmetio/css-abbreviation': 2.1.8 - emoji-regex@10.6.0: {} - emoji-regex@8.0.0: {} entities@4.5.0: {} @@ -4229,7 +4284,7 @@ snapshots: entities@7.0.1: {} - es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} esast-util-from-estree@2.0.0: dependencies: @@ -4245,35 +4300,6 @@ snapshots: esast-util-from-estree: 2.0.0 vfile-message: 4.0.3 - esbuild@0.25.12: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.12 - '@esbuild/android-arm': 0.25.12 - '@esbuild/android-arm64': 0.25.12 - '@esbuild/android-x64': 0.25.12 - '@esbuild/darwin-arm64': 0.25.12 - '@esbuild/darwin-x64': 0.25.12 - '@esbuild/freebsd-arm64': 0.25.12 - '@esbuild/freebsd-x64': 0.25.12 - '@esbuild/linux-arm': 0.25.12 - '@esbuild/linux-arm64': 0.25.12 - '@esbuild/linux-ia32': 0.25.12 - '@esbuild/linux-loong64': 0.25.12 - '@esbuild/linux-mips64el': 0.25.12 - '@esbuild/linux-ppc64': 0.25.12 - '@esbuild/linux-riscv64': 0.25.12 - '@esbuild/linux-s390x': 0.25.12 - '@esbuild/linux-x64': 0.25.12 - '@esbuild/netbsd-arm64': 0.25.12 - '@esbuild/netbsd-x64': 0.25.12 - '@esbuild/openbsd-arm64': 0.25.12 - '@esbuild/openbsd-x64': 0.25.12 - '@esbuild/openharmony-arm64': 0.25.12 - '@esbuild/sunos-x64': 0.25.12 - '@esbuild/win32-arm64': 0.25.12 - '@esbuild/win32-ia32': 0.25.12 - '@esbuild/win32-x64': 0.25.12 - esbuild@0.27.3: optionalDependencies: '@esbuild/aix-ppc64': 0.27.3 @@ -4466,6 +4492,13 @@ snapshots: eventemitter3@5.0.4: {} + expressive-code@0.41.7: + dependencies: + '@expressive-code/core': 0.41.7 + '@expressive-code/plugin-frames': 0.41.7 + '@expressive-code/plugin-shiki': 0.41.7 + '@expressive-code/plugin-text-markers': 0.41.7 + extend@3.0.2: {} fast-deep-equal@3.1.3: {} @@ -4534,8 +4567,6 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.5.0: {} - github-slugger@2.0.0: {} glob-parent@5.1.2: @@ -4582,6 +4613,10 @@ snapshots: vfile-location: 5.0.3 web-namespaces: 2.0.1 + hast-util-has-property@3.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-is-element@3.0.0: dependencies: '@types/hast': 3.0.4 @@ -4606,6 +4641,24 @@ snapshots: web-namespaces: 2.0.1 zwitch: 2.0.4 + hast-util-select@6.0.4: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + bcp-47-match: 2.0.3 + comma-separated-tokens: 2.0.3 + css-selector-parser: 3.3.0 + devlop: 1.1.0 + direction: 2.0.1 + hast-util-has-property: 3.0.0 + hast-util-to-string: 3.0.1 + hast-util-whitespace: 3.0.0 + nth-check: 2.1.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + hast-util-to-estree@3.1.3: dependencies: '@types/estree': 1.0.8 @@ -4671,6 +4724,10 @@ snapshots: web-namespaces: 2.0.1 zwitch: 2.0.4 + hast-util-to-string@3.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-text@4.0.2: dependencies: '@types/hast': 3.0.4 @@ -4714,8 +4771,6 @@ snapshots: ignore@7.0.5: {} - import-meta-resolve@4.2.0: {} - imurmurhash@0.1.4: {} inline-style-parser@0.2.7: {} @@ -4783,8 +4838,6 @@ snapshots: dependencies: json-buffer: 3.0.1 - kleur@3.0.3: {} - kleur@4.1.5: {} known-css-properties@0.37.0: {} @@ -4794,6 +4847,55 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + lilconfig@2.1.0: {} linkify-it@5.0.0: @@ -5311,6 +5413,8 @@ snapshots: dependencies: boolbase: 1.0.0 + obug@2.1.1: {} + ofetch@1.5.1: dependencies: destr: 2.0.5 @@ -5340,7 +5444,7 @@ snapshots: dependencies: yocto-queue: 0.1.0 - p-limit@6.2.0: + p-limit@7.3.0: dependencies: yocto-queue: 1.2.2 @@ -5348,12 +5452,12 @@ snapshots: dependencies: p-limit: 3.1.0 - p-queue@8.1.1: + p-queue@9.1.0: dependencies: eventemitter3: 5.0.4 - p-timeout: 6.1.4 + p-timeout: 7.0.1 - p-timeout@6.1.4: {} + p-timeout@7.0.1: {} package-manager-detector@1.6.0: {} @@ -5412,6 +5516,11 @@ snapshots: optionalDependencies: postcss: 8.5.8 + postcss-nested@6.2.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + postcss-safe-parser@7.0.1(postcss@8.5.8): dependencies: postcss: 8.5.8 @@ -5420,6 +5529,11 @@ snapshots: dependencies: postcss: 8.5.8 + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 @@ -5437,11 +5551,6 @@ snapshots: prismjs@1.30.0: {} - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - property-information@7.1.0: {} punycode.js@2.3.1: {} @@ -5497,6 +5606,10 @@ snapshots: dependencies: regex-utilities: 2.3.0 + rehype-expressive-code@0.41.7: + dependencies: + expressive-code: 0.41.7 + rehype-parse@9.0.1: dependencies: '@types/hast': 3.0.4 @@ -5622,6 +5735,27 @@ snapshots: reusify@1.1.0: {} + rolldown@1.0.0-rc.9: + dependencies: + '@oxc-project/types': 0.115.0 + '@rolldown/pluginutils': 1.0.0-rc.9 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.9 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.9 + '@rolldown/binding-darwin-x64': 1.0.0-rc.9 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.9 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.9 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.9 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.9 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.9 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.9 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.9 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.9 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.9 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.9 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.9 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.9 + rollup@4.59.0: dependencies: '@types/estree': 1.0.8 @@ -5754,12 +5888,6 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@7.2.0: - dependencies: - emoji-regex: 10.6.0 - get-east-asian-width: 1.5.0 - strip-ansi: 7.2.0 - stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -5769,10 +5897,6 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.2.2 - strnum@2.2.0: {} style-to-js@1.1.21: @@ -5837,6 +5961,8 @@ snapshots: tiny-inflate@1.0.3: {} + tinyclip@0.1.12: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -5869,8 +5995,6 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@4.41.0: {} - typesafe-path@0.2.2: {} typescript-auto-import-cache@0.3.6: @@ -5994,9 +6118,9 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@6.4.1(@types/node@25.3.5)(yaml@2.8.2): + vite@7.3.1(@types/node@25.3.5)(lightningcss@1.32.0)(yaml@2.8.2): dependencies: - esbuild: 0.25.12 + esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.8 @@ -6005,11 +6129,30 @@ snapshots: optionalDependencies: '@types/node': 25.3.5 fsevents: 2.3.3 + lightningcss: 1.32.0 + yaml: 2.8.2 + + vite@8.0.0(@types/node@25.3.5)(esbuild@0.27.3)(yaml@2.8.2): + dependencies: + '@oxc-project/runtime': 0.115.0 + lightningcss: 1.32.0 + picomatch: 4.0.3 + postcss: 8.5.8 + rolldown: 1.0.0-rc.9 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 25.3.5 + esbuild: 0.27.3 + fsevents: 2.3.3 yaml: 2.8.2 - vitefu@1.1.2(vite@6.4.1(@types/node@25.3.5)(yaml@2.8.2)): + vitefu@1.1.2(vite@7.3.1(@types/node@25.3.5)(lightningcss@1.32.0)(yaml@2.8.2)): + optionalDependencies: + vite: 7.3.1(@types/node@25.3.5)(lightningcss@1.32.0)(yaml@2.8.2) + + vitefu@1.1.2(vite@8.0.0(@types/node@25.3.5)(esbuild@0.27.3)(yaml@2.8.2)): optionalDependencies: - vite: 6.4.1(@types/node@25.3.5)(yaml@2.8.2) + vite: 8.0.0(@types/node@25.3.5)(esbuild@0.27.3)(yaml@2.8.2) volar-service-css@0.0.68(@volar/language-service@2.4.28): dependencies: @@ -6116,10 +6259,6 @@ snapshots: dependencies: isexe: 2.0.0 - widest-line@5.0.0: - dependencies: - string-width: 7.2.0 - word-wrap@1.2.5: {} wrap-ansi@7.0.0: @@ -6128,12 +6267,6 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@9.0.2: - dependencies: - ansi-styles: 6.2.3 - string-width: 7.2.0 - strip-ansi: 7.2.0 - xxhash-wasm@1.1.0: {} y18n@5.0.8: {} @@ -6161,6 +6294,8 @@ snapshots: yargs-parser@21.1.1: {} + yargs-parser@22.0.0: {} + yargs@17.7.2: dependencies: cliui: 8.0.1 @@ -6175,23 +6310,8 @@ snapshots: yocto-queue@1.2.2: {} - yocto-spinner@0.2.3: - dependencies: - yoctocolors: 2.1.2 - - yoctocolors@2.1.2: {} - zimmerframe@1.1.4: {} - zod-to-json-schema@3.25.1(zod@3.25.76): - dependencies: - zod: 3.25.76 - - zod-to-ts@1.2.0(typescript@5.9.3)(zod@3.25.76): - dependencies: - typescript: 5.9.3 - zod: 3.25.76 - - zod@3.25.76: {} + zod@4.3.6: {} zwitch@2.0.4: {} diff --git a/src/components/CopyButton.svelte b/src/components/CopyButton.svelte deleted file mode 100644 index 27a2b5b..0000000 --- a/src/components/CopyButton.svelte +++ /dev/null @@ -1,61 +0,0 @@ - - - - - diff --git a/src/components/FileTabs.svelte b/src/components/FileTabs.svelte new file mode 100644 index 0000000..ef40995 --- /dev/null +++ b/src/components/FileTabs.svelte @@ -0,0 +1,317 @@ + + +
+
+ +
+ +
+ {#each files as file, i (i)} + + {/each} +
+
+
+
+ {@render children()} +
+
+ + diff --git a/src/components/blog/Post.svelte b/src/components/blog/Post.svelte index bfd5d58..80d6307 100644 --- a/src/components/blog/Post.svelte +++ b/src/components/blog/Post.svelte @@ -1,20 +1,14 @@ @@ -127,6 +107,7 @@ font-weight: 400; font-size: 0.75em; margin-inline-start: 1ch; + white-space: nowrap; } .meta { diff --git a/src/components/blog/PostList.svelte b/src/components/blog/PostList.svelte index a096014..2445848 100644 --- a/src/components/blog/PostList.svelte +++ b/src/components/blog/PostList.svelte @@ -26,10 +26,10 @@
    - {#each posts as post (post.slug)} + {#each posts as post (post.id)}
  • - {post.data.title} + {post.data.title} {#if post.data.minutesRead} // {post.data.minutesRead} {/if} @@ -56,7 +56,7 @@

    {post.data.description}

    {/if} {#if showReadMore} - Read more ⤴ + Read more ⤴ {/if}
  • {/each} diff --git a/src/components/blog/index.svelte b/src/components/blog/index.svelte index 9ffa728..7af96dd 100644 --- a/src/components/blog/index.svelte +++ b/src/components/blog/index.svelte @@ -92,7 +92,7 @@ }); function matchesSearch(post: Post) { - const postUrl = `/blog/${post.slug}/`; + const postUrl = `/blog/${post.id}/`; if (!searchQuery.trim()) return true; if (!pagefind) { return post.data.title.toLowerCase().includes(searchQuery.toLowerCase()); diff --git a/src/content/config.ts b/src/content.config.ts similarity index 63% rename from src/content/config.ts rename to src/content.config.ts index 3772023..dea7495 100644 --- a/src/content/config.ts +++ b/src/content.config.ts @@ -1,7 +1,9 @@ -import { defineCollection, z } from "astro:content"; +import { glob } from "astro/loaders"; +import { z } from "astro/zod"; +import { defineCollection } from "astro:content"; const blog = defineCollection({ - type: "content", + loader: glob({ base: `${import.meta.dirname}/content/blog`, pattern: "**/*.{md,mdx}" }), schema: z.object({ title: z.string(), date: z.date(), @@ -11,7 +13,7 @@ const blog = defineCollection({ }); const project = defineCollection({ - type: "content", + loader: glob({ base: `${import.meta.dirname}/content/project`, pattern: "**/*.{md,mdx}" }), schema: z.object({ name: z.string(), tagLine: z.string(), diff --git a/src/content/blog/announcing-tinc.mdx b/src/content/blog/announcing-tinc.mdx new file mode 100644 index 0000000..1ba68ce --- /dev/null +++ b/src/content/blog/announcing-tinc.mdx @@ -0,0 +1,372 @@ +--- +title: Announcing Tinc +date: 2026-03-12 +description: A gRPC to REST transcoding library for Rust +tags: ["crate", "grpc", "api", "rust", "openapi"] +--- + +import FileTabs from "@components/FileTabs.svelte"; + +Designing high quality APIs is hard, and it's often just as hard to accurately +document them. [tinc](https://docs.rs/tinc) is a library designed to help with +API design supporting both gRPC and REST APIs, with minimal boilerplate and code +duplication. + +## Introduction + +Here's the premise, we have a single endpoint on our server `/ping` which accepts a `POST` request where the body is a JSON object. +This object must contain a `message` field which is a string and starts with the prefix `hello:`. The server will respond with a +JSON object containing a `response` field which is a string. + +Here's how we could implement this in Rust using the [`axum`](https://docs.rs/axum) web framework. + + +```rs +::: anchor +rust-first-example +::: links +PingRequest@13,14 -> :2/PingRequest/ +PingResponse@14,19 -> :7/PingResponse/ +message@15,20 -> :3/message/ +response@20 -> :8/response/ +::: +#[derive(Deserialize)] +struct PingRequest { + message: String, +} + +#[derive(Serialize)] +struct PingResponse { + response: String, +} + +// POST /ping +async fn ping( + Json(req): Json, +) -> Result, StatusCode> { + if !req.message.starts_with("hello:") { + return Err(StatusCode::BAD_REQUEST); + } + + Ok(Json(PingResponse { + response: format!("Pong: {}", req.message), + })) +} +``` + + +## OpenAPI + +[OpenAPI](https://www.openapis.org/) is a specification for describing REST APIs. + +An OpenAPI schema is either a JSON or YAML document that contains documentation, metadata, +payload structures, and other information about the API and its endpoints. + +Here's the above API described as a JSON document. + + +```json +::: anchor +openapi-code-example +::: collapse +11 +21 +41 +53 +::: hover +info@3: Metadata about the API - title, version, description, contact info +paths@7: Defines all API endpoints - maps URL paths to their operations +post@9: HTTP method for this endpoint - POST, GET, PUT, DELETE, etc. +requestBody@11: Describes the expected request payload +$ref@16: JSON Reference - points to a reusable schema definition +responses@21: Maps HTTP status codes to their response descriptions +components@39: Reusable definitions for schemas, parameters, and responses +schemas@40: Object type definitions that can be referenced via `$ref` +pattern@49: Regex pattern the string value must match +::: links +#/components/schemas/PingRequest: #openapi-code-example:41/"PingRequest"/ +#/components/schemas/PingResponse: #openapi-code-example:53/"PingResponse"/ +::: +{ + "openapi": "3.0.3", + "info": { + "title": "Ping API", + "version": "1.0.0" + }, + "paths": { + "/ping": { + "post": { + "summary": "Post a ping message", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PingRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PingResponse" + } + } + } + }, + "400": { + "description": "Message must start with hello" + } + } + } + } + }, + "components": { + "schemas": { + "PingRequest": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "pattern": "^hello:" + } + } + }, + "PingResponse": { + "type": "object", + "res alsquired": [ + "response" + ], + "properties": { + "response": { + "type": "string" + } + } + } + } + } +} +``` + + +It may seem a bit overwhelming at first, but there are only a few things that are important +for our example. + +1. [`paths`](#openapi-code-example:7/"paths"/) where we define the `/ping` endpoint. +2. [`components`](#openapi-code-example:39/"components"/) where we define the `PingRequest` and `PingResponse` payload structures. +3. [`pattern`](#openapi-code-example:49/"pattern"/) where we add our `hello:` requirement +to the [`message`](#openapi-code-example:47-50) field. + +Having a well defined OpenAPI document is very useful for communicating with other developers about the API. +There are documentation generators like [Swagger UI](https://swagger.io/tools/swagger-ui/) or +[Spotlight Elements](https://elements-demo.stoplight.io/) that can convert the spec into a +nice looking documentation [website](https://elements-demo.stoplight.io/?spec=https://haste.potat.app/raw/60a0). + +You can also generate client-side libraries using tools like +[OpenAPI generator](https://github.com/OpenAPITools/openapi-generator), allowing for +consumers of the API to interact with it with bindings in their preferred language. + +## Code-first approach + +Its often too annoying to maintain both an OpenAPI document aswell as the code to implement it. + +One way to avoid maintaining both is to describe your API in terms of code and have the framework +generate the OpenAPI document for you. + +In Rust a popular library for this is [`utoipa`](https://docs.rs/utoipa/latest/utoipa/) +which allows us to annotate our structs and functions to describe the API. + + +```rs +::: anchor +rust-example-utoipa +::: links +ping@3 -> :30/ping/ +PingRequest@4,24,31 -> :10/PingRequest/ +PingResponse@4,26,32,37 -> :16/PingResponse/ +message@33,38 -> :12/message/ +response@38 -> :17/response/ +::: +#[derive(OpenApi)] +#[openapi( + paths(ping), + components(schemas(PingRequest, PingResponse)), + info(title = "Ping API", version = "1.0.0") +)] +struct ApiDoc; + +#[derive(Deserialize, ToSchema)] +struct PingRequest { + #[schema(pattern = r"^hello:")] + message: String, +} + +#[derive(Serialize, ToSchema)] +struct PingResponse { + response: String, +} + +#[utoipa::path( + post, + path = "/ping", + summary = "Post a ping message", + request_body = PingRequest, + responses( + (status = 200, description = "Successful response", body = PingResponse), + (status = 400, description = "Message must start with `hello:`") + ) +)] +async fn ping( + Json(req): Json, +) -> Result, StatusCode> { + if !req.message.starts_with("hello:") { + return Err(StatusCode::BAD_REQUEST); + } + + Ok(Json(PingResponse { + response: format!("Pong: {}", req.message), + })) +} +``` + + +This is called a "code first" approach as the **code** becomes the source of truth for the API. + +With `utoipa` we can access the generated OpenAPI document with the following: + +```rs +::: links +ApiDoc@1 -> #rust-example-utoipa:7/ApiDoc/ +::: +let spec = ApiDoc::openapi(); +let spec_json = serde_json::to_string(&spec).unwrap(); +``` + +## Schema-first approach + +The alternative to a code-first approach is a schema-first approach. By defining your API +in a schema you can then generate mock code to implement the API. + +This is the approach taken by tinc. However instead of OpenAPI being the source of truth, +we use [protobuf](https://protobuf.dev/) and [gRPC](https://grpc.io/) to define the API. + +Protobuf is a language-neutral, platform-neutral, extensible mechanism for serializing structured data. +gRPC extends on this by providing a way to define RPC services using protobuf. + +Tinc takes that one step further by providing a way to define REST endpoints for gRPC methods. As well as +allowing you to specify custom validation logic for the REST and gRPC endpoints. + +Here's the same API except described using tinc. + + + +```protobuf +::: anchor +tinc-proto-example +::: links +PingRequest@20 -> :7/PingRequest/ +PingResponse@20 -> :14/PingResponse/ +::: +syntax = "proto3"; + +import "tinc/annotations.proto"; + +package ping; + +message PingRequest { + string message = 1 [(tinc.field).constraint.string = { + match: "^hello:" + }]; +} + +message PingResponse { + string response = 1; +} + +service PingService { + rpc Ping(PingRequest) returns PingResponse { + option (tinc.method).endpoint = {post: "/ping"}; + }; +} +``` + +```rs +::: anchor +tinc-rust-example +::: links +ping@3 -> #tinc-proto-example:5/ping/ +MyPingService@10 -> :7/MyPingService/ +PingService@10 -> #tinc-proto-example:17/PingService/ +ping@11 -> #tinc-proto-example:18/\bPing\b/ +PingRequest@13 -> #tinc-proto-example:7/PingRequest/ +PingResponse@14,17 -> #tinc-proto-example:13/PingResponse/ +message@18 -> #tinc-proto-example:8/message/ +response@18 -> #tinc-proto-example:14/response/ +::: +pub mod pb { + // definitions are generated at compile time + tinc::include_proto!("ping"); +} + +#[derive(Debug, Default)] +pub struct MyPingService {} + +#[tonic::async_trait] +impl PingService for MyPingService { + async fn ping( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + + Ok(Response::new(PingResponse { + response: format!("Pong: {}", req.message), + })) + } +} +``` + + + +Accessing the OpenAPI spec can be done via the following code. + +```rs +::: links +MyPingService@1 -> #tinc-rust-example:7/MyPingService/ +::: +let svc = PingServiceTinc::new(MyPingService {}); +let spec = svc.openapi(); +let spec_json = serde_json::to_string(&spec).unwrap(); +``` + +Here are some of the choices made when creating tinc. + +- **Schema vs code**: Tinc goes for a schema-first approach because the developer can easily +ensure that all routes conform to the same schema and have consistent validation rules. + +- **Protobuf vs OpenAPI**: Protobuf often has better generators than OpenAPI. +Protobuf also gives the ability to use gRPC clients aswell as REST clients. + +- **Validation logic is enforced at the request level**: + The developer can define validation logic directly in the protobuf file and tinc enforces it for every HTTP and gRPC request. + +- **No proxy layer**: Other gRPC-REST transcoding solutions like [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) +require you to run a proxy server which translates the gRPC requests into REST requests. Since +tinc is embedded into your application this is not needed. + +- **Custom annotation instead of google's**: Google has their own custom annotation system for gRPC-REST transcoding (used by grpc-gateway), +however this is not as flexible as tinc and doesn't allow for custom validation logic. + +- **Non-optional protobuf3**: Technically all fields in protobuf3 are optional, however for REST transcoding all fields must be present +unless they are marked as optional in the schema. + +- **Stop on last error**: By default tinc will accumulate all errors and return a set of all errors instead of stopping on the first error. + +If you find tinc useful or have any feedback, please let me know on [GitHub](https://github.com/troymoder/crates). diff --git a/src/lib/posts.ts b/src/lib/posts.ts index 2018db5..689a272 100644 --- a/src/lib/posts.ts +++ b/src/lib/posts.ts @@ -1,4 +1,4 @@ -import { type CollectionEntry, getCollection } from "astro:content"; +import { type CollectionEntry, getCollection, render } from "astro:content"; export type Post = CollectionEntry<"blog"> & { data: { @@ -10,7 +10,7 @@ export async function getPosts() { const posts: Post[] = await getCollection("blog"); for (const post of posts) { - const { remarkPluginFrontmatter } = await post.render(); + const { remarkPluginFrontmatter } = await render(post); post.data.minutesRead = remarkPluginFrontmatter.minutesRead; } diff --git a/src/pages/blog/[slug].astro b/src/pages/blog/[slug].astro index 5c50901..7297d00 100644 --- a/src/pages/blog/[slug].astro +++ b/src/pages/blog/[slug].astro @@ -1,18 +1,18 @@ --- import BlogPost from "@components/blog/Post.svelte"; import Base from "@layouts/Base.astro"; -import { getCollection } from "astro:content"; +import { getCollection, render } from "astro:content"; export async function getStaticPaths() { const posts = await getCollection("blog"); return posts.map(post => ({ - params: { slug: post.slug }, + params: { slug: post.id }, props: { post }, })); } const { post } = Astro.props; -const { Content, headings, remarkPluginFrontmatter } = await post.render(); +const { Content, headings, remarkPluginFrontmatter } = await render(post); --- diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts index 26593a7..393dda5 100644 --- a/src/pages/rss.xml.ts +++ b/src/pages/rss.xml.ts @@ -1,9 +1,6 @@ import rss from "@astrojs/rss"; import type { APIRoute } from "astro"; import { getCollection } from "astro:content"; -import MarkdownIt from "markdown-it"; -import sanitizeHtml from "sanitize-html"; -const parser = new MarkdownIt(); export const GET: APIRoute = async (context) => { const blog = await getCollection("blog"); @@ -18,8 +15,7 @@ export const GET: APIRoute = async (context) => { description: post.data.description, author: "Troy Benson", categories: post.data.tags, - content: sanitizeHtml(parser.render(post.body)), - link: `/blog/${post.slug}`, + link: `/blog/${post.id}`, })), }); }; diff --git a/src/styles/global.css b/src/styles/global.css index 4747553..20387c1 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -78,19 +78,27 @@ a:hover, a:focus-visible { ul { list-style: none; - padding: 0; + padding: 0.25em; } ul li { padding: 0; -} - -ul > li::before { - content: "* "; - font-weight: bold; - color: var(--color-text-muted); - top: 0.05ch; position: relative; + margin-block-end: 0.5em; + &::before { + content: "*"; + font-weight: bold; + color: var(--color-text-muted); + position: absolute; + left: -1.75ch; + } + > p:first-child { + margin-block-start: 0; + display: inline; + } + > p:last-child { + margin-block-end: 0; + } } ol { @@ -111,74 +119,13 @@ code { font-size: 0.9em; } -pre { - background-color: var(--color-background-pre); - line-height: 1.4; - overflow-x: auto; - padding: 1em; - border-radius: var(--border-radius); - border: 1px solid var(--color-border); - position: relative; - margin-left: -1.05em; - width: calc(100% + 2em); -} - -pre code { - background-color: transparent; - color: inherit; - font-size: 100%; - padding: 0; -} - -pre .line { - display: inline-block; - width: calc(100% + 2em); - margin: 0 -1em; - padding: 0 1em; -} - -pre .line.highlighted { - background-color: rgba(249, 200, 155, 0.3); -} - -pre .line.diff.add { - background-color: rgba(46, 160, 67, 0.2); -} - -pre .line.diff.remove { - background-color: rgba(248, 81, 73, 0.2); -} - -pre .line.focused { - background-color: rgba(249, 200, 155, 0.4); -} - -pre:has(.line.focused) .line:not(.focused) { - opacity: 0.5; - filter: blur(0.8px); - transition: opacity 0.3s, filter 0.3s; -} - -pre:has(.line.focused):hover .line:not(.focused) { - opacity: 1; - filter: none; -} - -pre .line.highlighted.error { - background-color: rgba(196, 76, 65, 0.3); -} - -pre .line.highlighted.warning { - background-color: rgba(244, 225, 103, 0.45); -} - -pre .line.highlighted.info { - background-color: rgba(91, 201, 238, 0.3); +.expressive-code { + margin-left: -2em; } .content { margin-block-end: 4em; margin-inline: auto; max-inline-size: var(--content-max-width); - padding-inline: 2.5em; + padding-inline: 2em 0.5em; }