Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@weave-md/basic

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@weave-md/basic - npm Package Compare versions

Comparing version
0.3.0-alpha.0
to
0.3.1-alpha.0
+21
LICENSE
MIT License
Copyright (c) 2025 weavepage
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+42
-4

@@ -52,2 +52,3 @@ import { extractNodeLinks } from '@weave-md/validate';

const inlineNodeIds = new Set();
const stretchNodeIds = new Set();
const footnoteNodeIds = new Set();

@@ -57,3 +58,3 @@ const findNodeRefs = (node) => {

const display = node.display || 'footnote';
if (display === 'inline' || display === 'overlay' || display === 'footnote') {
if (display === 'inline' || display === 'overlay' || display === 'footnote' || display === 'stretch') {
referencedNodeIds.add(node.targetId);

@@ -65,2 +66,4 @@ }

inlineNodeIds.add(node.targetId);
if (display === 'stretch')
stretchNodeIds.add(node.targetId);
if (display === 'footnote')

@@ -137,2 +140,10 @@ footnoteNodeIds.add(node.targetId);

const mainNodeLinksHandler = (ref, text) => {
// Handle stretch display mode (Nutshell-style)
if (ref.display === 'stretch') {
const targetSection = sectionsMap[ref.id];
if (targetSection) {
const displayText = text && text.trim() !== '' ? escapeHtml(text) : escapeHtml(ref.id);
return `<span class="weave-stretch-trigger" data-stretch-id="${escapeHtml(ref.id)}">${displayText}</span>`;
}
}
// Handle inline display mode

@@ -185,2 +196,22 @@ if (ref.display === 'inline') {

};
// nodeLinksHandler for stretch content (preserves stretch triggers, converts others to overlay)
const stretchNodeLinksHandler = (ref, text) => {
// Preserve stretch display mode for nested stretches
if (ref.display === 'stretch') {
const targetSection = sectionsMap[ref.id];
if (targetSection) {
const displayText = text && text.trim() !== '' ? escapeHtml(text) : escapeHtml(ref.id);
return `<span class="weave-stretch-trigger" data-stretch-id="${escapeHtml(ref.id)}">${displayText}</span>`;
}
}
// Convert other display modes to overlay
const targetSection = sectionsMap[ref.id];
if (!targetSection) {
return `<span class="weave-unsupported">${escapeHtml(text || ref.id)}</span>`;
}
if (!text || text.trim() === '') {
return `<span class="weave-overlay-anchor" data-display="overlay" data-node-id="${escapeHtml(ref.id)}" title="View ${escapeHtml(ref.id)}">${iconInformationCircle}</span>`;
}
return `<span class="weave-node-link" data-display="overlay" data-node-id="${escapeHtml(ref.id)}">${escapeHtml(text)}</span>`;
};
// Render main sections first (to establish footnote order)

@@ -211,3 +242,4 @@ const renderedSections = [];

}
// Build sectionsData for inline/overlay rendering
// Build sectionsData for inline/overlay/stretch rendering
// - Stretch content: preserves nested stretch triggers
// - Footnote/inline content: allows overlays only

@@ -219,4 +251,10 @@ // - Overlay content: no nesting allowed

if (tree) {
// Use noNestingHandler for sections used as overlays
const handler = overlayNodeIds.has(section.id) ? noNestingHandler : nestedNodeLinksHandler;
// Choose handler based on how section is used
let handler = nestedNodeLinksHandler;
if (overlayNodeIds.has(section.id)) {
handler = noNestingHandler;
}
else if (stretchNodeIds.has(section.id)) {
handler = stretchNodeLinksHandler;
}
const sectionHtml = toHtml(tree, {

@@ -223,0 +261,0 @@ renderMath: true,

+1
-1
/**
* Basic HTML/JS/CSS template for static export with footnote and overlay support
*/
export declare const HTML_TEMPLATE = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>{{TITLE}}</title>\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css\" integrity=\"sha384-n8MVd4RsNIU0tAv4ct0nTaAbDJwPJzDEaqSD1odI+WdtXRGWt2kTvGFasHpSy3SV\" crossorigin=\"anonymous\">\n <style>\n * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n line-height: 1.6;\n color: #333;\n background: #fff;\n padding: 2rem 1rem;\n }\n\n .weave-document {\n max-width: 42rem;\n margin: 0 auto;\n position: relative;\n }\n\n .weave-section {\n margin-bottom: 3rem;\n }\n\n h1 {\n font-size: 2.25rem;\n font-weight: 700;\n }\n\n h2 {\n margin-top: 1.5rem;\n margin-bottom: 1rem;\n font-size: 1.75rem;\n font-weight: 600;\n }\n\n h3 {\n margin-top: 1.5rem;\n margin-bottom: 0.75rem;\n font-size: 1.25rem;\n font-weight: 600;\n }\n\n p {\n margin-bottom: 1rem;\n }\n\n /* Node links */\n .weave-node-link {\n color: #0066cc;\n text-decoration: none;\n border-bottom: 1px solid #0066cc;\n cursor: pointer;\n position: relative;\n }\n\n .weave-node-link:hover {\n background: #f0f7ff;\n }\n\n /* Overlay anchor icon (for empty text links) */\n .weave-icon {\n width: 1.2em;\n height: 1.2em;\n vertical-align: -0.2em;\n }\n\n .weave-overlay-anchor,\n .weave-inline-anchor {\n display: inline;\n color: #0066cc;\n cursor: pointer;\n }\n\n /* Plus/minus toggle for inline anchors */\n .weave-inline-anchor .weave-icon-minus {\n display: none;\n }\n\n .weave-inline-anchor.expanded .weave-icon-plus {\n display: none;\n }\n\n .weave-inline-anchor.expanded .weave-icon-minus {\n display: inline;\n }\n\n .weave-overlay-anchor:hover .weave-icon,\n .weave-inline-anchor:hover .weave-icon {\n fill: #f0f7ff;\n }\n\n /* Inline expandable content */\n .weave-inline-trigger {\n color: #0066cc;\n text-decoration: none;\n border-bottom: 1px solid #0066cc;\n cursor: pointer;\n }\n\n .weave-inline-trigger:hover {\n background: #f0f7ff;\n }\n\n .weave-inline-trigger.expanded {\n background: #e8f4ff;\n border-bottom: 2px solid #0066cc;\n }\n\n /* Inline substitution */\n .weave-sub {\n color: #0066cc;\n text-decoration: none;\n border-bottom: 1px solid #0066cc;\n cursor: pointer;\n }\n\n .weave-sub:hover {\n background: #f0f7ff;\n }\n\n .weave-sub.expanded {\n color: inherit;\n border-bottom: none;\n cursor: default;\n background: none;\n }\n\n /* Nested subs inside expanded subs should still be styled as links */\n .weave-sub.expanded .weave-sub:not(.expanded) {\n color: #0066cc;\n border-bottom: 1px solid #0066cc;\n cursor: pointer;\n }\n\n /* Redacted style - black blocks that are still clickable */\n .weave-sub-redacted:not(.expanded) {\n color: inherit;\n border-bottom: none;\n cursor: pointer;\n background: none;\n }\n\n .weave-sub-redacted:not(.expanded):hover {\n opacity: 0.7;\n }\n\n .weave-inline-content {\n display: block;\n margin: 1rem 0;\n padding: 1rem;\n background: #f8f9fa;\n border-left: 3px solid #0066cc;\n border-radius: 4px;\n }\n\n .weave-inline-content.hidden {\n display: none;\n }\n\n .weave-inline-content p:last-child {\n margin-bottom: 0;\n }\n\n /* Footnote references */\n .weave-footnote-ref {\n font-size: 0.75em;\n vertical-align: super;\n }\n\n .weave-footnote-ref a {\n color: #0066cc;\n text-decoration: none;\n }\n\n .weave-footnote-ref a:hover {\n background: #f0f7ff;\n }\n\n /* Text-linked footnote references */\n .weave-footnote-link {\n color: #0066cc;\n text-decoration: none;\n }\n\n .weave-footnote-link-text {\n border-bottom: 1px solid #0066cc;\n }\n\n .weave-footnote-link:hover {\n background: #f0f7ff;\n }\n\n .weave-footnote-link sup {\n font-size: 0.75em;\n margin-left: 0.1em;\n }\n\n /* Footnotes section */\n .weave-footnotes-separator {\n margin: 3rem 0 2rem;\n border: none;\n border-top: 1px solid #ddd;\n }\n\n .weave-footnotes {\n font-size: 0.9em;\n color: #666;\n }\n\n .weave-footnotes-list {\n list-style: none;\n padding: 0;\n }\n\n .weave-footnote {\n display: grid;\n grid-template-columns: 2.5em 1fr;\n margin-bottom: 1rem;\n }\n\n .weave-footnote-marker {\n text-align: left;\n }\n\n .weave-footnote-backref {\n text-decoration: none;\n color: #0066cc;\n }\n\n .weave-footnote-backref:hover {\n background: #f0f7ff;\n }\n\n .weave-footnote-content {\n min-width: 0;\n }\n\n .weave-footnote-content p:first-child {\n display: inline;\n }\n\n .weave-footnote-content p + p {\n margin-top: 0.5rem;\n }\n\n /* Overlay - bigfoot-style tooltip */\n .weave-overlay {\n position: fixed;\n z-index: 10000;\n box-sizing: border-box;\n max-width: min(22rem, calc(100vw - 20px));\n display: inline-block;\n background: #fafafa;\n border-radius: 0.5em;\n border: 1px solid #c3c3c3;\n box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);\n opacity: 0;\n transform: scale(0.1) translateZ(0);\n transform-origin: 50% 0;\n transition: opacity 0.25s ease, transform 0.25s ease;\n pointer-events: none;\n }\n\n .weave-overlay.active {\n opacity: 0.97;\n transform: scale(1) translateZ(0);\n pointer-events: auto;\n }\n\n .weave-overlay.above {\n transform-origin: 50% 100%;\n }\n\n /* Tooltip arrow */\n .weave-overlay-tooltip {\n position: absolute;\n width: 0;\n height: 0;\n border-left: 10px solid transparent;\n border-right: 10px solid transparent;\n }\n\n .weave-overlay.below .weave-overlay-tooltip {\n top: -10px;\n border-bottom: 10px solid #c3c3c3;\n }\n\n .weave-overlay.below .weave-overlay-tooltip::after {\n content: '';\n position: absolute;\n top: 2px;\n left: -9px;\n border-left: 9px solid transparent;\n border-right: 9px solid transparent;\n border-bottom: 9px solid #fafafa;\n }\n\n .weave-overlay.above .weave-overlay-tooltip {\n bottom: -10px;\n border-top: 10px solid #c3c3c3;\n }\n\n .weave-overlay.above .weave-overlay-tooltip::after {\n content: '';\n position: absolute;\n bottom: 2px;\n left: -9px;\n border-left: 9px solid transparent;\n border-right: 9px solid transparent;\n border-top: 9px solid #fafafa;\n }\n\n .weave-overlay-content {\n position: relative;\n }\n\n .weave-overlay-main-wrapper {\n max-height: 15em;\n overflow: auto;\n }\n\n .weave-overlay-body {\n padding: 0.6em 0.8em;\n line-height: 1.5;\n font-size: 0.95em;\n color: #333;\n }\n\n .weave-overlay-body p {\n margin: 0;\n }\n\n .weave-overlay-body p + p {\n margin-top: 0.5em;\n }\n\n /* Math blocks */\n .weave-math-block {\n margin: 1.5rem 0;\n overflow-x: auto;\n }\n\n /* Media */\n .weave-media {\n margin: 1.5rem auto;\n text-align: center;\n }\n\n .weave-media img,\n .weave-media video,\n .weave-media iframe {\n max-width: 100%;\n width: 100%;\n height: auto;\n display: block;\n margin: 0 auto;\n }\n\n .weave-media video {\n background: #000;\n }\n\n .weave-media iframe {\n background: #000;\n }\n\n /* Fallback aspect ratio for embeds without explicit width/height */\n .weave-media iframe:not([width]):not([height]) {\n aspect-ratio: 16 / 9;\n }\n\n .weave-media figcaption {\n margin-top: 0.5rem;\n font-size: 0.9em;\n color: #666;\n font-style: italic;\n }\n\n /* Gallery Carousel */\n .weave-gallery {\n position: relative;\n overflow: hidden;\n }\n\n .weave-gallery figure {\n display: none;\n margin: 0;\n }\n\n .weave-gallery figure.active {\n display: block;\n }\n\n .weave-gallery img {\n border-radius: 4px;\n }\n\n .weave-gallery-nav {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n background: rgba(0,0,0,0.5);\n color: white;\n border: none;\n padding: 0.75rem;\n cursor: pointer;\n font-size: 1.25rem;\n border-radius: 4px;\n z-index: 10;\n }\n\n .weave-gallery-nav:hover {\n background: rgba(0,0,0,0.7);\n }\n\n .weave-gallery-prev { left: 0.5rem; }\n .weave-gallery-next { right: 0.5rem; }\n\n .weave-gallery-dots {\n display: flex;\n justify-content: center;\n gap: 0.5rem;\n margin-top: 0.75rem;\n }\n\n .weave-gallery-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #ccc;\n border: none;\n cursor: pointer;\n padding: 0;\n }\n\n .weave-gallery-dot.active {\n background: #0066cc;\n }\n\n /* Tables */\n table {\n border-collapse: collapse;\n width: 100%;\n margin: 1rem 0;\n }\n\n th, td {\n border: 1px solid #ddd;\n padding: 0.5rem 0.75rem;\n text-align: left;\n }\n\n th {\n background: #f5f5f5;\n font-weight: 600;\n }\n\n tr:nth-child(even) {\n background: #fafafa;\n }\n\n /* Code blocks */\n pre {\n background: #f5f5f5;\n padding: 1rem;\n border-radius: 4px;\n overflow-x: auto;\n margin: 1rem 0;\n }\n\n code {\n font-family: 'Monaco', 'Menlo', 'Courier New', monospace;\n font-size: 0.9em;\n }\n\n /* Preformatted - preserves spacing, matches paragraph spacing */\n .weave-preformatted {\n white-space: pre-wrap;\n margin-bottom: 1rem;\n }\n\n /* Mobile */\n @media (max-width: 640px) {\n body {\n padding: 1rem 0.75rem;\n }\n }\n </style>\n</head>\n<body>\n <main class=\"weave-document\">\n {{CONTENT}}\n </main>\n\n <!-- Overlay container -->\n <div class=\"weave-overlay\" id=\"weave-overlay\">\n <div class=\"weave-overlay-main-wrapper\">\n <div class=\"weave-overlay-body\" id=\"weave-overlay-body\"></div>\n </div>\n <div class=\"weave-overlay-tooltip\" id=\"weave-overlay-tooltip\"></div>\n </div>\n\n <script>\n // Section content lookup\n const sections = {{SECTIONS_DATA}};\n\n // Overlay handling\n const overlay = document.getElementById('weave-overlay');\n const overlayBody = document.getElementById('weave-overlay-body');\n\n let currentTrigger = null;\n\n const overlayTooltip = document.getElementById('weave-overlay-tooltip');\n\n function positionOverlay() {\n if (!currentTrigger) return;\n \n // Use getClientRects() to handle wrapped inline elements\n // Pick the last rect (end of link) for better UX\n const rects = currentTrigger.getClientRects();\n const rect = rects.length > 0 ? rects[rects.length - 1] : currentTrigger.getBoundingClientRect();\n const viewportHeight = window.innerHeight;\n \n // Use content container bounds instead of viewport for horizontal positioning\n // This keeps overlay within content area, leaving margins free for side notes\n const container = document.querySelector('.weave-document');\n const containerRect = container.getBoundingClientRect();\n \n const overlayHeight = overlay.offsetHeight;\n const overlayWidth = overlay.offsetWidth;\n \n // Trigger center is the anchor - arrow MUST point here\n const triggerCenterX = rect.left + (rect.width / 2);\n \n // Check space above and below\n const spaceBelow = viewportHeight - rect.bottom;\n const spaceAbove = rect.top;\n const showBelow = spaceBelow >= overlayHeight + 15 || spaceBelow > spaceAbove;\n \n const arrowMinEdge = 15; // min distance from arrow center to overlay edge\n const screenEdgePadding = 8; // minimum distance from screen edge for shadow visibility\n \n // Bounds: prefer container, but always keep minimum distance from screen edges\n const boundsLeft = Math.max(screenEdgePadding, containerRect.left);\n const boundsRight = Math.min(window.innerWidth - screenEdgePadding, containerRect.right);\n \n // Position overlay so arrow can reach the trigger\n // Arrow must be at triggerCenterX, and arrow must be within [arrowMinEdge, overlayWidth - arrowMinEdge]\n let leftMin = triggerCenterX - (overlayWidth - arrowMinEdge);\n let leftMax = triggerCenterX - arrowMinEdge;\n \n // Start centered on trigger\n let left = triggerCenterX - (overlayWidth / 2);\n \n // Ensure arrow can reach trigger (clamp to valid range)\n left = Math.max(leftMin, Math.min(leftMax, left));\n \n // Now clamp to container bounds\n // Clamp right first, then left (left takes priority so shadow is visible)\n if (left + overlayWidth > boundsRight) {\n left = boundsRight - overlayWidth;\n }\n if (left < boundsLeft) {\n left = boundsLeft;\n }\n \n overlay.style.left = left + 'px';\n \n // Arrow position = trigger center relative to overlay left edge\n const arrowLeftPx = triggerCenterX - left;\n overlayTooltip.style.left = arrowLeftPx + 'px';\n overlayTooltip.style.transform = 'translateX(-50%)';\n \n // Position vertically\n overlay.classList.remove('above', 'below');\n if (showBelow) {\n overlay.style.top = (rect.bottom + 10) + 'px';\n overlay.classList.add('below');\n } else {\n overlay.style.top = (rect.top - overlayHeight - 10) + 'px';\n overlay.classList.add('above');\n }\n }\n\n function openOverlay(sectionId, triggerElement) {\n const section = sections[sectionId];\n if (!section) return;\n\n overlayBody.innerHTML = section.html;\n currentTrigger = triggerElement;\n \n overlay.classList.add('active');\n positionOverlay();\n }\n\n // Reposition on scroll/resize to keep arrow attached\n window.addEventListener('scroll', () => {\n if (overlay.classList.contains('active')) {\n positionOverlay();\n }\n }, true);\n \n window.addEventListener('resize', () => {\n if (overlay.classList.contains('active')) {\n positionOverlay();\n }\n });\n\n function closeOverlay() {\n overlay.classList.remove('active');\n currentTrigger = null;\n }\n\n // Click handlers\n overlay.addEventListener('click', (e) => {\n if (e.target === overlay) closeOverlay();\n });\n\n // Escape key\n document.addEventListener('keydown', (e) => {\n if (e.key === 'Escape' && overlay.classList.contains('active')) {\n closeOverlay();\n }\n });\n\n // Click to open overlay\n document.addEventListener('click', (e) => {\n const link = e.target.closest('.weave-node-link, .weave-overlay-anchor');\n \n // Close overlay if clicking outside\n if (!link && !e.target.closest('.weave-overlay')) {\n if (overlay.classList.contains('active')) {\n closeOverlay();\n }\n return;\n }\n \n if (!link) return;\n\n const display = link.dataset.display;\n const nodeId = link.dataset.nodeId;\n\n if (display === 'overlay' && nodeId) {\n e.preventDefault();\n \n // Toggle overlay if clicking same trigger\n if (overlay.classList.contains('active') && currentTrigger === link) {\n closeOverlay();\n } else {\n openOverlay(nodeId, link);\n }\n }\n });\n\n // Inline expand/collapse handling\n document.addEventListener('click', (e) => {\n const trigger = e.target.closest('.weave-inline-trigger, .weave-inline-anchor');\n if (!trigger) return;\n\n e.preventDefault();\n \n const inlineId = trigger.dataset.inlineId;\n let content = document.getElementById('weave-inline-' + inlineId);\n \n // If content doesn't exist, create it\n if (!content) {\n const section = sections[inlineId];\n if (!section) return;\n \n content = document.createElement('div');\n content.id = 'weave-inline-' + inlineId;\n content.className = 'weave-inline-content';\n content.innerHTML = section.html;\n content.style.display = 'none';\n \n // Insert after the parent paragraph\n const paragraph = trigger.closest('p');\n if (paragraph && paragraph.parentNode) {\n paragraph.parentNode.insertBefore(content, paragraph.nextSibling);\n }\n }\n \n // Toggle visibility\n const isHidden = content.style.display === 'none';\n content.style.display = isHidden ? 'block' : 'none';\n trigger.classList.toggle('expanded', isHidden);\n });\n\n // Inline substitution click handling\n document.addEventListener('click', (e) => {\n const sub = e.target.closest('.weave-sub');\n if (!sub || sub.classList.contains('expanded')) return;\n\n e.preventDefault();\n const encodedReplacement = sub.dataset.replacementB64;\n if (encodedReplacement) {\n // Decode base64 with proper UTF-8 handling\n const binary = atob(encodedReplacement);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n const replacement = new TextDecoder().decode(bytes);\n sub.innerHTML = replacement;\n sub.classList.add('expanded');\n }\n });\n\n // Footnote backlink tracking - remember which reference was clicked\n document.addEventListener('click', (e) => {\n const link = e.target.closest('.weave-footnote-ref a, a.weave-footnote-link');\n if (!link) return;\n \n const refId = link.id;\n if (!refId) return;\n \n // Extract footnote number from the href (e.g., #fn-3 -> 3)\n const href = link.getAttribute('href');\n if (!href || !href.startsWith('#fn-')) return;\n const fnNum = href.replace('#fn-', '');\n \n // Find the backref link in the footnote and update it\n const backref = document.querySelector('#fn-' + fnNum + ' .weave-footnote-backref');\n if (backref) {\n backref.setAttribute('href', '#' + refId);\n }\n \n // Navigate to the footnote\n e.preventDefault();\n const footnoteId = 'fn-' + fnNum;\n const footnote = document.getElementById(footnoteId);\n if (footnote) {\n footnote.scrollIntoView({ behavior: 'smooth' });\n // Update URL fragment without triggering navigation\n history.pushState(null, '', '#' + footnoteId);\n }\n });\n\n // Backref click - scroll and clear hash\n document.addEventListener('click', (e) => {\n const backref = e.target.closest('.weave-footnote-backref');\n if (!backref) return;\n \n e.preventDefault();\n const href = backref.getAttribute('href');\n if (!href) return;\n \n const target = document.getElementById(href.replace('#', ''));\n if (target) {\n target.scrollIntoView({ behavior: 'smooth' });\n history.pushState(null, '', window.location.pathname + window.location.search);\n }\n });\n\n // Video start time handling\n document.querySelectorAll('video[data-start]').forEach(video => {\n const startTime = parseFloat(video.dataset.start);\n if (!isNaN(startTime)) {\n video.currentTime = startTime;\n video.addEventListener('loadedmetadata', () => {\n video.currentTime = startTime;\n }, { once: true });\n }\n });\n\n // Gallery carousel initialization\n document.querySelectorAll('.weave-gallery').forEach(gallery => {\n const figures = gallery.querySelectorAll('figure');\n if (figures.length <= 1) return;\n \n // Set first figure as active\n figures[0].classList.add('active');\n \n // Create navigation buttons\n const prevBtn = document.createElement('button');\n prevBtn.className = 'weave-gallery-nav weave-gallery-prev';\n prevBtn.innerHTML = '&#10094;';\n prevBtn.setAttribute('aria-label', 'Previous');\n \n const nextBtn = document.createElement('button');\n nextBtn.className = 'weave-gallery-nav weave-gallery-next';\n nextBtn.innerHTML = '&#10095;';\n nextBtn.setAttribute('aria-label', 'Next');\n \n gallery.insertBefore(prevBtn, gallery.firstChild);\n gallery.insertBefore(nextBtn, gallery.querySelector('figcaption') || null);\n \n // Create dots\n const dotsContainer = document.createElement('div');\n dotsContainer.className = 'weave-gallery-dots';\n figures.forEach((_, i) => {\n const dot = document.createElement('button');\n dot.className = 'weave-gallery-dot' + (i === 0 ? ' active' : '');\n dot.setAttribute('aria-label', 'Go to slide ' + (i + 1));\n dot.dataset.index = i;\n dotsContainer.appendChild(dot);\n });\n const caption = gallery.querySelector('figcaption');\n if (caption) {\n gallery.insertBefore(dotsContainer, caption);\n } else {\n gallery.appendChild(dotsContainer);\n }\n \n let current = 0;\n \n const showSlide = (index) => {\n figures.forEach((f, i) => f.classList.toggle('active', i === index));\n dotsContainer.querySelectorAll('.weave-gallery-dot').forEach((d, i) => \n d.classList.toggle('active', i === index)\n );\n current = index;\n };\n \n prevBtn.addEventListener('click', () => {\n showSlide((current - 1 + figures.length) % figures.length);\n });\n \n nextBtn.addEventListener('click', () => {\n showSlide((current + 1) % figures.length);\n });\n \n dotsContainer.addEventListener('click', (e) => {\n const dot = e.target.closest('.weave-gallery-dot');\n if (dot) showSlide(parseInt(dot.dataset.index));\n });\n });\n </script>\n</body>\n</html>\n";
export declare const HTML_TEMPLATE = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>{{TITLE}}</title>\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css\" integrity=\"sha384-n8MVd4RsNIU0tAv4ct0nTaAbDJwPJzDEaqSD1odI+WdtXRGWt2kTvGFasHpSy3SV\" crossorigin=\"anonymous\">\n <style>\n * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n body {\n font-family: 'Iowan Old Style', 'Cambria', 'Palatino Linotype', Palatino, Georgia, serif;\n font-size: 20px;\n font-weight: 300;\n line-height: 1.6;\n letter-spacing: 0.01em;\n color: #363737;\n background: #fff;\n padding: 2rem 1rem;\n }\n\n .weave-document {\n max-width: 42rem;\n margin: 0 auto;\n position: relative;\n }\n\n .weave-section {\n margin-bottom: 3rem;\n }\n\n h1, h2, h3, h4, h5, h6 {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Helvetica, Arial, 'Liberation Sans', sans-serif;\n letter-spacing: -0.02em;\n }\n\n /* Reduce top margin when header follows another header */\n h1 + h2, h2 + h3, h3 + h4, h4 + h5, h5 + h6 {\n margin-top: 4px;\n }\n\n h1 {\n font-size: 32px;\n font-weight: 700;\n margin-bottom: 20px;\n }\n\n h2 {\n margin-top: 32px;\n margin-bottom: 20px;\n font-size: 28px;\n font-weight: 600;\n }\n\n h3 {\n margin-top: 26px;\n margin-bottom: 16px;\n font-size: 26px;\n font-weight: 600;\n }\n \n h4 {\n margin-top: 22px;\n margin-bottom: 14px;\n font-size: 24px;\n font-weight: 600;\n }\n \n h5 {\n margin-top: 18px;\n margin-bottom: 12px;\n font-size: 22px;\n font-weight: 500;\n }\n \n h6 {\n margin-top: 16px;\n margin-bottom: 10px;\n font-size: 20px;\n font-weight: 500;\n }\n\n p {\n margin-bottom: 16px;\n }\n\n blockquote {\n padding-left: 20px;\n border-left: 3px solid #0066cc;\n margin-top: 20px;\n }\n\n /* Node links */\n .weave-node-link {\n color: #0066cc;\n text-decoration: none;\n border-bottom: 1px solid #0066cc;\n cursor: pointer;\n position: relative;\n }\n\n .weave-node-link:hover {\n background: #f0f7ff;\n }\n\n /* Overlay anchor icon (for empty text links) */\n .weave-icon {\n width: 1.2em;\n height: 1.2em;\n vertical-align: -0.2em;\n }\n\n .weave-overlay-anchor,\n .weave-inline-anchor {\n display: inline;\n color: #0066cc;\n cursor: pointer;\n }\n\n /* Plus/minus toggle for inline anchors */\n .weave-inline-anchor .weave-icon-minus {\n display: none;\n }\n\n .weave-inline-anchor.expanded .weave-icon-plus {\n display: none;\n }\n\n .weave-inline-anchor.expanded .weave-icon-minus {\n display: inline;\n }\n\n .weave-overlay-anchor:hover .weave-icon,\n .weave-inline-anchor:hover .weave-icon {\n fill: #f0f7ff;\n }\n\n /* Inline expandable content */\n .weave-inline-trigger {\n color: #0066cc;\n text-decoration: none;\n border-bottom: 1px solid #0066cc;\n cursor: pointer;\n }\n\n .weave-inline-trigger:hover {\n background: #f0f7ff;\n }\n\n .weave-inline-trigger.expanded {\n background: #e8f4ff;\n border-bottom: 2px solid #0066cc;\n }\n\n /* Inline substitution */\n .weave-sub {\n color: #0066cc;\n text-decoration: none;\n border-bottom: 1px solid #0066cc;\n cursor: pointer;\n }\n\n .weave-sub:hover {\n background: #f0f7ff;\n }\n\n .weave-sub.expanded {\n color: inherit;\n border-bottom: none;\n cursor: default;\n background: none;\n }\n\n /* Nested subs inside expanded subs should still be styled as links */\n .weave-sub.expanded .weave-sub:not(.expanded) {\n color: #0066cc;\n border-bottom: 1px solid #0066cc;\n cursor: pointer;\n }\n\n /* Redacted style - black blocks that are still clickable */\n .weave-sub-redacted:not(.expanded) {\n color: inherit;\n border-bottom: none;\n cursor: pointer;\n background: none;\n }\n\n .weave-sub-redacted:not(.expanded):hover {\n opacity: 0.7;\n }\n\n .weave-inline-content {\n display: block;\n margin: 1rem 0;\n padding: 1rem;\n background: #f8f9fa;\n border-left: 3px solid #0066cc;\n border-radius: 4px;\n }\n\n .weave-inline-content.hidden {\n display: none;\n }\n\n .weave-inline-content p:last-child {\n margin-bottom: 0;\n }\n\n /* Stretch/Nutshell-style expandable content */\n .weave-stretch-trigger {\n color: #2b67ad;\n text-decoration: none;\n border-bottom: 2px dotted #2b67ad;\n cursor: pointer;\n position: relative;\n }\n\n .weave-stretch-trigger:hover {\n background: rgba(43, 103, 173, 0.1);\n }\n\n .weave-stretch-trigger.expanded {\n border-bottom-style: solid;\n }\n\n .weave-stretch-bubble {\n display: block;\n position: relative;\n margin-top: 20px;\n transition: opacity 0.3s ease-out, margin 0.3s ease-out;\n opacity: 0;\n margin-bottom: 0;\n max-height: 0;\n overflow: hidden;\n }\n\n .weave-stretch-bubble.open {\n max-height: none;\n overflow: visible;\n opacity: 1;\n margin-bottom: 0.75rem;\n }\n\n .weave-stretch-content {\n background: #fff;\n border: 2px solid #ccc;\n border-radius: 1rem;\n padding: 1rem 0.8rem;\n position: relative;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n }\n\n /* Arrow pointing up to trigger (Nutshell-style) */\n .weave-stretch-arrow {\n width: 0;\n height: 0;\n border-left: 20px solid transparent;\n border-right: 20px solid transparent;\n border-bottom: 20px solid #ccc;\n position: absolute;\n top: -20px;\n pointer-events: none;\n }\n\n .weave-stretch-arrow::after {\n content: '';\n width: 0;\n height: 0;\n border-left: 20px solid transparent;\n border-right: 20px solid transparent;\n border-bottom: 20px solid #fff;\n position: absolute;\n top: 2px;\n left: -20px;\n pointer-events: none;\n }\n\n .weave-stretch-content p:first-child {\n margin-top: 0;\n }\n\n .weave-stretch-content p:last-child {\n margin-bottom: 0;\n }\n\n .weave-stretch-content ul,\n .weave-stretch-content ol {\n padding-left: 1.5rem;\n margin: 0.5rem 0;\n }\n\n /* Nested stretch bubbles - slightly different style */\n .weave-stretch-content .weave-stretch-content {\n background: #fafafa;\n border-color: #ddd;\n }\n\n .weave-stretch-content .weave-stretch-arrow {\n border-bottom-color: #ddd;\n }\n\n .weave-stretch-content .weave-stretch-arrow::after {\n border-bottom-color: #fafafa;\n }\n\n /* Level 3+ nesting */\n .weave-stretch-content .weave-stretch-content .weave-stretch-content {\n background: #f5f5f5;\n border-color: #e0e0e0;\n }\n\n .weave-stretch-content .weave-stretch-content .weave-stretch-arrow {\n border-bottom-color: #e0e0e0;\n }\n\n .weave-stretch-content .weave-stretch-content .weave-stretch-arrow::after {\n border-bottom-color: #f5f5f5;\n }\n\n /* Footnote references */\n .weave-footnote-ref {\n font-size: 0.75em;\n vertical-align: super;\n }\n\n .weave-footnote-ref a {\n color: #0066cc;\n text-decoration: none;\n }\n\n .weave-footnote-ref a:hover {\n background: #f0f7ff;\n }\n\n /* Text-linked footnote references */\n .weave-footnote-link {\n color: #0066cc;\n text-decoration: none;\n }\n\n .weave-footnote-link-text {\n border-bottom: 1px solid #0066cc;\n }\n\n .weave-footnote-link:hover {\n background: #f0f7ff;\n }\n\n .weave-footnote-link sup {\n font-size: 0.75em;\n margin-left: 0.1em;\n }\n\n /* Footnotes section */\n .weave-footnotes-separator {\n margin: 3rem 0 2rem;\n border: none;\n border-top: 1px solid #ddd;\n }\n\n .weave-footnotes {\n font-size: 0.9em;\n color: #666;\n }\n\n .weave-footnotes-list {\n list-style: none;\n padding: 0;\n }\n\n .weave-footnote {\n display: grid;\n grid-template-columns: 2.5em 1fr;\n margin-bottom: 1rem;\n }\n\n .weave-footnote-marker {\n text-align: left;\n }\n\n .weave-footnote-backref {\n text-decoration: none;\n color: #0066cc;\n }\n\n .weave-footnote-backref:hover {\n background: #f0f7ff;\n }\n\n .weave-footnote-content {\n min-width: 0;\n }\n\n .weave-footnote-content p:first-child {\n display: inline;\n }\n\n .weave-footnote-content p + p {\n margin-top: 0.5rem;\n }\n\n /* Overlay - bigfoot-style tooltip */\n .weave-overlay {\n position: fixed;\n z-index: 10000;\n box-sizing: border-box;\n max-width: min(22rem, calc(100vw - 20px));\n display: inline-block;\n background: #fafafa;\n border-radius: 0.5em;\n border: 1px solid #c3c3c3;\n box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.3);\n opacity: 0;\n transform: scale(0.1) translateZ(0);\n transform-origin: 50% 0;\n transition: opacity 0.25s ease, transform 0.25s ease;\n pointer-events: none;\n }\n\n .weave-overlay.active {\n opacity: 0.97;\n transform: scale(1) translateZ(0);\n pointer-events: auto;\n }\n\n .weave-overlay.above {\n transform-origin: 50% 100%;\n }\n\n /* Tooltip arrow */\n .weave-overlay-tooltip {\n position: absolute;\n width: 0;\n height: 0;\n border-left: 10px solid transparent;\n border-right: 10px solid transparent;\n }\n\n .weave-overlay.below .weave-overlay-tooltip {\n top: -10px;\n border-bottom: 10px solid #c3c3c3;\n }\n\n .weave-overlay.below .weave-overlay-tooltip::after {\n content: '';\n position: absolute;\n top: 2px;\n left: -9px;\n border-left: 9px solid transparent;\n border-right: 9px solid transparent;\n border-bottom: 9px solid #fafafa;\n }\n\n .weave-overlay.above .weave-overlay-tooltip {\n bottom: -10px;\n border-top: 10px solid #c3c3c3;\n }\n\n .weave-overlay.above .weave-overlay-tooltip::after {\n content: '';\n position: absolute;\n bottom: 2px;\n left: -9px;\n border-left: 9px solid transparent;\n border-right: 9px solid transparent;\n border-top: 9px solid #fafafa;\n }\n\n .weave-overlay-content {\n position: relative;\n }\n\n .weave-overlay-main-wrapper {\n max-height: 15em;\n overflow: auto;\n }\n\n .weave-overlay-body {\n padding: 0.6em 0.8em;\n line-height: 1.5;\n font-size: 0.95em;\n color: #333;\n }\n\n .weave-overlay-body p {\n margin: 0;\n }\n\n .weave-overlay-body p + p {\n margin-top: 0.5em;\n }\n\n /* Math blocks */\n .weave-math-block {\n margin: 1.5rem 0;\n overflow-x: auto;\n }\n\n /* Media */\n .weave-media {\n margin: 1.5rem auto;\n text-align: center;\n }\n\n .weave-media img,\n .weave-media video,\n .weave-media iframe {\n max-width: 100%;\n width: 100%;\n height: auto;\n display: block;\n margin: 0 auto;\n }\n\n .weave-media video {\n background: #000;\n }\n\n .weave-media iframe {\n background: #000;\n }\n\n /* Fallback aspect ratio for embeds without explicit width/height */\n .weave-media iframe:not([width]):not([height]) {\n aspect-ratio: 16 / 9;\n }\n\n .weave-media figcaption {\n margin-top: 0.5rem;\n font-size: 0.9em;\n color: #666;\n font-style: italic;\n }\n\n /* Gallery Carousel */\n .weave-gallery {\n position: relative;\n overflow: hidden;\n }\n\n .weave-gallery figure {\n display: none;\n margin: 0;\n }\n\n .weave-gallery figure.active {\n display: block;\n }\n\n .weave-gallery img {\n border-radius: 4px;\n }\n\n .weave-gallery-nav {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n background: rgba(0,0,0,0.5);\n color: white;\n border: none;\n padding: 0.75rem;\n cursor: pointer;\n font-size: 1.25rem;\n border-radius: 4px;\n z-index: 10;\n }\n\n .weave-gallery-nav:hover {\n background: rgba(0,0,0,0.7);\n }\n\n .weave-gallery-prev { left: 0.5rem; }\n .weave-gallery-next { right: 0.5rem; }\n\n .weave-gallery-dots {\n display: flex;\n justify-content: center;\n gap: 0.5rem;\n margin-top: 0.75rem;\n }\n\n .weave-gallery-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #ccc;\n border: none;\n cursor: pointer;\n padding: 0;\n }\n\n .weave-gallery-dot.active {\n background: #0066cc;\n }\n\n /* Tables */\n table {\n border-collapse: collapse;\n width: 100%;\n margin: 1rem 0;\n }\n\n th, td {\n border: 1px solid #ddd;\n padding: 0.5rem 0.75rem;\n text-align: left;\n }\n\n th {\n background: #f5f5f5;\n font-weight: 600;\n }\n\n tr:nth-child(even) {\n background: #fafafa;\n }\n\n /* Task lists */\n ul:has(input[type=\"checkbox\"]) {\n list-style: none;\n padding-left: 0;\n }\n\n ul:has(input[type=\"checkbox\"]) ul {\n list-style: none;\n padding-left: 1.5em;\n }\n\n li:has(> input[type=\"checkbox\"]) {\n margin-bottom: 0.25em;\n }\n\n input[type=\"checkbox\"] {\n margin-right: 0.5em;\n }\n\n /* Code blocks */\n pre {\n background: #f5f5f5;\n padding: 1rem;\n border-radius: 4px;\n overflow-x: auto;\n margin: 1rem 0;\n }\n\n code {\n font-family: 'Monaco', 'Menlo', 'Courier New', monospace;\n font-size: 0.9em;\n }\n\n /* Preformatted - preserves spacing, matches paragraph spacing */\n .weave-preformatted {\n white-space: pre-wrap;\n margin-bottom: 1rem;\n }\n\n /* Mobile */\n @media (max-width: 640px) {\n body {\n padding: 1rem 0.75rem;\n }\n }\n </style>\n</head>\n<body>\n <main class=\"weave-document\">\n {{CONTENT}}\n </main>\n\n <!-- Overlay container -->\n <div class=\"weave-overlay\" id=\"weave-overlay\">\n <div class=\"weave-overlay-main-wrapper\">\n <div class=\"weave-overlay-body\" id=\"weave-overlay-body\"></div>\n </div>\n <div class=\"weave-overlay-tooltip\" id=\"weave-overlay-tooltip\"></div>\n </div>\n\n <script>\n // Section content lookup\n const sections = {{SECTIONS_DATA}};\n\n // Overlay handling\n const overlay = document.getElementById('weave-overlay');\n const overlayBody = document.getElementById('weave-overlay-body');\n\n let currentTrigger = null;\n\n const overlayTooltip = document.getElementById('weave-overlay-tooltip');\n\n function positionOverlay() {\n if (!currentTrigger) return;\n \n // Use getClientRects() to handle wrapped inline elements\n // Pick the last rect (end of link) for better UX\n const rects = currentTrigger.getClientRects();\n const rect = rects.length > 0 ? rects[rects.length - 1] : currentTrigger.getBoundingClientRect();\n const viewportHeight = window.innerHeight;\n \n // Use content container bounds instead of viewport for horizontal positioning\n // This keeps overlay within content area, leaving margins free for side notes\n const container = document.querySelector('.weave-document');\n const containerRect = container.getBoundingClientRect();\n \n const overlayHeight = overlay.offsetHeight;\n const overlayWidth = overlay.offsetWidth;\n \n // Trigger center is the anchor - arrow MUST point here\n const triggerCenterX = rect.left + (rect.width / 2);\n \n // Check space above and below\n const spaceBelow = viewportHeight - rect.bottom;\n const spaceAbove = rect.top;\n const showBelow = spaceBelow >= overlayHeight + 15 || spaceBelow > spaceAbove;\n \n const arrowMinEdge = 15; // min distance from arrow center to overlay edge\n const screenEdgePadding = 8; // minimum distance from screen edge for shadow visibility\n \n // Bounds: prefer container, but always keep minimum distance from screen edges\n const boundsLeft = Math.max(screenEdgePadding, containerRect.left);\n const boundsRight = Math.min(window.innerWidth - screenEdgePadding, containerRect.right);\n \n // Position overlay so arrow can reach the trigger\n // Arrow must be at triggerCenterX, and arrow must be within [arrowMinEdge, overlayWidth - arrowMinEdge]\n let leftMin = triggerCenterX - (overlayWidth - arrowMinEdge);\n let leftMax = triggerCenterX - arrowMinEdge;\n \n // Start centered on trigger\n let left = triggerCenterX - (overlayWidth / 2);\n \n // Ensure arrow can reach trigger (clamp to valid range)\n left = Math.max(leftMin, Math.min(leftMax, left));\n \n // Now clamp to container bounds\n // Clamp right first, then left (left takes priority so shadow is visible)\n if (left + overlayWidth > boundsRight) {\n left = boundsRight - overlayWidth;\n }\n if (left < boundsLeft) {\n left = boundsLeft;\n }\n \n overlay.style.left = left + 'px';\n \n // Arrow position = trigger center relative to overlay left edge\n const arrowLeftPx = triggerCenterX - left;\n overlayTooltip.style.left = arrowLeftPx + 'px';\n overlayTooltip.style.transform = 'translateX(-50%)';\n \n // Position vertically\n overlay.classList.remove('above', 'below');\n if (showBelow) {\n overlay.style.top = (rect.bottom + 10) + 'px';\n overlay.classList.add('below');\n } else {\n overlay.style.top = (rect.top - overlayHeight - 10) + 'px';\n overlay.classList.add('above');\n }\n }\n\n function openOverlay(sectionId, triggerElement) {\n const section = sections[sectionId];\n if (!section) return;\n\n overlayBody.innerHTML = section.html;\n currentTrigger = triggerElement;\n \n overlay.classList.add('active');\n positionOverlay();\n }\n\n // Reposition on scroll/resize to keep arrow attached\n window.addEventListener('scroll', () => {\n if (overlay.classList.contains('active')) {\n positionOverlay();\n }\n }, true);\n \n window.addEventListener('resize', () => {\n if (overlay.classList.contains('active')) {\n positionOverlay();\n }\n });\n\n function closeOverlay() {\n overlay.classList.remove('active');\n currentTrigger = null;\n }\n\n // Click handlers\n overlay.addEventListener('click', (e) => {\n if (e.target === overlay) closeOverlay();\n });\n\n // Escape key\n document.addEventListener('keydown', (e) => {\n if (e.key === 'Escape' && overlay.classList.contains('active')) {\n closeOverlay();\n }\n });\n\n // Click to open overlay\n document.addEventListener('click', (e) => {\n const link = e.target.closest('.weave-node-link, .weave-overlay-anchor');\n \n // Close overlay if clicking outside\n if (!link && !e.target.closest('.weave-overlay')) {\n if (overlay.classList.contains('active')) {\n closeOverlay();\n }\n return;\n }\n \n if (!link) return;\n\n const display = link.dataset.display;\n const nodeId = link.dataset.nodeId;\n\n if (display === 'overlay' && nodeId) {\n e.preventDefault();\n \n // Toggle overlay if clicking same trigger\n if (overlay.classList.contains('active') && currentTrigger === link) {\n closeOverlay();\n } else {\n openOverlay(nodeId, link);\n }\n }\n });\n\n // Inline expand/collapse handling\n document.addEventListener('click', (e) => {\n const trigger = e.target.closest('.weave-inline-trigger, .weave-inline-anchor');\n if (!trigger) return;\n\n e.preventDefault();\n \n const inlineId = trigger.dataset.inlineId;\n let content = document.getElementById('weave-inline-' + inlineId);\n \n // If content doesn't exist, create it\n if (!content) {\n const section = sections[inlineId];\n if (!section) return;\n \n content = document.createElement('div');\n content.id = 'weave-inline-' + inlineId;\n content.className = 'weave-inline-content';\n content.innerHTML = section.html;\n content.style.display = 'none';\n \n // Insert after the parent paragraph\n const paragraph = trigger.closest('p');\n if (paragraph && paragraph.parentNode) {\n paragraph.parentNode.insertBefore(content, paragraph.nextSibling);\n }\n }\n \n // Toggle visibility\n const isHidden = content.style.display === 'none';\n content.style.display = isHidden ? 'block' : 'none';\n trigger.classList.toggle('expanded', isHidden);\n });\n\n // Inline substitution click handling\n document.addEventListener('click', (e) => {\n const sub = e.target.closest('.weave-sub');\n if (!sub || sub.classList.contains('expanded')) return;\n\n e.preventDefault();\n const encodedReplacement = sub.dataset.replacementB64;\n if (encodedReplacement) {\n // Decode base64 with proper UTF-8 handling\n const binary = atob(encodedReplacement);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n const replacement = new TextDecoder().decode(bytes);\n sub.innerHTML = replacement;\n sub.classList.add('expanded');\n }\n });\n\n // Stretch/Nutshell-style expand/collapse handling\n document.addEventListener('click', (e) => {\n const trigger = e.target.closest('.weave-stretch-trigger');\n if (!trigger) return;\n\n e.preventDefault();\n \n const stretchId = trigger.dataset.stretchId;\n \n // Find or create bubble\n let bubble = trigger._bubble;\n \n if (!bubble) {\n const section = sections[stretchId];\n if (!section) return;\n \n bubble = document.createElement('div');\n bubble.className = 'weave-stretch-bubble';\n bubble.dataset.stretchId = stretchId;\n bubble.innerHTML = '<div class=\"weave-stretch-content\"><div class=\"weave-stretch-arrow\"></div>' + section.html + '</div>';\n \n // Insert bubble after the parent paragraph\n const paragraph = trigger.closest('p, li, td, div.weave-stretch-content');\n if (paragraph && paragraph.parentNode) {\n paragraph.parentNode.insertBefore(bubble, paragraph.nextSibling);\n } else {\n trigger.parentNode.insertBefore(bubble, trigger.nextSibling);\n }\n \n // Position arrow to point at trigger\n const arrow = bubble.querySelector('.weave-stretch-arrow');\n const triggerRect = trigger.getBoundingClientRect();\n const bubbleRect = bubble.getBoundingClientRect();\n const arrowLeft = triggerRect.left - bubbleRect.left + (triggerRect.width / 2) - 20; // -20 to center the arrow\n arrow.style.left = arrowLeft + 'px';\n \n trigger._bubble = bubble;\n \n // Force reflow for animation\n bubble.offsetHeight;\n }\n \n // Toggle open/close\n const isOpen = bubble.classList.contains('open');\n if (isOpen) {\n bubble.classList.remove('open');\n trigger.classList.remove('expanded');\n } else {\n bubble.classList.add('open');\n trigger.classList.add('expanded');\n }\n });\n\n // Footnote backlink tracking - remember which reference was clicked\n document.addEventListener('click', (e) => {\n const link = e.target.closest('.weave-footnote-ref a, a.weave-footnote-link');\n if (!link) return;\n \n const refId = link.id;\n if (!refId) return;\n \n // Extract footnote number from the href (e.g., #fn-3 -> 3)\n const href = link.getAttribute('href');\n if (!href || !href.startsWith('#fn-')) return;\n const fnNum = href.replace('#fn-', '');\n \n // Find the backref link in the footnote and update it\n const backref = document.querySelector('#fn-' + fnNum + ' .weave-footnote-backref');\n if (backref) {\n backref.setAttribute('href', '#' + refId);\n }\n \n // Navigate to the footnote\n e.preventDefault();\n const footnoteId = 'fn-' + fnNum;\n const footnote = document.getElementById(footnoteId);\n if (footnote) {\n footnote.scrollIntoView({ behavior: 'smooth' });\n // Update URL fragment without triggering navigation\n history.pushState(null, '', '#' + footnoteId);\n }\n });\n\n // Backref click - scroll and clear hash\n document.addEventListener('click', (e) => {\n const backref = e.target.closest('.weave-footnote-backref');\n if (!backref) return;\n \n e.preventDefault();\n const href = backref.getAttribute('href');\n if (!href) return;\n \n const target = document.getElementById(href.replace('#', ''));\n if (target) {\n target.scrollIntoView({ behavior: 'smooth' });\n history.pushState(null, '', window.location.pathname + window.location.search);\n }\n });\n\n // Video start time handling\n document.querySelectorAll('video[data-start]').forEach(video => {\n const startTime = parseFloat(video.dataset.start);\n if (!isNaN(startTime)) {\n video.currentTime = startTime;\n video.addEventListener('loadedmetadata', () => {\n video.currentTime = startTime;\n }, { once: true });\n }\n });\n\n // Gallery carousel initialization\n document.querySelectorAll('.weave-gallery').forEach(gallery => {\n const figures = gallery.querySelectorAll('figure');\n if (figures.length <= 1) return;\n \n // Set first figure as active\n figures[0].classList.add('active');\n \n // Create navigation buttons\n const prevBtn = document.createElement('button');\n prevBtn.className = 'weave-gallery-nav weave-gallery-prev';\n prevBtn.innerHTML = '&#10094;';\n prevBtn.setAttribute('aria-label', 'Previous');\n \n const nextBtn = document.createElement('button');\n nextBtn.className = 'weave-gallery-nav weave-gallery-next';\n nextBtn.innerHTML = '&#10095;';\n nextBtn.setAttribute('aria-label', 'Next');\n \n gallery.insertBefore(prevBtn, gallery.firstChild);\n gallery.insertBefore(nextBtn, gallery.querySelector('figcaption') || null);\n \n // Create dots\n const dotsContainer = document.createElement('div');\n dotsContainer.className = 'weave-gallery-dots';\n figures.forEach((_, i) => {\n const dot = document.createElement('button');\n dot.className = 'weave-gallery-dot' + (i === 0 ? ' active' : '');\n dot.setAttribute('aria-label', 'Go to slide ' + (i + 1));\n dot.dataset.index = i;\n dotsContainer.appendChild(dot);\n });\n const caption = gallery.querySelector('figcaption');\n if (caption) {\n gallery.insertBefore(dotsContainer, caption);\n } else {\n gallery.appendChild(dotsContainer);\n }\n \n let current = 0;\n \n const showSlide = (index) => {\n figures.forEach((f, i) => f.classList.toggle('active', i === index));\n dotsContainer.querySelectorAll('.weave-gallery-dot').forEach((d, i) => \n d.classList.toggle('active', i === index)\n );\n current = index;\n };\n \n prevBtn.addEventListener('click', () => {\n showSlide((current - 1 + figures.length) % figures.length);\n });\n \n nextBtn.addEventListener('click', () => {\n showSlide((current + 1) % figures.length);\n });\n \n dotsContainer.addEventListener('click', (e) => {\n const dot = e.target.closest('.weave-gallery-dot');\n if (dot) showSlide(parseInt(dot.dataset.index));\n });\n });\n </script>\n</body>\n</html>\n";
export declare function renderTemplate(options: {

@@ -6,0 +6,0 @@ title: string;

@@ -19,5 +19,8 @@ /**

body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-family: 'Iowan Old Style', 'Cambria', 'Palatino Linotype', Palatino, Georgia, serif;
font-size: 20px;
font-weight: 300;
line-height: 1.6;
color: #333;
letter-spacing: 0.01em;
color: #363737;
background: #fff;

@@ -37,11 +40,22 @@ padding: 2rem 1rem;

h1, h2, h3, h4, h5, h6 {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Helvetica, Arial, 'Liberation Sans', sans-serif;
letter-spacing: -0.02em;
}
/* Reduce top margin when header follows another header */
h1 + h2, h2 + h3, h3 + h4, h4 + h5, h5 + h6 {
margin-top: 4px;
}
h1 {
font-size: 2.25rem;
font-size: 32px;
font-weight: 700;
margin-bottom: 20px;
}
h2 {
margin-top: 1.5rem;
margin-bottom: 1rem;
font-size: 1.75rem;
margin-top: 32px;
margin-bottom: 20px;
font-size: 28px;
font-weight: 600;

@@ -51,12 +65,39 @@ }

h3 {
margin-top: 1.5rem;
margin-bottom: 0.75rem;
font-size: 1.25rem;
margin-top: 26px;
margin-bottom: 16px;
font-size: 26px;
font-weight: 600;
}
h4 {
margin-top: 22px;
margin-bottom: 14px;
font-size: 24px;
font-weight: 600;
}
h5 {
margin-top: 18px;
margin-bottom: 12px;
font-size: 22px;
font-weight: 500;
}
h6 {
margin-top: 16px;
margin-bottom: 10px;
font-size: 20px;
font-weight: 500;
}
p {
margin-bottom: 1rem;
margin-bottom: 16px;
}
blockquote {
padding-left: 20px;
border-left: 3px solid #0066cc;
margin-top: 20px;
}
/* Node links */

@@ -179,2 +220,113 @@ .weave-node-link {

/* Stretch/Nutshell-style expandable content */
.weave-stretch-trigger {
color: #2b67ad;
text-decoration: none;
border-bottom: 2px dotted #2b67ad;
cursor: pointer;
position: relative;
}
.weave-stretch-trigger:hover {
background: rgba(43, 103, 173, 0.1);
}
.weave-stretch-trigger.expanded {
border-bottom-style: solid;
}
.weave-stretch-bubble {
display: block;
position: relative;
margin-top: 20px;
transition: opacity 0.3s ease-out, margin 0.3s ease-out;
opacity: 0;
margin-bottom: 0;
max-height: 0;
overflow: hidden;
}
.weave-stretch-bubble.open {
max-height: none;
overflow: visible;
opacity: 1;
margin-bottom: 0.75rem;
}
.weave-stretch-content {
background: #fff;
border: 2px solid #ccc;
border-radius: 1rem;
padding: 1rem 0.8rem;
position: relative;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
/* Arrow pointing up to trigger (Nutshell-style) */
.weave-stretch-arrow {
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-bottom: 20px solid #ccc;
position: absolute;
top: -20px;
pointer-events: none;
}
.weave-stretch-arrow::after {
content: '';
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-bottom: 20px solid #fff;
position: absolute;
top: 2px;
left: -20px;
pointer-events: none;
}
.weave-stretch-content p:first-child {
margin-top: 0;
}
.weave-stretch-content p:last-child {
margin-bottom: 0;
}
.weave-stretch-content ul,
.weave-stretch-content ol {
padding-left: 1.5rem;
margin: 0.5rem 0;
}
/* Nested stretch bubbles - slightly different style */
.weave-stretch-content .weave-stretch-content {
background: #fafafa;
border-color: #ddd;
}
.weave-stretch-content .weave-stretch-arrow {
border-bottom-color: #ddd;
}
.weave-stretch-content .weave-stretch-arrow::after {
border-bottom-color: #fafafa;
}
/* Level 3+ nesting */
.weave-stretch-content .weave-stretch-content .weave-stretch-content {
background: #f5f5f5;
border-color: #e0e0e0;
}
.weave-stretch-content .weave-stretch-content .weave-stretch-arrow {
border-bottom-color: #e0e0e0;
}
.weave-stretch-content .weave-stretch-content .weave-stretch-arrow::after {
border-bottom-color: #f5f5f5;
}
/* Footnote references */

@@ -478,2 +630,21 @@ .weave-footnote-ref {

/* Task lists */
ul:has(input[type="checkbox"]) {
list-style: none;
padding-left: 0;
}
ul:has(input[type="checkbox"]) ul {
list-style: none;
padding-left: 1.5em;
}
li:has(> input[type="checkbox"]) {
margin-bottom: 0.25em;
}
input[type="checkbox"] {
margin-right: 0.5em;
}
/* Code blocks */

@@ -726,2 +897,55 @@ pre {

// Stretch/Nutshell-style expand/collapse handling
document.addEventListener('click', (e) => {
const trigger = e.target.closest('.weave-stretch-trigger');
if (!trigger) return;
e.preventDefault();
const stretchId = trigger.dataset.stretchId;
// Find or create bubble
let bubble = trigger._bubble;
if (!bubble) {
const section = sections[stretchId];
if (!section) return;
bubble = document.createElement('div');
bubble.className = 'weave-stretch-bubble';
bubble.dataset.stretchId = stretchId;
bubble.innerHTML = '<div class="weave-stretch-content"><div class="weave-stretch-arrow"></div>' + section.html + '</div>';
// Insert bubble after the parent paragraph
const paragraph = trigger.closest('p, li, td, div.weave-stretch-content');
if (paragraph && paragraph.parentNode) {
paragraph.parentNode.insertBefore(bubble, paragraph.nextSibling);
} else {
trigger.parentNode.insertBefore(bubble, trigger.nextSibling);
}
// Position arrow to point at trigger
const arrow = bubble.querySelector('.weave-stretch-arrow');
const triggerRect = trigger.getBoundingClientRect();
const bubbleRect = bubble.getBoundingClientRect();
const arrowLeft = triggerRect.left - bubbleRect.left + (triggerRect.width / 2) - 20; // -20 to center the arrow
arrow.style.left = arrowLeft + 'px';
trigger._bubble = bubble;
// Force reflow for animation
bubble.offsetHeight;
}
// Toggle open/close
const isOpen = bubble.classList.contains('open');
if (isOpen) {
bubble.classList.remove('open');
trigger.classList.remove('expanded');
} else {
bubble.classList.add('open');
trigger.classList.add('expanded');
}
});
// Footnote backlink tracking - remember which reference was clicked

@@ -728,0 +952,0 @@ document.addEventListener('click', (e) => {

{
"name": "@weave-md/basic",
"version": "0.3.0-alpha.0",
"version": "0.3.1-alpha.0",
"description": "Reference implementation of Weave Markdown - CLI, renderer, and exporter",

@@ -20,6 +20,2 @@ "type": "module",

],
"scripts": {
"build": "tsc",
"test": "cd ../.. && pnpm vitest run packages/basic/test"
},
"keywords": [

@@ -40,11 +36,11 @@ "weave",

"peerDependencies": {
"@weave-md/core": "workspace:*"
"@weave-md/core": "^0.3.1-alpha.0"
},
"dependencies": {
"@weave-md/parse": "workspace:*",
"@weave-md/validate": "workspace:*",
"hast-util-to-html": "^9.0.5",
"katex": "^0.16.9",
"mdast-util-to-hast": "^13.2.1",
"unist-util-visit": "^5.0.0"
"unist-util-visit": "^5.0.0",
"@weave-md/validate": "^0.3.1-alpha.0",
"@weave-md/parse": "^0.3.1-alpha.0"
},

@@ -55,6 +51,10 @@ "devDependencies": {

"@types/node": "^20.10.0",
"@weave-md/core": "workspace:*",
"typescript": "^5.7.0",
"vitest": "^1.6.0"
"vitest": "^1.6.0",
"@weave-md/core": "^0.3.1-alpha.0"
},
"scripts": {
"build": "tsc",
"test": "cd ../.. && pnpm vitest run packages/basic/test"
}
}
}