diff --git a/.gitignore b/.gitignore
index 7c07181..91376a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,4 @@ debug-output
# Snapshot diff files
*.diff.png
+.vercel
diff --git a/package.json b/package.json
index 285ef99..83aea27 100644
--- a/package.json
+++ b/package.json
@@ -9,10 +9,14 @@
"test:update-snapshots": "BUN_UPDATE_SNAPSHOTS=1 bun test",
"build": "tsup-node ./lib/index.ts --format esm --dts",
"format": "biome format --write .",
- "format:check": "biome format ."
+ "format:check": "biome format .",
+ "start": "bun site/index.html",
+ "build:site": "bun build site/index.html --outdir=site-export"
},
"devDependencies": {
"@biomejs/biome": "^2.3.8",
+ "@resvg/resvg-js": "^2.6.2",
+ "@resvg/resvg-wasm": "^2.6.2",
"@types/bun": "latest",
"circuit-json": "^0.0.286",
"looks-same": "^10.0.1",
diff --git a/site/index.html b/site/index.html
new file mode 100644
index 0000000..8e2c0fc
--- /dev/null
+++ b/site/index.html
@@ -0,0 +1,253 @@
+
+
+
+
+
+ Circuit JSON to STEP Converter
+
+
+
+
+
Circuit JSON to STEP Converter
+
Convert your Circuit JSON files to STEP format for 3D CAD software
+
+
+
📁
+
Drag & Drop your Circuit JSON file here
+
or click to browse
+
+
+
+
+
+
+
+
Options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/site/main.tsx b/site/main.tsx
new file mode 100644
index 0000000..17d356d
--- /dev/null
+++ b/site/main.tsx
@@ -0,0 +1,172 @@
+import { circuitJsonToStep } from "../lib/index"
+
+// Get DOM elements
+const uploadArea = document.getElementById("uploadArea")!
+const fileInput = document.getElementById("fileInput")! as HTMLInputElement
+const fileInfo = document.getElementById("fileInfo")!
+const fileName = document.getElementById("fileName")!
+const fileSize = document.getElementById("fileSize")!
+const convertBtn = document.getElementById("convertBtn")! as HTMLButtonElement
+const clearBtn = document.getElementById("clearBtn")!
+const status = document.getElementById("status")!
+const includeComponentsCheckbox = document.getElementById(
+ "includeComponents",
+)! as HTMLInputElement
+const includeExternalMeshesCheckbox = document.getElementById(
+ "includeExternalMeshes",
+)! as HTMLInputElement
+
+let currentFile: File | null = null
+let circuitJson: any = null
+
+// Handle click on upload area
+uploadArea.addEventListener("click", () => {
+ fileInput.click()
+})
+
+// Handle file selection
+fileInput.addEventListener("change", (e) => {
+ const target = e.target as HTMLInputElement
+ if (target.files && target.files.length > 0 && target.files[0]) {
+ handleFile(target.files[0])
+ }
+})
+
+// Handle drag over
+uploadArea.addEventListener("dragover", (e) => {
+ e.preventDefault()
+ uploadArea.classList.add("dragover")
+})
+
+// Handle drag leave
+uploadArea.addEventListener("dragleave", () => {
+ uploadArea.classList.remove("dragover")
+})
+
+// Handle drop
+uploadArea.addEventListener("drop", (e) => {
+ e.preventDefault()
+ uploadArea.classList.remove("dragover")
+
+ if (
+ e.dataTransfer &&
+ e.dataTransfer.files.length > 0 &&
+ e.dataTransfer.files[0]
+ ) {
+ handleFile(e.dataTransfer.files[0])
+ }
+})
+
+// Handle includeComponents checkbox change
+includeComponentsCheckbox.addEventListener("change", () => {
+ if (!includeComponentsCheckbox.checked) {
+ includeExternalMeshesCheckbox.checked = false
+ includeExternalMeshesCheckbox.disabled = true
+ } else {
+ includeExternalMeshesCheckbox.disabled = false
+ }
+})
+
+// Initialize external meshes checkbox state
+includeExternalMeshesCheckbox.disabled = true
+
+// Handle file
+async function handleFile(file: File) {
+ currentFile = file
+
+ // Show file info
+ fileName.textContent = file.name
+ fileSize.textContent = `${(file.size / 1024).toFixed(2)} KB`
+ fileInfo.classList.add("visible")
+
+ // Read and parse the file
+ try {
+ showStatus("Reading file...", "processing")
+ const text = await file.text()
+ circuitJson = JSON.parse(text)
+
+ // Enable convert button
+ convertBtn.disabled = false
+ showStatus("File loaded successfully! Ready to convert.", "success")
+ } catch (error) {
+ showStatus(
+ `Error reading file: ${error instanceof Error ? error.message : String(error)}`,
+ "error",
+ )
+ convertBtn.disabled = true
+ circuitJson = null
+ }
+}
+
+// Convert and download
+convertBtn.addEventListener("click", async () => {
+ if (!circuitJson) return
+
+ try {
+ showStatus("Converting to STEP format...", "processing")
+ convertBtn.disabled = true
+
+ // Allow UI to update
+ await new Promise((resolve) => setTimeout(resolve, 100))
+
+ // Get base filename without extension
+ const baseName = currentFile!.name.replace(/\.json$/i, "")
+
+ // Get options from checkboxes
+ const includeComponents = includeComponentsCheckbox.checked
+ const includeExternalMeshes = includeExternalMeshesCheckbox.checked
+
+ // Convert to STEP
+ const stepContent = await circuitJsonToStep(circuitJson, {
+ includeComponents,
+ includeExternalMeshes: includeComponents && includeExternalMeshes,
+ })
+
+ // Download the STEP file
+ downloadFile(`${baseName}.step`, stepContent)
+
+ showStatus("Conversion complete! File downloaded.", "success")
+ convertBtn.disabled = false
+ } catch (error) {
+ showStatus(
+ `Error during conversion: ${error instanceof Error ? error.message : String(error)}`,
+ "error",
+ )
+ convertBtn.disabled = false
+ console.error(error)
+ }
+})
+
+// Clear button
+clearBtn.addEventListener("click", () => {
+ currentFile = null
+ circuitJson = null
+ fileInput.value = ""
+ fileInfo.classList.remove("visible")
+ convertBtn.disabled = true
+ hideStatus()
+})
+
+// Helper function to download a file
+function downloadFile(filename: string, content: string) {
+ const blob = new Blob([content], { type: "application/step" })
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement("a")
+ a.href = url
+ a.download = filename
+ document.body.appendChild(a)
+ a.click()
+ document.body.removeChild(a)
+ URL.revokeObjectURL(url)
+}
+
+// Helper function to show status
+function showStatus(message: string, type: "success" | "error" | "processing") {
+ status.textContent = message
+ status.className = `status visible ${type}`
+}
+
+// Helper function to hide status
+function hideStatus() {
+ status.classList.remove("visible")
+}
diff --git a/tsconfig.json b/tsconfig.json
index f01ef2e..7474645 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -26,5 +26,5 @@
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
},
- "exclude": ["circuit-json", "stepts"]
+ "exclude": ["circuit-json", "stepts", "site"]
}