+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/script.js b/docs/script.js
new file mode 100644
index 00000000..edce1d35
--- /dev/null
+++ b/docs/script.js
@@ -0,0 +1,139 @@
+import init, { convert } from "./html_to_markdown_wasm.js";
+
+// State
+let wasmInitialized = false;
+
+// DOM Elements
+const htmlInput = document.getElementById("htmlInput");
+const markdownOutput = document.getElementById("markdownOutput");
+const convertBtn = document.getElementById("convertBtn");
+const copyBtn = document.getElementById("copyBtn");
+const clearBtn = document.getElementById("clearBtn");
+const statusEl = document.getElementById("status");
+const wasmStatusEl = document.getElementById("wasmStatus");
+
+// Initialize WASM module
+async function initWasm() {
+ try {
+ await init();
+ wasmInitialized = true;
+ wasmStatusEl.textContent = "✓ WASM module loaded";
+ wasmStatusEl.classList.add("loaded");
+ statusEl.textContent = "Ready to convert!";
+ statusEl.classList.add("success");
+
+ // Auto-convert the example on load
+ performConversion();
+ } catch (error) {
+ console.error("Failed to initialize WASM:", error);
+ wasmStatusEl.textContent = "✗ Failed to load WASM";
+ wasmStatusEl.classList.add("error");
+ statusEl.textContent = "Error: Failed to load WASM module";
+ statusEl.classList.add("error");
+ convertBtn.disabled = true;
+ }
+}
+
+// Perform HTML to Markdown conversion
+function performConversion() {
+ if (!wasmInitialized) {
+ statusEl.textContent = "WASM module not initialized yet...";
+ statusEl.className = "status";
+ return;
+ }
+
+ const html = htmlInput.value.trim();
+
+ if (!html) {
+ markdownOutput.textContent = "";
+ statusEl.textContent = "Please enter some HTML to convert";
+ statusEl.className = "status";
+ return;
+ }
+
+ try {
+ const startTime = performance.now();
+
+ // Convert HTML to Markdown
+ const markdown = convert(html, null);
+
+ const endTime = performance.now();
+ const duration = (endTime - startTime).toFixed(2);
+
+ // Display result
+ markdownOutput.textContent = markdown;
+ statusEl.textContent = `✓ Converted in ${duration}ms`;
+ statusEl.className = "status success";
+
+ // Reset copy button if it was in copied state
+ copyBtn.classList.remove("copied");
+ } catch (error) {
+ console.error("Conversion error:", error);
+ markdownOutput.textContent = "";
+ statusEl.textContent = `Error: ${error.message}`;
+ statusEl.className = "status error";
+ }
+}
+
+// Copy markdown to clipboard
+async function copyToClipboard() {
+ const markdown = markdownOutput.textContent;
+
+ if (!markdown) {
+ statusEl.textContent = "Nothing to copy";
+ statusEl.className = "status";
+ return;
+ }
+
+ try {
+ await navigator.clipboard.writeText(markdown);
+ copyBtn.classList.add("copied");
+ statusEl.textContent = "✓ Copied to clipboard!";
+ statusEl.className = "status success";
+
+ // Reset button after 2 seconds
+ setTimeout(() => {
+ copyBtn.classList.remove("copied");
+ }, 2000);
+ } catch (error) {
+ console.error("Failed to copy:", error);
+ statusEl.textContent = "Failed to copy to clipboard";
+ statusEl.className = "status error";
+ }
+}
+
+// Clear input
+function clearInput() {
+ htmlInput.value = "";
+ markdownOutput.textContent = "";
+ statusEl.textContent = "Input cleared";
+ statusEl.className = "status";
+ htmlInput.focus();
+}
+
+// Event Listeners
+convertBtn.addEventListener("click", performConversion);
+copyBtn.addEventListener("click", copyToClipboard);
+clearBtn.addEventListener("click", clearInput);
+
+// Convert on Enter (Ctrl/Cmd + Enter)
+htmlInput.addEventListener("keydown", (e) => {
+ if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
+ e.preventDefault();
+ performConversion();
+ }
+});
+
+// Auto-convert on input (debounced)
+let debounceTimer;
+htmlInput.addEventListener("input", () => {
+ clearTimeout(debounceTimer);
+ debounceTimer = setTimeout(() => {
+ if (wasmInitialized && htmlInput.value.trim()) {
+ performConversion();
+ }
+ }, 500);
+});
+
+// Initialize on page load
+initWasm();
diff --git a/docs/style.css b/docs/style.css
new file mode 100644
index 00000000..8a26b8d2
--- /dev/null
+++ b/docs/style.css
@@ -0,0 +1,363 @@
+:root {
+ --primary-color: #2563eb;
+ --primary-hover: #1d4ed8;
+ --secondary-color: #64748b;
+ --success-color: #10b981;
+ --background: #ffffff;
+ --surface: #f8fafc;
+ --border: #e2e8f0;
+ --text-primary: #0f172a;
+ --text-secondary: #475569;
+ --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ min-height: 100vh;
+ padding: 2rem 1rem;
+ color: var(--text-primary);
+}
+
+.container {
+ max-width: 1400px;
+ margin: 0 auto;
+ background: var(--background);
+ border-radius: 1rem;
+ box-shadow: var(--shadow-lg);
+ overflow: hidden;
+}
+
+header {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 3rem 2rem;
+ text-align: center;
+}
+
+header h1 {
+ font-size: 2.5rem;
+ font-weight: 700;
+ margin-bottom: 0.5rem;
+}
+
+.subtitle {
+ font-size: 1.125rem;
+ opacity: 0.95;
+ margin-bottom: 1.5rem;
+}
+
+.links {
+ display: flex;
+ gap: 1rem;
+ justify-content: center;
+ flex-wrap: wrap;
+}
+
+.links a {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.5rem 1rem;
+ background: rgba(255, 255, 255, 0.2);
+ color: white;
+ text-decoration: none;
+ border-radius: 0.5rem;
+ font-weight: 500;
+ transition: all 0.2s;
+ backdrop-filter: blur(10px);
+}
+
+.links a:hover {
+ background: rgba(255, 255, 255, 0.3);
+ transform: translateY(-2px);
+}
+
+main {
+ padding: 2rem;
+}
+
+.converter {
+ display: grid;
+ gap: 1.5rem;
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1rem;
+}
+
+.section-header h2 {
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.input-section,
+.output-section {
+ background: var(--surface);
+ padding: 1.5rem;
+ border-radius: 0.75rem;
+ border: 1px solid var(--border);
+}
+
+textarea,
+.output {
+ width: 100%;
+ min-height: 400px;
+ padding: 1rem;
+ border: 1px solid var(--border);
+ border-radius: 0.5rem;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
+ font-size: 0.875rem;
+ line-height: 1.6;
+ resize: vertical;
+ background: var(--background);
+ color: var(--text-primary);
+ transition: border-color 0.2s;
+}
+
+textarea:focus {
+ outline: none;
+ border-color: var(--primary-color);
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
+}
+
+.output {
+ overflow-x: auto;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+.controls {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+ padding: 1rem 0;
+}
+
+.primary-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 1rem 2rem;
+ background: var(--primary-color);
+ color: white;
+ border: none;
+ border-radius: 0.5rem;
+ font-size: 1.125rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s;
+ box-shadow: var(--shadow);
+}
+
+.primary-btn:hover {
+ background: var(--primary-hover);
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-lg);
+}
+
+.primary-btn:active {
+ transform: translateY(0);
+}
+
+.primary-btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ transform: none;
+}
+
+.btn-icon {
+ font-size: 1.5rem;
+ transition: transform 0.2s;
+}
+
+.primary-btn:hover .btn-icon {
+ transform: translateX(4px);
+}
+
+.secondary-btn {
+ padding: 0.5rem 1rem;
+ background: var(--background);
+ color: var(--text-secondary);
+ border: 1px solid var(--border);
+ border-radius: 0.375rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s;
+ position: relative;
+}
+
+.secondary-btn:hover {
+ background: var(--surface);
+ border-color: var(--secondary-color);
+ color: var(--text-primary);
+}
+
+#copyBtn .copied-text {
+ display: none;
+}
+
+#copyBtn.copied .copy-text {
+ display: none;
+}
+
+#copyBtn.copied .copied-text {
+ display: inline;
+}
+
+#copyBtn.copied {
+ background: var(--success-color);
+ color: white;
+ border-color: var(--success-color);
+}
+
+.status {
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+ min-height: 1.5rem;
+}
+
+.status.success {
+ color: var(--success-color);
+ font-weight: 500;
+}
+
+.status.error {
+ color: #ef4444;
+ font-weight: 500;
+}
+
+footer {
+ background: var(--surface);
+ padding: 2rem;
+ text-align: center;
+ border-top: 1px solid var(--border);
+ color: var(--text-secondary);
+}
+
+footer p {
+ margin-bottom: 0.5rem;
+}
+
+footer a {
+ color: var(--primary-color);
+ text-decoration: none;
+ font-weight: 500;
+}
+
+footer a:hover {
+ text-decoration: underline;
+}
+
+.version {
+ font-size: 0.875rem;
+ opacity: 0.8;
+}
+
+#wasmStatus {
+ display: inline-block;
+ padding: 0.25rem 0.75rem;
+ background: var(--background);
+ border-radius: 0.25rem;
+ font-family: 'Monaco', 'Menlo', monospace;
+ font-size: 0.75rem;
+}
+
+#wasmStatus.loaded {
+ color: var(--success-color);
+}
+
+#wasmStatus.error {
+ color: #ef4444;
+}
+
+/* Responsive Design */
+@media (min-width: 768px) {
+ body {
+ padding: 3rem 2rem;
+ }
+
+ header h1 {
+ font-size: 3rem;
+ }
+
+ main {
+ padding: 3rem;
+ }
+
+ .converter {
+ grid-template-columns: 1fr 1fr;
+ grid-template-rows: auto auto;
+ }
+
+ .input-section {
+ grid-column: 1;
+ grid-row: 1;
+ }
+
+ .controls {
+ grid-column: 1 / -1;
+ grid-row: 2;
+ flex-direction: row;
+ justify-content: center;
+ }
+
+ .output-section {
+ grid-column: 2;
+ grid-row: 1;
+ }
+}
+
+/* Loading Animation */
+@keyframes pulse {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.5;
+ }
+}
+
+.primary-btn:disabled {
+ animation: pulse 1.5s ease-in-out infinite;
+}
+
+/* Scrollbar Styling */
+textarea::-webkit-scrollbar,
+.output::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+textarea::-webkit-scrollbar-track,
+.output::-webkit-scrollbar-track {
+ background: var(--surface);
+ border-radius: 4px;
+}
+
+textarea::-webkit-scrollbar-thumb,
+.output::-webkit-scrollbar-thumb {
+ background: var(--border);
+ border-radius: 4px;
+}
+
+textarea::-webkit-scrollbar-thumb:hover,
+.output::-webkit-scrollbar-thumb:hover {
+ background: var(--secondary-color);
+}
diff --git a/scripts/build-demo.sh b/scripts/build-demo.sh
new file mode 100755
index 00000000..c0e0303d
--- /dev/null
+++ b/scripts/build-demo.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+# Script to rebuild and update the GitHub Pages demo
+
+set -e
+
+echo "🔨 Building WASM package..."
+cd crates/html-to-markdown-wasm
+wasm-pack build --target web --out-dir dist-web
+
+echo "📦 Copying files to docs/..."
+cd ../..
+cp crates/html-to-markdown-wasm/dist-web/html_to_markdown_wasm.js docs/
+cp crates/html-to-markdown-wasm/dist-web/html_to_markdown_wasm_bg.wasm docs/
+
+echo "✅ Demo updated successfully!"
+echo ""
+echo "To test locally, run:"
+echo " cd docs && python3 -m http.server 8000"
+echo ""
+echo "Then open http://localhost:8000 in your browser"