feat: lazily-loaded Gerber viewer demo in the blog, served from R2

Embed KiCad's gerbview WASM in the graphics-to-WebGL blog post as a
click-to-load demo that opens the tiny_tapeout board entirely in-browser.

- GerberDemo.astro: lazy iframe embed + cross-origin-isolation reload guard
  + WebGL2/SharedArrayBuffer feature-detect with a poster fallback
- public/gerber-demo/{index.html,boot.js}: self-contained gerbview boot
  harness (config seed, MEMFS board preload, argv auto-open)
- board/ tiny_tapeout layers + poster.png committed; wasm/ glue JS committed
  (served same-origin — pthread worker can't be cross-origin), while
  gerbview.wasm + kicad-resources.bin are git-ignored and served from
  Cloudflare R2 (assets.pcbjam.com)
- COOP/COEP require-corp scoped to the post + /gerber-demo routes
  (dev: astro.config.mjs + middleware.ts; prod: vercel.json)
- post converted to MDX (@astrojs/mdx) so it can embed the component
- scripts/: sync-demo-wasm.sh, r2-deploy.sh, r2-cors.json

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Viktor Vaczi 2026-06-17 10:28:06 +02:00
commit 57f1f53d26
36 changed files with 242845 additions and 9 deletions

5
.gitignore vendored
View file

@ -81,4 +81,7 @@ output/
/scripts/bench/vm/
/tests/.test-port-asyncify
/memory/
/memory/
# Wrangler (Cloudflare CLI) local state/cache — created when running R2 deploys.
.wrangler/

7
site/.gitignore vendored
View file

@ -2,6 +2,13 @@
dist/
.vercel/
# Gerber-viewer demo WASM (synced from /output by scripts/sync-demo-wasm.sh).
# The big binaries are served from Cloudflare R2 in production — never commit /
# ship them to Vercel. The small glue JS (wx.js, wx-dom.js, gerbview.js) MUST be
# committed: it's served same-origin (the pthread worker can't be cross-origin).
public/gerber-demo/wasm/gerbview.wasm
public/gerber-demo/wasm/kicad-resources.bin
# generated content collection types & cache
.astro/

View file

@ -1,6 +1,7 @@
// @ts-check
import { defineConfig, envField } from 'astro/config';
import vercel from '@astrojs/vercel';
import mdx from '@astrojs/mdx';
// Static by default (every page prerenders to HTML, zero client JS).
// The Vercel adapter is wired in so any single route can opt into
@ -9,8 +10,24 @@ import vercel from '@astrojs/vercel';
export default defineConfig({
output: 'static',
adapter: vercel(),
// MDX lets the Gerber-viewer blog post embed the <GerberDemo /> component
// inline (markdown posts can't import components).
integrations: [mdx()],
// Prefetch linked pages so SPA-style navigation feels instant.
prefetch: { prefetchAll: true, defaultStrategy: 'viewport' },
// Dev-server cross-origin isolation so the embedded Gerber viewer's WASM
// threads (SharedArrayBuffer) work under `npm run dev`. Production headers are
// scoped per-route in vercel.json. require-corp (not credentialless) for the
// widest browser support incl. Safari 15.2+; safe because the site loads only
// same-origin subresources.
vite: {
server: {
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
},
},
// Typed, validated server secrets for the waitlist endpoint. All optional so
// the build never requires them and the endpoint degrades gracefully when a
// key is absent (see src/pages/api/waitlist.ts). The @astrojs/vercel adapter

766
site/package-lock.json generated
View file

@ -8,6 +8,7 @@
"name": "site",
"version": "0.0.1",
"dependencies": {
"@astrojs/mdx": "^6.0.3",
"@astrojs/vercel": "^10.0.8",
"@fontsource/oswald": "^5.2.8",
"@vercel/analytics": "^2.0.1",
@ -182,6 +183,40 @@
"vfile": "^6.0.3"
}
},
"node_modules/@astrojs/mdx": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-6.0.3.tgz",
"integrity": "sha512-+4P3ZvwsRAqAbBgY+uZMewFo3ficlIBPZfu/Luk+v4ia/ZOuFhpsw7r+7672uT2Fc1UPdp7yW0eU5egvSq0wbw==",
"license": "MIT",
"dependencies": {
"@astrojs/internal-helpers": "0.10.0",
"@astrojs/markdown-remark": "7.2.0",
"@mdx-js/mdx": "^3.1.1",
"acorn": "^8.16.0",
"es-module-lexer": "^2.0.0",
"estree-util-visit": "^2.0.0",
"hast-util-to-html": "^9.0.5",
"piccolore": "^0.1.3",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"remark-smartypants": "^3.0.2",
"source-map": "^0.7.6",
"unist-util-visit": "^5.1.0",
"vfile": "^6.0.3"
},
"engines": {
"node": ">=22.12.0"
},
"peerDependencies": {
"@astrojs/markdown-satteri": "0.3.0",
"astro": "^6.4.0"
},
"peerDependenciesMeta": {
"@astrojs/markdown-satteri": {
"optional": true
}
}
},
"node_modules/@astrojs/prism": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.2.tgz",
@ -1407,6 +1442,52 @@
"node": ">=18"
}
},
"node_modules/@mdx-js/mdx": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz",
"integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"@types/estree-jsx": "^1.0.0",
"@types/hast": "^3.0.0",
"@types/mdx": "^2.0.0",
"acorn": "^8.0.0",
"collapse-white-space": "^2.0.0",
"devlop": "^1.0.0",
"estree-util-is-identifier-name": "^3.0.0",
"estree-util-scope": "^1.0.0",
"estree-walker": "^3.0.0",
"hast-util-to-jsx-runtime": "^2.0.0",
"markdown-extensions": "^2.0.0",
"recma-build-jsx": "^1.0.0",
"recma-jsx": "^1.0.0",
"recma-stringify": "^1.0.0",
"rehype-recma": "^1.0.0",
"remark-mdx": "^3.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.0.0",
"source-map": "^0.7.0",
"unified": "^11.0.0",
"unist-util-position-from-estree": "^2.0.0",
"unist-util-stringify-position": "^4.0.0",
"unist-util-visit": "^5.0.0",
"vfile": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/@mdx-js/mdx/node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0"
}
},
"node_modules/@oslojs/encoding": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz",
@ -2152,6 +2233,15 @@
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"license": "MIT"
},
"node_modules/@types/estree-jsx": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
"integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
"license": "MIT",
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/@types/hast": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
@ -2170,6 +2260,12 @@
"@types/unist": "*"
}
},
"node_modules/@types/mdx": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz",
"integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==",
"license": "MIT"
},
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
@ -2468,6 +2564,15 @@
"acorn": "^8"
}
},
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"license": "MIT",
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
@ -2570,6 +2675,15 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/astring": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz",
"integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==",
"license": "MIT",
"bin": {
"astring": "bin/astring"
}
},
"node_modules/astro": {
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/astro/-/astro-6.4.4.tgz",
@ -2758,6 +2872,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/character-reference-invalid": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
"integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/chokidar": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
@ -2821,6 +2945,16 @@
"node": ">=6"
}
},
"node_modules/collapse-white-space": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz",
"integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -3214,6 +3348,38 @@
"integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
"license": "MIT"
},
"node_modules/esast-util-from-estree": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz",
"integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==",
"license": "MIT",
"dependencies": {
"@types/estree-jsx": "^1.0.0",
"devlop": "^1.0.0",
"estree-util-visit": "^2.0.0",
"unist-util-position-from-estree": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/esast-util-from-js": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz",
"integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==",
"license": "MIT",
"dependencies": {
"@types/estree-jsx": "^1.0.0",
"acorn": "^8.0.0",
"esast-util-from-estree": "^2.0.0",
"vfile-message": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/esbuild": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
@ -3277,6 +3443,97 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/estree-util-attach-comments": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz",
"integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/estree-util-build-jsx": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz",
"integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==",
"license": "MIT",
"dependencies": {
"@types/estree-jsx": "^1.0.0",
"devlop": "^1.0.0",
"estree-util-is-identifier-name": "^3.0.0",
"estree-walker": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/estree-util-build-jsx/node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0"
}
},
"node_modules/estree-util-is-identifier-name": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
"integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/estree-util-scope": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz",
"integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"devlop": "^1.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/estree-util-to-js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz",
"integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==",
"license": "MIT",
"dependencies": {
"@types/estree-jsx": "^1.0.0",
"astring": "^1.8.0",
"source-map": "^0.7.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/estree-util-visit": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz",
"integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==",
"license": "MIT",
"dependencies": {
"@types/estree-jsx": "^1.0.0",
"@types/unist": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
@ -3618,6 +3875,34 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-estree": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz",
"integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"@types/estree-jsx": "^1.0.0",
"@types/hast": "^3.0.0",
"comma-separated-tokens": "^2.0.0",
"devlop": "^1.0.0",
"estree-util-attach-comments": "^3.0.0",
"estree-util-is-identifier-name": "^3.0.0",
"hast-util-whitespace": "^3.0.0",
"mdast-util-mdx-expression": "^2.0.0",
"mdast-util-mdx-jsx": "^3.0.0",
"mdast-util-mdxjs-esm": "^2.0.0",
"property-information": "^7.0.0",
"space-separated-tokens": "^2.0.0",
"style-to-js": "^1.0.0",
"unist-util-position": "^5.0.0",
"zwitch": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-html": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
@ -3641,6 +3926,33 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-jsx-runtime": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
"integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"@types/hast": "^3.0.0",
"@types/unist": "^3.0.0",
"comma-separated-tokens": "^2.0.0",
"devlop": "^1.0.0",
"estree-util-is-identifier-name": "^3.0.0",
"hast-util-whitespace": "^3.0.0",
"mdast-util-mdx-expression": "^2.0.0",
"mdast-util-mdx-jsx": "^3.0.0",
"mdast-util-mdxjs-esm": "^2.0.0",
"property-information": "^7.0.0",
"space-separated-tokens": "^2.0.0",
"style-to-js": "^1.0.0",
"unist-util-position": "^5.0.0",
"vfile-message": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-parse5": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz",
@ -3750,6 +4062,12 @@
"node": ">=10.17.0"
}
},
"node_modules/inline-style-parser": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
"integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
"license": "MIT"
},
"node_modules/iron-webcrypto": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
@ -3759,6 +4077,40 @@
"url": "https://github.com/sponsors/brc-dd"
}
},
"node_modules/is-alphabetical": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
"integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-alphanumerical": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
"integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
"license": "MIT",
"dependencies": {
"is-alphabetical": "^2.0.0",
"is-decimal": "^2.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-decimal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
"integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-docker": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz",
@ -3784,6 +4136,16 @@
"node": ">=8"
}
},
"node_modules/is-hexadecimal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
"integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-inside-container": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
@ -4217,6 +4579,18 @@
"source-map-js": "^1.2.1"
}
},
"node_modules/markdown-extensions": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz",
"integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==",
"license": "MIT",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/markdown-table": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
@ -4383,6 +4757,83 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-mdx": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz",
"integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==",
"license": "MIT",
"dependencies": {
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-mdx-expression": "^2.0.0",
"mdast-util-mdx-jsx": "^3.0.0",
"mdast-util-mdxjs-esm": "^2.0.0",
"mdast-util-to-markdown": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-mdx-expression": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
"integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
"license": "MIT",
"dependencies": {
"@types/estree-jsx": "^1.0.0",
"@types/hast": "^3.0.0",
"@types/mdast": "^4.0.0",
"devlop": "^1.0.0",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-to-markdown": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-mdx-jsx": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
"integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
"license": "MIT",
"dependencies": {
"@types/estree-jsx": "^1.0.0",
"@types/hast": "^3.0.0",
"@types/mdast": "^4.0.0",
"@types/unist": "^3.0.0",
"ccount": "^2.0.0",
"devlop": "^1.1.0",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-to-markdown": "^2.0.0",
"parse-entities": "^4.0.0",
"stringify-entities": "^4.0.0",
"unist-util-stringify-position": "^4.0.0",
"vfile-message": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-mdxjs-esm": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
"integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
"license": "MIT",
"dependencies": {
"@types/estree-jsx": "^1.0.0",
"@types/hast": "^3.0.0",
"@types/mdast": "^4.0.0",
"devlop": "^1.0.0",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-to-markdown": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-phrasing": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
@ -4654,6 +5105,108 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-extension-mdx-expression": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz",
"integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==",
"funding": [
{
"type": "GitHub Sponsors",
"url": "https://github.com/sponsors/unifiedjs"
},
{
"type": "OpenCollective",
"url": "https://opencollective.com/unified"
}
],
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"devlop": "^1.0.0",
"micromark-factory-mdx-expression": "^2.0.0",
"micromark-factory-space": "^2.0.0",
"micromark-util-character": "^2.0.0",
"micromark-util-events-to-acorn": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0"
}
},
"node_modules/micromark-extension-mdx-jsx": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz",
"integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"devlop": "^1.0.0",
"estree-util-is-identifier-name": "^3.0.0",
"micromark-factory-mdx-expression": "^2.0.0",
"micromark-factory-space": "^2.0.0",
"micromark-util-character": "^2.0.0",
"micromark-util-events-to-acorn": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0",
"vfile-message": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-extension-mdx-md": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz",
"integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==",
"license": "MIT",
"dependencies": {
"micromark-util-types": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-extension-mdxjs": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz",
"integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==",
"license": "MIT",
"dependencies": {
"acorn": "^8.0.0",
"acorn-jsx": "^5.0.0",
"micromark-extension-mdx-expression": "^3.0.0",
"micromark-extension-mdx-jsx": "^3.0.0",
"micromark-extension-mdx-md": "^2.0.0",
"micromark-extension-mdxjs-esm": "^3.0.0",
"micromark-util-combine-extensions": "^2.0.0",
"micromark-util-types": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-extension-mdxjs-esm": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz",
"integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"devlop": "^1.0.0",
"micromark-core-commonmark": "^2.0.0",
"micromark-util-character": "^2.0.0",
"micromark-util-events-to-acorn": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0",
"unist-util-position-from-estree": "^2.0.0",
"vfile-message": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-factory-destination": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
@ -4697,6 +5250,33 @@
"micromark-util-types": "^2.0.0"
}
},
"node_modules/micromark-factory-mdx-expression": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz",
"integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==",
"funding": [
{
"type": "GitHub Sponsors",
"url": "https://github.com/sponsors/unifiedjs"
},
{
"type": "OpenCollective",
"url": "https://opencollective.com/unified"
}
],
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"devlop": "^1.0.0",
"micromark-factory-space": "^2.0.0",
"micromark-util-character": "^2.0.0",
"micromark-util-events-to-acorn": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0",
"unist-util-position-from-estree": "^2.0.0",
"vfile-message": "^4.0.0"
}
},
"node_modules/micromark-factory-space": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
@ -4898,6 +5478,31 @@
],
"license": "MIT"
},
"node_modules/micromark-util-events-to-acorn": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz",
"integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==",
"funding": [
{
"type": "GitHub Sponsors",
"url": "https://github.com/sponsors/unifiedjs"
},
{
"type": "OpenCollective",
"url": "https://opencollective.com/unified"
}
],
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"@types/unist": "^3.0.0",
"devlop": "^1.0.0",
"estree-util-visit": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0",
"vfile-message": "^4.0.0"
}
},
"node_modules/micromark-util-html-tag-name": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
@ -5345,6 +5950,31 @@
"integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
"license": "MIT"
},
"node_modules/parse-entities": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
"integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
"license": "MIT",
"dependencies": {
"@types/unist": "^2.0.0",
"character-entities-legacy": "^3.0.0",
"character-reference-invalid": "^2.0.0",
"decode-named-character-reference": "^1.0.0",
"is-alphanumerical": "^2.0.0",
"is-decimal": "^2.0.0",
"is-hexadecimal": "^2.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/parse-entities/node_modules/@types/unist": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
"license": "MIT"
},
"node_modules/parse-latin": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz",
@ -5542,6 +6172,73 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/recma-build-jsx": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz",
"integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-util-build-jsx": "^3.0.0",
"vfile": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/recma-jsx": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz",
"integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==",
"license": "MIT",
"dependencies": {
"acorn-jsx": "^5.0.0",
"estree-util-to-js": "^2.0.0",
"recma-parse": "^1.0.0",
"recma-stringify": "^1.0.0",
"unified": "^11.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
},
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/recma-parse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz",
"integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"esast-util-from-js": "^2.0.0",
"unified": "^11.0.0",
"vfile": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/recma-stringify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz",
"integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-util-to-js": "^2.0.0",
"unified": "^11.0.0",
"vfile": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz",
@ -5612,6 +6309,21 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/rehype-recma": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz",
"integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"@types/hast": "^3.0.0",
"hast-util-to-estree": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/rehype-stringify": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz",
@ -5645,6 +6357,20 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-mdx": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz",
"integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==",
"license": "MIT",
"dependencies": {
"mdast-util-mdx": "^3.0.0",
"micromark-extension-mdxjs": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-parse": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
@ -6009,6 +6735,15 @@
"url": "https://github.com/sponsors/cyyynthia"
}
},
"node_modules/source-map": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
"integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">= 12"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -6089,6 +6824,24 @@
"node": ">=6"
}
},
"node_modules/style-to-js": {
"version": "1.1.21",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
"integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
"license": "MIT",
"dependencies": {
"style-to-object": "1.0.14"
}
},
"node_modules/style-to-object": {
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz",
"integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
"license": "MIT",
"dependencies": {
"inline-style-parser": "0.2.7"
}
},
"node_modules/svgo": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz",
@ -6357,6 +7110,19 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/unist-util-position-from-estree": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz",
"integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==",
"license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/unist-util-remove-position": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz",

View file

@ -14,6 +14,7 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^6.0.3",
"@astrojs/vercel": "^10.0.8",
"@fontsource/oswald": "^5.2.8",
"@vercel/analytics": "^2.0.1",

View file

@ -0,0 +1,38 @@
# Gerber viewer blog demo
KiCad's `gerbview` compiled to WebAssembly, embedded (lazily) in the blog post.
Click → it streams the WASM and renders the bundled tiny_tapeout board in-browser.
## Files here (`site/public/gerber-demo/`)
| Path | What it does |
|------|--------------|
| `index.html` | The iframe target — minimal page with the `#main-window` / `#window-container` the WASM needs. |
| `boot.js` | Boot harness: configures Emscripten `Module`, seeds KiCad config, preloads the board into MEMFS, auto-opens it via `Module.arguments`, and injects `wx.js → wx-dom.js → gerbview.js`. Holds `R2_BASE` + the dev/prod asset-base switch. |
| `board/` | The tiny_tapeout Gerber layers (committed) the demo opens. |
| `poster.png` | Static fallback shown to browsers that can't run the live viewer. |
| `wasm/` | `wx.js`/`wx-dom.js`/`gerbview.js` (committed, served same-origin) + `gerbview.wasm`/`kicad-resources.bin` (git-ignored; served from R2 in prod, local in dev). Synced from `/output`. |
## Related pieces (elsewhere in `site/`)
| Path | What it does |
|------|--------------|
| `src/components/GerberDemo.astro` | The embed: lazy click-to-load iframe, cross-origin-isolation reload guard, feature-detect + poster fallback. |
| `src/content/blog/porting-kicad-graphics-to-webgl-with-claude.mdx` | The post that renders `<GerberDemo />`. |
| `astro.config.mjs` + `src/middleware.ts` | Dev cross-origin-isolation headers (COOP/COEP `require-corp`). |
| `vercel.json` | Prod COOP/COEP, scoped to the post + `/gerber-demo/` routes. |
| `scripts/sync-demo-wasm.sh` | Copy fresh WASM from `/output` into `wasm/` (run after a gerbview rebuild). |
| `scripts/r2-deploy.sh` + `scripts/r2-cors.json` | Upload the heavy binaries to Cloudflare R2 (`pcbjam-assets``assets.pcbjam.com`) and set CORS. |
## Run / update
```bash
# local dev (serves wasm/ from the local mirror)
scripts/sync-demo-wasm.sh && npm run dev # /blog/porting-kicad-graphics-to-webgl-with-claude
# after a new gerbview build: refresh local mirror, then push binaries to R2
scripts/sync-demo-wasm.sh && scripts/r2-deploy.sh
```
The live viewer needs SharedArrayBuffer + WebGL2 (Chrome/Edge/Firefox, Safari 15.2+);
other browsers get `poster.png`.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,268 @@
G04 #@! TF.GenerationSoftware,KiCad,Pcbnew,7.0.9-7.0.9~ubuntu22.04.1*
G04 #@! TF.CreationDate,2023-12-15T15:20:45-05:00*
G04 #@! TF.ProjectId,tinytapeout-demo,74696e79-7461-4706-956f-75742d64656d,0.9.4*
G04 #@! TF.SameCoordinates,PX35e1f20PY8044ea0*
G04 #@! TF.FileFunction,Soldermask,Bot*
G04 #@! TF.FilePolarity,Negative*
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9-7.0.9~ubuntu22.04.1) date 2023-12-15 15:20:45*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
%ADD10C,0.900000*%
%ADD11C,6.400000*%
%ADD12O,1.250000X2.400000*%
%ADD13O,1.180000X2.080000*%
%ADD14R,1.700000X1.700000*%
%ADD15O,1.700000X1.700000*%
%ADD16C,1.700000*%
%ADD17R,1.000000X2.510000*%
G04 APERTURE END LIST*
D10*
G04 #@! TO.C,JP7*
X99900000Y16780000D03*
X96900000Y16780000D03*
G04 #@! TD*
G04 #@! TO.C,JP1*
X99900000Y31780000D03*
X96900000Y31780000D03*
G04 #@! TD*
D11*
G04 #@! TO.C,MT3*
X3750000Y77250000D03*
G04 #@! TD*
D12*
G04 #@! TO.C,J15*
X33870000Y79000000D03*
D13*
X22630000Y75000000D03*
X33870000Y75000000D03*
D12*
X22630000Y79000000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,J13*
X4500000Y34440000D03*
D15*
X4500000Y36980000D03*
X4500000Y39520000D03*
X4500000Y42060000D03*
X4500000Y44600000D03*
D16*
X4500000Y47140000D03*
D15*
X1960000Y34440000D03*
X1960000Y36980000D03*
X1960000Y39520000D03*
X1960000Y42060000D03*
X1960000Y44600000D03*
X1960000Y47140000D03*
G04 #@! TD*
D16*
G04 #@! TO.C,J2*
X18810000Y3000000D03*
D15*
X16270000Y3000000D03*
X13730000Y3000000D03*
X11190000Y3000000D03*
G04 #@! TD*
D10*
G04 #@! TO.C,JP3*
X99900000Y26780000D03*
X96900000Y26780000D03*
G04 #@! TD*
G04 #@! TO.C,JP8*
X99900000Y14280000D03*
X96900000Y14280000D03*
G04 #@! TD*
D16*
G04 #@! TO.C,J8*
X64545000Y3000000D03*
D15*
X62005000Y3000000D03*
X59465000Y3000000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,J12*
X4500000Y11580000D03*
D15*
X4500000Y14120000D03*
X4500000Y16660000D03*
X4500000Y19200000D03*
X4500000Y21740000D03*
D16*
X4500000Y24280000D03*
D15*
X1960000Y11580000D03*
X1960000Y14120000D03*
X1960000Y16660000D03*
X1960000Y19200000D03*
X1960000Y21740000D03*
X1960000Y24280000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,J3*
X34045000Y5545000D03*
D15*
X31505000Y5545000D03*
X28965000Y5545000D03*
X26425000Y5545000D03*
X23885000Y5545000D03*
D16*
X21345000Y5545000D03*
D15*
X34045000Y3005000D03*
X31505000Y3005000D03*
X28965000Y3005000D03*
X26425000Y3005000D03*
X23885000Y3005000D03*
X21345000Y3005000D03*
G04 #@! TD*
D11*
G04 #@! TO.C,MT4*
X100750000Y77250000D03*
G04 #@! TD*
G04 #@! TO.C,MT2*
X100750000Y3750000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,J5*
X56905000Y5545000D03*
D15*
X54365000Y5545000D03*
X51825000Y5545000D03*
X49285000Y5545000D03*
X46745000Y5545000D03*
D16*
X44205000Y5545000D03*
D15*
X56905000Y3005000D03*
X54365000Y3005000D03*
X51825000Y3005000D03*
X49285000Y3005000D03*
X46745000Y3005000D03*
X44205000Y3005000D03*
G04 #@! TD*
D10*
G04 #@! TO.C,JP2*
X99900000Y29280000D03*
X96900000Y29280000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,J10*
X1690000Y29260000D03*
D15*
X4230000Y29260000D03*
G04 #@! TD*
D16*
G04 #@! TO.C,J17*
X41700000Y3000000D03*
D15*
X39160000Y3000000D03*
X36620000Y3000000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,J9*
X95000000Y79000000D03*
D15*
X92460000Y79000000D03*
X89920000Y79000000D03*
X87380000Y79000000D03*
X84840000Y79000000D03*
X82300000Y79000000D03*
X79760000Y79000000D03*
X77220000Y79000000D03*
X74680000Y79000000D03*
X72140000Y79000000D03*
X69600000Y79000000D03*
X67060000Y79000000D03*
X64520000Y79000000D03*
X61980000Y79000000D03*
X59440000Y79000000D03*
X56900000Y79000000D03*
X54360000Y79000000D03*
X51820000Y79000000D03*
X49280000Y79000000D03*
X46740000Y79000000D03*
X44200000Y79000000D03*
X41660000Y79000000D03*
X39120000Y79000000D03*
X36580000Y79000000D03*
G04 #@! TD*
D10*
G04 #@! TO.C,JP5*
X99900000Y21780000D03*
X96900000Y21780000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,J14*
X4500000Y57300000D03*
D15*
X4500000Y59840000D03*
X4500000Y62380000D03*
X4500000Y64920000D03*
X4500000Y67460000D03*
D16*
X4500000Y70000000D03*
D15*
X1960000Y57300000D03*
X1960000Y59840000D03*
X1960000Y62380000D03*
X1960000Y64920000D03*
X1960000Y67460000D03*
X1960000Y70000000D03*
G04 #@! TD*
D10*
G04 #@! TO.C,JP6*
X99900000Y19280000D03*
X96900000Y19280000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,J1*
X95000000Y3000000D03*
D15*
X92460000Y3000000D03*
X89920000Y3000000D03*
X87380000Y3000000D03*
X84840000Y3000000D03*
X82300000Y3000000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,J6*
X79765000Y5545000D03*
D15*
X77225000Y5545000D03*
X74685000Y5545000D03*
X72145000Y5545000D03*
X69605000Y5545000D03*
D16*
X67065000Y5545000D03*
D15*
X79765000Y3005000D03*
X77225000Y3005000D03*
X74685000Y3005000D03*
X72145000Y3005000D03*
X69605000Y3005000D03*
X67065000Y3005000D03*
G04 #@! TD*
D10*
G04 #@! TO.C,JP4*
X99900000Y24280000D03*
X96900000Y24280000D03*
G04 #@! TD*
D11*
G04 #@! TO.C,MT1*
X3750000Y3750000D03*
G04 #@! TD*
D17*
G04 #@! TO.C,J11*
X22950000Y30775000D03*
X20410000Y27465000D03*
X17870000Y30775000D03*
X15330000Y27465000D03*
X12790000Y30775000D03*
X10250000Y27465000D03*
G04 #@! TD*
M02*

View file

@ -0,0 +1,25 @@
G04 #@! TF.GenerationSoftware,KiCad,Pcbnew,7.0.9-7.0.9~ubuntu22.04.1*
G04 #@! TF.CreationDate,2023-12-15T15:20:45-05:00*
G04 #@! TF.ProjectId,tinytapeout-demo,74696e79-7461-4706-956f-75742d64656d,0.9.4*
G04 #@! TF.SameCoordinates,PX35e1f20PY8044ea0*
G04 #@! TF.FileFunction,Paste,Bot*
G04 #@! TF.FilePolarity,Positive*
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9-7.0.9~ubuntu22.04.1) date 2023-12-15 15:20:45*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
%ADD10R,1.000000X2.510000*%
G04 APERTURE END LIST*
D10*
G04 #@! TO.C,J11*
X22950000Y30775000D03*
X20410000Y27465000D03*
X17870000Y30775000D03*
X15330000Y27465000D03*
X12790000Y30775000D03*
X10250000Y27465000D03*
G04 #@! TD*
M02*

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,100 @@
G04 #@! TF.GenerationSoftware,KiCad,Pcbnew,7.0.9-7.0.9~ubuntu22.04.1*
G04 #@! TF.CreationDate,2023-12-15T15:20:45-05:00*
G04 #@! TF.ProjectId,tinytapeout-demo,74696e79-7461-4706-956f-75742d64656d,0.9.4*
G04 #@! TF.SameCoordinates,PX35e1f20PY8044ea0*
G04 #@! TF.FileFunction,Profile,NP*
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9-7.0.9~ubuntu22.04.1) date 2023-12-15 15:20:45*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
G04 #@! TA.AperFunction,Profile*
%ADD10C,0.100000*%
G04 #@! TD*
G04 #@! TA.AperFunction,Profile*
%ADD11C,0.080000*%
G04 #@! TD*
G04 APERTURE END LIST*
D10*
X23630000Y80600000D02*
G75*
G03*
X23230000Y81000000I-400000J0D01*
G01*
X33270000Y81000000D02*
G75*
G03*
X32870000Y80600000I0J-400000D01*
G01*
X104500000Y78000000D02*
G75*
G03*
X101500000Y81000000I-3000000J0D01*
G01*
X33270000Y81000000D02*
X101500000Y81000000D01*
X101500000Y0D02*
X3000000Y0D01*
X104500000Y78000000D02*
X104500000Y3000000D01*
X0Y3000000D02*
G75*
G03*
X3000000Y0I3000000J0D01*
G01*
X23230000Y81000000D02*
X3000000Y81000000D01*
X3000000Y81000000D02*
G75*
G03*
X0Y78000000I0J-3000000D01*
G01*
X0Y3000000D02*
X0Y78000000D01*
X101500000Y0D02*
G75*
G03*
X104500000Y3000000I0J3000000D01*
G01*
D11*
G04 #@! TO.C,J15*
X32870000Y80600000D02*
X32870000Y74400000D01*
X32070000Y74400000D02*
X24430000Y74400000D01*
X23630000Y74400000D02*
X23630000Y80600000D01*
X32545000Y74000001D02*
G75*
G03*
X32869999Y74400000I-37502J362500D01*
G01*
X32220001Y74280000D02*
G75*
G03*
X32545000Y74000001I302498J22499D01*
G01*
X32219999Y74280000D02*
G75*
G03*
X32070001Y74399998I-134999J-15001D01*
G01*
X24430000Y74399998D02*
G75*
G03*
X24280002Y74279999I-15000J-134998D01*
G01*
X23955000Y74000003D02*
G75*
G03*
X24279997Y74279999I22500J302497D01*
G01*
X23630000Y74400000D02*
G75*
G03*
X23955000Y74000000I362500J-37500D01*
G01*
G04 #@! TD*
M02*

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,865 @@
G04 #@! TF.GenerationSoftware,KiCad,Pcbnew,7.0.9-7.0.9~ubuntu22.04.1*
G04 #@! TF.CreationDate,2023-12-15T15:20:44-05:00*
G04 #@! TF.ProjectId,tinytapeout-demo,74696e79-7461-4706-956f-75742d64656d,0.9.4*
G04 #@! TF.SameCoordinates,PX35e1f20PY8044ea0*
G04 #@! TF.FileFunction,Paste,Top*
G04 #@! TF.FilePolarity,Positive*
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9-7.0.9~ubuntu22.04.1) date 2023-12-15 15:20:44*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
G04 Aperture macros list*
%AMRoundRect*
0 Rectangle with rounded corners*
0 $1 Rounding radius*
0 $2 $3 $4 $5 $6 $7 $8 $9 X,Y pos of 4 corners*
0 Add a 4 corners polygon primitive as box body*
4,1,4,$2,$3,$4,$5,$6,$7,$8,$9,$2,$3,0*
0 Add four circle primitives for the rounded corners*
1,1,$1+$1,$2,$3*
1,1,$1+$1,$4,$5*
1,1,$1+$1,$6,$7*
1,1,$1+$1,$8,$9*
0 Add four rect primitives between the rounded corners*
20,1,$1+$1,$2,$3,$4,$5,0*
20,1,$1+$1,$4,$5,$6,$7,0*
20,1,$1+$1,$6,$7,$8,$9,0*
20,1,$1+$1,$8,$9,$2,$3,0*%
G04 Aperture macros list end*
%ADD10RoundRect,0.225000X0.225000X0.250000X-0.225000X0.250000X-0.225000X-0.250000X0.225000X-0.250000X0*%
%ADD11RoundRect,0.135000X0.185000X-0.135000X0.185000X0.135000X-0.185000X0.135000X-0.185000X-0.135000X0*%
%ADD12RoundRect,0.225000X-0.250000X0.225000X-0.250000X-0.225000X0.250000X-0.225000X0.250000X0.225000X0*%
%ADD13RoundRect,0.140000X-0.140000X-0.170000X0.140000X-0.170000X0.140000X0.170000X-0.140000X0.170000X0*%
%ADD14RoundRect,0.135000X-0.185000X0.135000X-0.185000X-0.135000X0.185000X-0.135000X0.185000X0.135000X0*%
%ADD15RoundRect,0.135000X0.135000X0.185000X-0.135000X0.185000X-0.135000X-0.185000X0.135000X-0.185000X0*%
%ADD16R,0.600000X1.100000*%
%ADD17R,0.300000X1.100000*%
%ADD18RoundRect,0.250000X0.250000X0.475000X-0.250000X0.475000X-0.250000X-0.475000X0.250000X-0.475000X0*%
%ADD19RoundRect,0.135000X-0.135000X-0.185000X0.135000X-0.185000X0.135000X0.185000X-0.135000X0.185000X0*%
%ADD20RoundRect,0.140000X-0.170000X0.140000X-0.170000X-0.140000X0.170000X-0.140000X0.170000X0.140000X0*%
%ADD21RoundRect,0.200000X0.200000X0.275000X-0.200000X0.275000X-0.200000X-0.275000X0.200000X-0.275000X0*%
%ADD22RoundRect,0.140000X0.170000X-0.140000X0.170000X0.140000X-0.170000X0.140000X-0.170000X-0.140000X0*%
%ADD23R,1.400000X1.200000*%
%ADD24RoundRect,0.225000X-0.225000X-0.250000X0.225000X-0.250000X0.225000X0.250000X-0.225000X0.250000X0*%
%ADD25RoundRect,0.140000X0.140000X0.170000X-0.140000X0.170000X-0.140000X-0.170000X0.140000X-0.170000X0*%
%ADD26RoundRect,0.225000X0.250000X-0.225000X0.250000X0.225000X-0.250000X0.225000X-0.250000X-0.225000X0*%
%ADD27R,1.500000X1.400000*%
%ADD28R,1.000000X2.510000*%
%ADD29R,2.200000X1.100000*%
%ADD30RoundRect,0.150000X-0.150000X0.650000X-0.150000X-0.650000X0.150000X-0.650000X0.150000X0.650000X0*%
%ADD31RoundRect,0.200000X0.300000X-0.950000X0.300000X0.950000X-0.300000X0.950000X-0.300000X-0.950000X0*%
%ADD32R,1.000000X2.300000*%
%ADD33RoundRect,0.150000X-0.650000X-0.150000X0.650000X-0.150000X0.650000X0.150000X-0.650000X0.150000X0*%
%ADD34RoundRect,0.249999X-0.395001X-0.395001X0.395001X-0.395001X0.395001X0.395001X-0.395001X0.395001X0*%
%ADD35RoundRect,0.050000X-0.387500X-0.050000X0.387500X-0.050000X0.387500X0.050000X-0.387500X0.050000X0*%
%ADD36RoundRect,0.050000X-0.050000X-0.387500X0.050000X-0.387500X0.050000X0.387500X-0.050000X0.387500X0*%
%ADD37RoundRect,0.243750X0.243750X0.456250X-0.243750X0.456250X-0.243750X-0.456250X0.243750X-0.456250X0*%
%ADD38RoundRect,0.200000X0.200000X0.250000X-0.200000X0.250000X-0.200000X-0.250000X0.200000X-0.250000X0*%
%ADD39RoundRect,0.200000X0.275000X-0.200000X0.275000X0.200000X-0.275000X0.200000X-0.275000X-0.200000X0*%
%ADD40RoundRect,0.100000X0.100000X-0.637500X0.100000X0.637500X-0.100000X0.637500X-0.100000X-0.637500X0*%
%ADD41RoundRect,0.150000X-0.150000X0.512500X-0.150000X-0.512500X0.150000X-0.512500X0.150000X0.512500X0*%
%ADD42R,1.500000X2.000000*%
%ADD43R,3.800000X2.000000*%
%ADD44R,3.150000X1.000000*%
%ADD45RoundRect,0.218750X0.256250X-0.218750X0.256250X0.218750X-0.256250X0.218750X-0.256250X-0.218750X0*%
%ADD46RoundRect,0.250000X-0.250000X-0.475000X0.250000X-0.475000X0.250000X0.475000X-0.250000X0.475000X0*%
%ADD47RoundRect,0.218750X-0.218750X-0.256250X0.218750X-0.256250X0.218750X0.256250X-0.218750X0.256250X0*%
G04 APERTURE END LIST*
D10*
G04 #@! TO.C,C21*
X94775000Y74500000D03*
X93225000Y74500000D03*
G04 #@! TD*
D11*
G04 #@! TO.C,R15*
X99500000Y70790000D03*
X99500000Y71810000D03*
G04 #@! TD*
D12*
G04 #@! TO.C,C32*
X28200000Y27700000D03*
X28200000Y26150000D03*
G04 #@! TD*
D13*
G04 #@! TO.C,C23*
X68195000Y72000000D03*
X69155000Y72000000D03*
G04 #@! TD*
G04 #@! TO.C,C36*
X26920000Y14250000D03*
X27880000Y14250000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,R20*
X26900000Y71710000D03*
X26900000Y70690000D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R38*
X24360000Y31050000D03*
X23340000Y31050000D03*
G04 #@! TD*
D16*
G04 #@! TO.C,J15*
X31450000Y73550000D03*
X30650000Y73550000D03*
D17*
X29500000Y73550000D03*
X28500000Y73550000D03*
X28000000Y73550000D03*
X27000000Y73550000D03*
D16*
X25050000Y73550000D03*
X25850000Y73550000D03*
D17*
X26500000Y73550000D03*
X27500000Y73550000D03*
X29000000Y73550000D03*
X30000000Y73550000D03*
G04 #@! TD*
D18*
G04 #@! TO.C,C6*
X16190000Y67290000D03*
X14290000Y67290000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R5*
X14390000Y52271429D03*
X15410000Y52271429D03*
G04 #@! TD*
D20*
G04 #@! TO.C,C56*
X8700000Y23880000D03*
X8700000Y22920000D03*
G04 #@! TD*
D18*
G04 #@! TO.C,C7*
X20800000Y79700000D03*
X18900000Y79700000D03*
G04 #@! TD*
D21*
G04 #@! TO.C,R7*
X42325000Y14750000D03*
X40675000Y14750000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R1*
X14390000Y42100000D03*
X15410000Y42100000D03*
G04 #@! TD*
D22*
G04 #@! TO.C,C29*
X22400000Y10870000D03*
X22400000Y11830000D03*
G04 #@! TD*
D13*
G04 #@! TO.C,C24*
X68195000Y29000000D03*
X69155000Y29000000D03*
G04 #@! TD*
G04 #@! TO.C,C8*
X47595000Y11100000D03*
X48555000Y11100000D03*
G04 #@! TD*
D23*
G04 #@! TO.C,Y1*
X23900000Y10900000D03*
X26100000Y10900000D03*
X26100000Y12600000D03*
X23900000Y12600000D03*
G04 #@! TD*
D11*
G04 #@! TO.C,R18*
X90400000Y70790000D03*
X90400000Y71810000D03*
G04 #@! TD*
D20*
G04 #@! TO.C,C58*
X8500000Y69880000D03*
X8500000Y68920000D03*
G04 #@! TD*
D13*
G04 #@! TO.C,C27*
X11120000Y25400000D03*
X12080000Y25400000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R11*
X14390000Y59900000D03*
X15410000Y59900000D03*
G04 #@! TD*
D24*
G04 #@! TO.C,C16*
X65400000Y13260000D03*
X66950000Y13260000D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R43*
X93810000Y59500000D03*
X92790000Y59500000D03*
G04 #@! TD*
D22*
G04 #@! TO.C,C4*
X17020000Y6165000D03*
X17020000Y7125000D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R24*
X95765000Y26808572D03*
X94745000Y26808572D03*
G04 #@! TD*
G04 #@! TO.C,R22*
X95765000Y31820000D03*
X94745000Y31820000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R3*
X14390000Y47185715D03*
X15410000Y47185715D03*
G04 #@! TD*
D13*
G04 #@! TO.C,C2*
X63020000Y14900000D03*
X63980000Y14900000D03*
G04 #@! TD*
D25*
G04 #@! TO.C,C18*
X69155000Y26500000D03*
X68195000Y26500000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,R41*
X39250000Y14547500D03*
X39250000Y13527500D03*
G04 #@! TD*
D26*
G04 #@! TO.C,C42*
X87100000Y70425000D03*
X87100000Y71975000D03*
G04 #@! TD*
D27*
G04 #@! TO.C,SW2*
X67250000Y10000000D03*
X60750000Y10000000D03*
G04 #@! TD*
D11*
G04 #@! TO.C,R8*
X61100000Y12890000D03*
X61100000Y13910000D03*
G04 #@! TD*
D25*
G04 #@! TO.C,C28*
X87680000Y65800000D03*
X86720000Y65800000D03*
G04 #@! TD*
D13*
G04 #@! TO.C,C35*
X32120000Y19200000D03*
X33080000Y19200000D03*
G04 #@! TD*
D28*
G04 #@! TO.C,J7*
X63190000Y21245000D03*
X60650000Y24555000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R12*
X40990000Y13250000D03*
X42010000Y13250000D03*
G04 #@! TD*
D10*
G04 #@! TO.C,C25*
X94775000Y76040000D03*
X93225000Y76040000D03*
G04 #@! TD*
D12*
G04 #@! TO.C,C5*
X18400000Y7675000D03*
X18400000Y6125000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R13*
X61090000Y14900000D03*
X62110000Y14900000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,R21*
X30200000Y70910000D03*
X30200000Y69890000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R42*
X86790000Y73200000D03*
X87810000Y73200000D03*
G04 #@! TD*
G04 #@! TO.C,R2*
X14390000Y44642858D03*
X15410000Y44642858D03*
G04 #@! TD*
D29*
G04 #@! TO.C,SW4*
X17600000Y59890000D03*
X17600000Y57350000D03*
X17600000Y54810000D03*
X17600000Y52270000D03*
X17600000Y49730000D03*
X17600000Y47190000D03*
X17600000Y44650000D03*
X17600000Y42110000D03*
X26400000Y42110000D03*
X26400000Y44650000D03*
X26400000Y47190000D03*
X26400000Y49730000D03*
X26400000Y52270000D03*
X26400000Y54810000D03*
X26400000Y57350000D03*
X26400000Y59890000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,R19*
X95250000Y64010000D03*
X95250000Y62990000D03*
G04 #@! TD*
D30*
G04 #@! TO.C,U7*
X93905000Y57600000D03*
X92635000Y57600000D03*
X91365000Y57600000D03*
X90095000Y57600000D03*
X90095000Y50400000D03*
X91365000Y50400000D03*
X92635000Y50400000D03*
X93905000Y50400000D03*
G04 #@! TD*
D20*
G04 #@! TO.C,C30*
X27700000Y12630000D03*
X27700000Y11670000D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R28*
X95765000Y16785716D03*
X94745000Y16785716D03*
G04 #@! TD*
D25*
G04 #@! TO.C,C39*
X21280000Y24100000D03*
X20320000Y24100000D03*
G04 #@! TD*
D31*
G04 #@! TO.C,U1*
X87050000Y17550000D03*
D32*
X88550000Y17550000D03*
X90050000Y17550000D03*
X91550000Y17550000D03*
X93050000Y17550000D03*
X93050000Y28450000D03*
X91550000Y28450000D03*
X90050000Y28450000D03*
X88550000Y28450000D03*
X87050000Y28450000D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R23*
X95765000Y29314286D03*
X94745000Y29314286D03*
G04 #@! TD*
D25*
G04 #@! TO.C,C38*
X20180000Y19300000D03*
X19220000Y19300000D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R45*
X93810000Y47600000D03*
X92790000Y47600000D03*
G04 #@! TD*
D33*
G04 #@! TO.C,U5*
X14400000Y31905000D03*
X14400000Y30635000D03*
X14400000Y29365000D03*
X14400000Y28095000D03*
X21600000Y28095000D03*
X21600000Y29365000D03*
X21600000Y30635000D03*
X21600000Y31905000D03*
G04 #@! TD*
D26*
G04 #@! TO.C,C37*
X29700000Y26125000D03*
X29700000Y27675000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R35*
X11740000Y29035000D03*
X12760000Y29035000D03*
G04 #@! TD*
D34*
G04 #@! TO.C,U6*
X25200000Y21300000D03*
X25200000Y19700000D03*
X26800000Y21300000D03*
X26800000Y19700000D03*
D35*
X22562500Y23100000D03*
X22562500Y22700000D03*
X22562500Y22300000D03*
X22562500Y21900000D03*
X22562500Y21500000D03*
X22562500Y21100000D03*
X22562500Y20700000D03*
X22562500Y20300000D03*
X22562500Y19900000D03*
X22562500Y19500000D03*
X22562500Y19100000D03*
X22562500Y18700000D03*
X22562500Y18300000D03*
X22562500Y17900000D03*
D36*
X23400000Y17062500D03*
X23800000Y17062500D03*
X24200000Y17062500D03*
X24600000Y17062500D03*
X25000000Y17062500D03*
X25400000Y17062500D03*
X25800000Y17062500D03*
X26200000Y17062500D03*
X26600000Y17062500D03*
X27000000Y17062500D03*
X27400000Y17062500D03*
X27800000Y17062500D03*
X28200000Y17062500D03*
X28600000Y17062500D03*
D35*
X29437500Y17900000D03*
X29437500Y18300000D03*
X29437500Y18700000D03*
X29437500Y19100000D03*
X29437500Y19500000D03*
X29437500Y19900000D03*
X29437500Y20300000D03*
X29437500Y20700000D03*
X29437500Y21100000D03*
X29437500Y21500000D03*
X29437500Y21900000D03*
X29437500Y22300000D03*
X29437500Y22700000D03*
X29437500Y23100000D03*
D36*
X28600000Y23937500D03*
X28200000Y23937500D03*
X27800000Y23937500D03*
X27400000Y23937500D03*
X27000000Y23937500D03*
X26600000Y23937500D03*
X26200000Y23937500D03*
X25800000Y23937500D03*
X25400000Y23937500D03*
X25000000Y23937500D03*
X24600000Y23937500D03*
X24200000Y23937500D03*
X23800000Y23937500D03*
X23400000Y23937500D03*
G04 #@! TD*
D22*
G04 #@! TO.C,C33*
X31100000Y25920000D03*
X31100000Y26880000D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R27*
X95765000Y19291430D03*
X94745000Y19291430D03*
G04 #@! TD*
D13*
G04 #@! TO.C,C20*
X68195000Y55500000D03*
X69155000Y55500000D03*
G04 #@! TD*
D10*
G04 #@! TO.C,C9*
X49150000Y9900000D03*
X47600000Y9900000D03*
G04 #@! TD*
D37*
G04 #@! TO.C,F1*
X19627500Y67302500D03*
X17752500Y67302500D03*
G04 #@! TD*
D13*
G04 #@! TO.C,C12*
X15020000Y80200000D03*
X15980000Y80200000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R40*
X11740000Y30335000D03*
X12760000Y30335000D03*
G04 #@! TD*
D11*
G04 #@! TO.C,R37*
X25900000Y13990000D03*
X25900000Y15010000D03*
G04 #@! TD*
D13*
G04 #@! TO.C,C26*
X68195000Y23500000D03*
X69155000Y23500000D03*
G04 #@! TD*
G04 #@! TO.C,C19*
X68195000Y69500000D03*
X69155000Y69500000D03*
G04 #@! TD*
D38*
G04 #@! TO.C,X1*
X88125000Y66975000D03*
X88125000Y68825000D03*
X86675000Y68825000D03*
X86675000Y66975000D03*
G04 #@! TD*
D10*
G04 #@! TO.C,C15*
X71225000Y8900000D03*
X69675000Y8900000D03*
G04 #@! TD*
D27*
G04 #@! TO.C,SW3*
X1650000Y52900000D03*
X8150000Y52900000D03*
G04 #@! TD*
D39*
G04 #@! TO.C,R16*
X37200000Y74875000D03*
X37200000Y76525000D03*
G04 #@! TD*
D24*
G04 #@! TO.C,C17*
X65400000Y14760000D03*
X66950000Y14760000D03*
G04 #@! TD*
D40*
G04 #@! TO.C,U4*
X11725000Y18137500D03*
X12375000Y18137500D03*
X13025000Y18137500D03*
X13675000Y18137500D03*
X14325000Y18137500D03*
X14975000Y18137500D03*
X15625000Y18137500D03*
X16275000Y18137500D03*
X16275000Y23862500D03*
X15625000Y23862500D03*
X14975000Y23862500D03*
X14325000Y23862500D03*
X13675000Y23862500D03*
X13025000Y23862500D03*
X12375000Y23862500D03*
X11725000Y23862500D03*
G04 #@! TD*
D27*
G04 #@! TO.C,SW1*
X44750000Y10000000D03*
X38250000Y10000000D03*
G04 #@! TD*
D26*
G04 #@! TO.C,C3*
X35700000Y74925000D03*
X35700000Y76475000D03*
G04 #@! TD*
D11*
G04 #@! TO.C,R17*
X94550000Y70790000D03*
X94550000Y71810000D03*
G04 #@! TD*
D13*
G04 #@! TO.C,C34*
X31570000Y23900000D03*
X32530000Y23900000D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R31*
X37510000Y64600000D03*
X36490000Y64600000D03*
G04 #@! TD*
D41*
G04 #@! TO.C,U3*
X21050000Y77637500D03*
X20100000Y77637500D03*
X19150000Y77637500D03*
X19150000Y75362500D03*
X21050000Y75362500D03*
G04 #@! TD*
D20*
G04 #@! TO.C,C60*
X8400000Y47030000D03*
X8400000Y46070000D03*
G04 #@! TD*
D42*
G04 #@! TO.C,U2*
X12350000Y69850000D03*
X14650000Y69850000D03*
D43*
X14650000Y76150000D03*
D42*
X16950000Y69850000D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R25*
X95765000Y24302858D03*
X94745000Y24302858D03*
G04 #@! TD*
G04 #@! TO.C,R29*
X95765000Y14280000D03*
X94745000Y14280000D03*
G04 #@! TD*
D26*
G04 #@! TO.C,C61*
X7150000Y45425000D03*
X7150000Y46975000D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R47*
X93810000Y46600000D03*
X92790000Y46600000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R6*
X14390000Y54814286D03*
X15410000Y54814286D03*
G04 #@! TD*
D10*
G04 #@! TO.C,C11*
X20925000Y73600000D03*
X19375000Y73600000D03*
G04 #@! TD*
D44*
G04 #@! TO.C,J4*
X41395000Y72030000D03*
X46445000Y72030000D03*
X41395000Y69490000D03*
X46445000Y69490000D03*
X41395000Y66950000D03*
X46445000Y66950000D03*
X41395000Y64410000D03*
X46445000Y64410000D03*
X41395000Y61870000D03*
X46445000Y61870000D03*
X41395000Y59330000D03*
X46445000Y59330000D03*
X41395000Y56790000D03*
X46445000Y56790000D03*
X41395000Y54250000D03*
X46445000Y54250000D03*
X41395000Y51710000D03*
X46445000Y51710000D03*
X41395000Y49170000D03*
X46445000Y49170000D03*
X41395000Y46630000D03*
X46445000Y46630000D03*
X41395000Y44090000D03*
X46445000Y44090000D03*
X41395000Y41550000D03*
X46445000Y41550000D03*
X41395000Y39010000D03*
X46445000Y39010000D03*
X41395000Y36470000D03*
X46445000Y36470000D03*
X41395000Y33930000D03*
X46445000Y33930000D03*
X41395000Y31390000D03*
X46445000Y31390000D03*
X41395000Y28850000D03*
X46445000Y28850000D03*
X41395000Y26310000D03*
X46445000Y26310000D03*
X41395000Y23770000D03*
X46445000Y23770000D03*
X77395000Y72030000D03*
X82445000Y72030000D03*
X77395000Y69490000D03*
X82445000Y69490000D03*
X77395000Y66950000D03*
X82445000Y66950000D03*
X77395000Y64410000D03*
X82445000Y64410000D03*
X77395000Y61870000D03*
X82445000Y61870000D03*
X77395000Y59330000D03*
X82445000Y59330000D03*
X77395000Y56790000D03*
X82445000Y56790000D03*
X77395000Y54250000D03*
X82445000Y54250000D03*
X77395000Y51710000D03*
X82445000Y51710000D03*
X77395000Y49170000D03*
X82445000Y49170000D03*
X77395000Y46630000D03*
X82445000Y46630000D03*
X77395000Y44090000D03*
X82445000Y44090000D03*
X77395000Y41550000D03*
X82445000Y41550000D03*
X77395000Y39010000D03*
X82445000Y39010000D03*
X77395000Y36470000D03*
X82445000Y36470000D03*
X77395000Y33930000D03*
X82445000Y33930000D03*
X77395000Y31390000D03*
X82445000Y31390000D03*
X77395000Y28850000D03*
X82445000Y28850000D03*
X77395000Y26310000D03*
X82445000Y26310000D03*
X77395000Y23770000D03*
X82445000Y23770000D03*
G04 #@! TD*
D25*
G04 #@! TO.C,C43*
X95280000Y48800000D03*
X94320000Y48800000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R10*
X14390000Y57357143D03*
X15410000Y57357143D03*
G04 #@! TD*
D45*
G04 #@! TO.C,D2*
X95800000Y70212500D03*
X95800000Y71787500D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R30*
X36490000Y68900000D03*
X37510000Y68900000D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R44*
X93810000Y60600000D03*
X92790000Y60600000D03*
G04 #@! TD*
G04 #@! TO.C,R26*
X95765000Y21797144D03*
X94745000Y21797144D03*
G04 #@! TD*
D22*
G04 #@! TO.C,C41*
X25800000Y26070000D03*
X25800000Y27030000D03*
G04 #@! TD*
D25*
G04 #@! TO.C,C13*
X21080000Y72100000D03*
X20120000Y72100000D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R48*
X91810000Y47600000D03*
X90790000Y47600000D03*
G04 #@! TD*
D26*
G04 #@! TO.C,C57*
X7200000Y22325000D03*
X7200000Y23875000D03*
G04 #@! TD*
D13*
G04 #@! TO.C,C14*
X69620000Y10200000D03*
X70580000Y10200000D03*
G04 #@! TD*
D45*
G04 #@! TO.C,D3*
X91700000Y70212500D03*
X91700000Y71787500D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R34*
X24410000Y29450000D03*
X23390000Y29450000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R9*
X6990000Y49500000D03*
X8010000Y49500000D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R46*
X91810000Y46600000D03*
X90790000Y46600000D03*
G04 #@! TD*
D14*
G04 #@! TO.C,R33*
X10500000Y23810000D03*
X10500000Y22790000D03*
G04 #@! TD*
D45*
G04 #@! TO.C,D1*
X100800000Y70212500D03*
X100800000Y71787500D03*
G04 #@! TD*
D15*
G04 #@! TO.C,R39*
X24410000Y27700000D03*
X23390000Y27700000D03*
G04 #@! TD*
D46*
G04 #@! TO.C,C10*
X15000000Y78540000D03*
X16900000Y78540000D03*
G04 #@! TD*
D47*
G04 #@! TO.C,D4*
X91712500Y63500000D03*
X93287500Y63500000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R32*
X36490000Y63300000D03*
X37510000Y63300000D03*
G04 #@! TD*
G04 #@! TO.C,R4*
X14390000Y49728572D03*
X15410000Y49728572D03*
G04 #@! TD*
D13*
G04 #@! TO.C,C40*
X27170000Y15200000D03*
X28130000Y15200000D03*
G04 #@! TD*
D26*
G04 #@! TO.C,C1*
X44000000Y13125000D03*
X44000000Y14675000D03*
G04 #@! TD*
D11*
G04 #@! TO.C,R36*
X6600000Y27890000D03*
X6600000Y28910000D03*
G04 #@! TD*
D26*
G04 #@! TO.C,C59*
X7200000Y68325000D03*
X7200000Y69875000D03*
G04 #@! TD*
D13*
G04 #@! TO.C,C31*
X21070000Y33400000D03*
X22030000Y33400000D03*
G04 #@! TD*
G04 #@! TO.C,C22*
X68195000Y31540000D03*
X69155000Y31540000D03*
G04 #@! TD*
D19*
G04 #@! TO.C,R14*
X1090000Y49500000D03*
X2110000Y49500000D03*
G04 #@! TD*
M02*

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
M48
; DRILL file {KiCad 7.0.9-7.0.9~ubuntu22.04.1} date Fri 15 Dec 2023 03:20:48 PM
; FORMAT={-:-/ absolute / metric / decimal}
; #@! TF.CreationDate,2023-12-15T15:20:48-05:00
; #@! TF.GenerationSoftware,Kicad,Pcbnew,7.0.9-7.0.9~ubuntu22.04.1
; #@! TF.FileFunction,NonPlated,1,4,NPTH
FMAT,2
METRIC
%
G90
G05
T0
M30

View file

@ -0,0 +1,740 @@
M48
; DRILL file {KiCad 7.0.9-7.0.9~ubuntu22.04.1} date Fri 15 Dec 2023 03:20:48 PM
; FORMAT={-:-/ absolute / metric / decimal}
; #@! TF.CreationDate,2023-12-15T15:20:48-05:00
; #@! TF.GenerationSoftware,Kicad,Pcbnew,7.0.9-7.0.9~ubuntu22.04.1
; #@! TF.FileFunction,Plated,1,4,PTH
FMAT,2
METRIC
; #@! TA.AperFunction,Plated,PTH,ViaDrill
T1C0.300
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T2C0.300
; #@! TA.AperFunction,Plated,PTH,ViaDrill
T3C0.400
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T4C0.600
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T5C1.000
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T6C3.200
%
G90
G05
T1
X4.9Y33.08
X5.724Y14.712
X5.998Y61.12
X6.433Y15.702
X6.525Y31.2
X6.543Y27.545
X6.632Y33.758
X6.677Y35.792
X7.055Y30.205
X7.067Y37.015
X7.2Y74.5
X7.2Y70.9
X7.3Y8.5
X7.382Y60.53
X7.8Y75.1
X8.2Y60.015
X8.3Y20.2
X8.62Y50.5
X8.65Y45.3
X8.674Y34.7
X9.1Y21.5
X9.109Y27.286
X9.252Y51.2
X9.4Y57.1
X9.507Y36.529
X9.6Y62.6
X9.6Y55.495
X9.6Y37.6
X9.65Y25.85
X9.9Y22.6
X10.025Y42.765
X10.152Y18.5
X10.154Y35.758
X10.4Y58.267
X10.47Y47.994
X10.47Y41.061
X10.8Y10.9
X11.102Y53.002
X11.903Y64.183
X12.18Y25.177
X12.2Y19.4
X12.5Y37.3
X12.58Y26.638
X12.626Y60.63
X12.8Y31.3
X13.029Y19.296
X13.45Y41.078
X13.5Y57.2
X13.5Y54.8
X13.5Y52.3
X13.5Y49.7
X13.5Y47.186
X13.5Y44.6
X13.506Y59.594
X13.709Y37.14
X13.9Y16.2
X14.157Y26.345
X14.232Y20.995
X14.291Y45.691
X14.322Y19.3
X14.325Y24.9
X14.5Y48.71
X14.942Y22.0
X14.989Y32.433
X15.25Y30.9
X15.558Y19.175
X15.639Y25.176
X15.665Y28.665
X16.1Y14.0
X16.2Y7.15
X16.287Y20.594
X16.555Y35.375
X16.602Y25.175
X16.9Y16.7
X16.924Y66.013
X17.1Y80.2
X17.1Y7.8
X17.346Y17.7
X17.6Y18.7
X17.648Y27.821
X17.68Y23.7
X17.85Y80.15
X18.0Y78.2
X18.0Y25.8
X18.175Y29.2
X18.221Y33.068
X18.807Y23.02
X18.9Y17.95
X19.0Y19.8
X19.2Y72.2
X19.25Y52.6
X19.476Y24.326
X19.5Y25.3
X19.762Y34.011
X19.77Y40.372
X19.964Y19.794
X20.1Y76.3
X20.194Y71.302
X20.3Y32.5
X20.436Y7.086
X20.476Y33.496
X20.527Y50.527
X21.057Y42.373
X21.2Y43.7
X21.3Y56.8
X21.3Y25.738
X21.3Y8.8
X21.5Y17.712
X21.922Y12.6
X22.4Y46.5
X22.521Y35.879
X22.637Y17.062
X22.7Y4.342
X22.8Y63.052
X22.912Y30.238
X23.1Y52.2
X23.1Y33.3
X23.7Y9.474
X23.73Y28.529
X23.9Y44.5
X23.981Y38.78
X24.026Y56.332
X24.1Y47.2
X24.159Y22.895
X24.65Y15.75
X24.738Y26.064
X24.898Y17.987
X25.099Y8.351
X25.312Y65.093
X25.442Y43.219
X25.675Y25.261
X25.8Y22.7
X26.2Y31.7
X26.407Y18.197
X26.473Y7.48
X26.506Y26.871
X26.924Y25.616
X27.1Y27.7
X27.247Y18.064
X27.4Y72.5
X27.55Y10.9
X27.615Y25.134
X28.162Y10.288
X28.527Y18.118
X28.671Y71.51
X28.8Y72.3
X28.85Y15.195
X28.85Y14.3
X29.444Y47.837
X29.5Y16.95
X29.6Y12.3
X30.11Y47.19
X30.3Y0.7
X30.35Y28.9
X30.35Y16.988
X31.027Y28.166
X31.1Y8.838
X31.6Y53.9
X31.6Y46.1
X32.0Y13.6
X32.0Y10.6
X32.0Y7.877
X32.004Y27.007
X32.371Y26.106
X32.39Y18.578
X32.4Y24.8
X32.5Y64.6
X32.688Y36.627
X32.752Y1.195
X32.889Y50.725
X32.95Y12.25
X33.0Y44.3
X33.264Y24.631
X33.371Y18.578
X33.7Y49.1
X34.167Y31.3
X34.556Y59.572
X34.88Y52.938
X35.05Y1.7
X35.114Y34.5
X35.2Y9.6
X35.22Y68.56
X35.4Y14.4
X35.85Y8.15
X35.97Y51.3
X35.986Y55.098
X35.995Y64.747
X36.0Y62.9
X36.234Y24.6
X36.234Y17.456
X36.5Y41.6
X36.8Y73.9
X36.8Y13.0
X37.9Y33.9
X38.507Y15.088
X38.6Y23.5
X38.8Y72.97
X39.1Y8.185
X39.172Y64.887
X39.176Y54.413
X39.3Y32.7
X39.572Y72.174
X40.25Y0.85
X41.8Y32.7
X43.098Y12.473
X43.1Y14.6
X43.25Y10.0
X44.046Y68.625
X44.05Y12.3
X44.1Y74.3
X44.67Y41.735
X45.896Y11.603
X45.9Y17.0
X46.3Y25.5
X46.6Y11.1
X46.65Y12.35
X46.8Y73.4
X48.6Y23.2
X48.741Y58.685
X49.247Y61.197
X49.285Y20.7
X50.0Y10.7
X50.1Y11.55
X50.2Y26.1
X50.267Y42.867
X50.444Y66.3
X51.042Y58.24
X51.042Y22.5
X51.6Y69.4
X51.755Y56.2
X51.8Y61.7
X51.835Y20.133
X51.867Y65.88
X52.427Y30.5
X52.468Y9.3
X53.1Y62.7
X53.1Y50.6
X53.14Y22.1
X53.9Y19.7
X54.4Y70.985
X54.4Y58.705
X54.4Y55.9
X54.967Y45.7
X54.967Y18.184
X55.152Y48.545
X55.7Y20.8
X55.999Y63.785
X56.9Y67.2
X57.35Y17.95
X58.0Y53.05
X58.037Y28.0
X58.2Y7.7
X58.7Y14.1
X58.8Y38.9
X59.6Y15.7
X59.8Y23.0
X60.9Y65.8
X60.9Y1.3
X61.51Y12.543
X61.8Y13.9
X61.847Y10.1
X61.9Y7.0
X61.9Y70.312
X63.19Y21.245
X63.2Y16.4
X64.25Y67.75
X64.3Y8.4
X64.331Y14.066
X64.55Y5.6
X65.4Y15.8
X65.9Y12.5
X66.22Y64.9
X66.7Y38.7
X67.0Y11.79
X67.55Y74.2
X68.2Y46.5
X69.0Y12.4
X69.0Y9.8
X69.0Y8.4
X70.0Y59.6
X70.5Y70.9
X70.807Y13.207
X72.1Y39.832
X72.14Y46.8
X73.256Y12.575
X74.7Y49.2
X74.7Y37.322
X75.7Y43.915
X75.9Y11.867
X76.4Y45.4
X77.2Y36.6
X78.397Y40.097
X78.397Y11.167
X79.2Y60.4
X79.825Y43.7
X82.3Y10.5
X82.4Y68.435
X84.8Y74.0
X84.8Y45.7
X84.8Y9.7
X84.9Y13.207
X86.2Y57.9
X86.2Y51.9
X87.1Y30.0
X87.15Y16.2
X87.2Y25.2
X87.4Y63.2
X87.4Y50.0
X87.45Y8.95
X87.82Y68.14
X88.05Y20.6
X88.15Y26.2
X88.4Y76.7
X88.65Y19.8
X88.669Y70.061
X88.775Y24.298
X89.92Y7.4
X90.0Y54.2
X90.0Y49.1
X90.05Y25.9
X90.05Y19.9
X90.1Y73.5
X90.531Y69.425
X91.45Y26.0
X91.45Y19.9
X92.0Y76.7
X92.5Y52.7
X92.65Y45.98
X92.703Y56.625
X93.8Y70.2
X94.35Y25.2
X94.35Y17.7
X94.4Y73.2
X94.4Y65.0
X94.4Y60.7
X94.5Y48.1
X95.7Y69.6
X98.6Y69.9
X99.1Y41.4
X99.7Y11.8
X100.0Y10.5
X100.529Y8.941
X100.553Y13.147
X100.679Y11.735
X101.174Y10.441
X101.551Y9.0
T2
X0.7Y63.6
X0.7Y58.6
X0.7Y12.9
X0.9Y72.3
X3.4Y49.5
X3.7Y9.0
X7.2Y34.8
X8.1Y73.6
X8.1Y62.9
X8.45Y67.55
X8.5Y19.1
X8.5Y13.1
X8.6Y58.2
X8.6Y54.8
X8.7Y41.9
X10.3Y39.0
X10.3Y29.3
X11.2Y54.7
X11.25Y80.2
X12.3Y21.3
X12.4Y71.8
X12.5Y49.7
X12.5Y45.7
X12.5Y41.9
X12.5Y13.1
X12.5Y5.2
X12.5Y0.7
X17.6Y13.9
X19.7Y5.2
X20.0Y48.8
X20.2Y57.3
X23.0Y43.3
X23.6Y8.1
X23.75Y71.0
X24.0Y73.3
X24.2Y33.3
X24.4Y18.9
X24.5Y22.1
X26.0Y63.0
X26.0Y20.5
X27.0Y68.2
X27.7Y22.0
X27.7Y18.8
X29.0Y70.4
X31.2Y56.8
X31.4Y44.5
X31.9Y67.2
X32.8Y73.3
X34.4Y10.3
X34.5Y14.8
X35.2Y72.2
X35.5Y26.4
X35.5Y19.0
X35.7Y32.9
X35.7Y5.6
X37.8Y41.5
X39.9Y9.7
X40.2Y19.1
X40.4Y5.6
X42.9Y0.8
X43.6Y41.3
X43.9Y36.4
X43.9Y31.5
X47.98Y0.8
X48.1Y75.6
X48.8Y66.0
X50.1Y17.2
X52.0Y62.8
X53.0Y69.5
X53.06Y0.8
X53.1Y75.6
X54.9Y50.1
X55.7Y75.6
X56.5Y30.3
X56.5Y9.2
X56.6Y55.5
X56.7Y19.1
X58.14Y0.8
X58.7Y13.2
X62.2Y65.3
X62.2Y55.6
X62.2Y50.3
X62.2Y45.3
X62.2Y41.1
X62.2Y34.0
X62.2Y27.1
X62.2Y19.1
X63.2Y6.3
X63.22Y0.8
X63.3Y75.6
X66.6Y31.6
X66.6Y29.0
X66.6Y26.5
X66.6Y23.5
X67.4Y75.6
X68.3Y0.8
X70.9Y75.6
X71.2Y65.2
X71.2Y59.4
X71.2Y50.9
X71.3Y46.3
X71.3Y38.8
X71.3Y32.6
X71.3Y27.4
X71.3Y22.1
X71.6Y12.0
X73.38Y0.8
X73.6Y75.6
X74.4Y73.5
X78.46Y0.8
X78.9Y73.5
X80.7Y14.2
X81.1Y75.6
X83.5Y6.8
X83.54Y0.8
X83.6Y75.6
X86.0Y43.1
X86.0Y37.5
X86.0Y31.4
X86.2Y75.6
X86.85Y14.2
X87.1Y23.0
X88.62Y0.8
X88.7Y49.4
X88.8Y6.8
X90.05Y23.0
X90.1Y59.2
X91.8Y75.6
X92.9Y23.0
X93.4Y55.1
X95.3Y73.6
X95.8Y25.6
X95.8Y18.0
X95.9Y59.4
X96.2Y43.1
X96.2Y37.5
X97.7Y71.7
X101.4Y26.6
X101.4Y24.1
X101.4Y21.5
X103.7Y69.1
X103.7Y64.1
X103.7Y59.1
X103.7Y54.1
X103.7Y49.1
X103.7Y44.1
X103.7Y39.1
X103.7Y34.1
X103.7Y29.1
T3
X11.2Y69.8
X12.7Y68.2
X13.1Y67.3
X21.1Y69.5
X21.7Y72.8
X23.1Y41.6
X24.701Y72.305
X25.75Y72.252
X26.2Y67.3
X30.6Y72.4
X30.7Y67.3
X31.7Y72.4
X36.435Y49.27
X38.027Y66.715
X39.2Y61.6
X41.5Y76.0
X41.5Y70.62
X41.5Y65.3
X43.4Y53.9
X43.6Y49.1
X44.0Y24.8
X46.5Y62.8
X48.3Y24.6
X48.7Y72.0
X67.6Y73.0
X67.6Y70.5
X67.6Y54.9
X69.0Y27.5
X69.0Y24.5
X69.25Y77.386
X69.25Y53.915
X69.25Y51.389
X69.47Y70.538
X69.56Y73.066
X70.0Y55.105
X70.0Y31.5
X70.0Y29.0
X75.1Y41.7
X75.4Y56.8
X79.0Y48.3
X79.4Y33.9
X80.1Y28.8
X80.2Y31.4
X80.2Y26.3
X80.2Y23.8
X80.6Y72.0
X80.7Y50.9
X82.5Y60.4
X82.6Y43.1
X83.5Y68.6
X84.7Y49.1
X87.15Y21.8
X88.5Y54.5
X88.669Y56.69
X96.5Y4.4
X96.9Y79.4
X96.9Y78.5
X96.9Y70.7
X97.1Y5.3
T4
X96.9Y31.78
X96.9Y29.28
X96.9Y26.78
X96.9Y24.28
X96.9Y21.78
X96.9Y19.28
X96.9Y16.78
X96.9Y14.28
X99.9Y31.78
X99.9Y29.28
X99.9Y26.78
X99.9Y24.28
X99.9Y21.78
X99.9Y19.28
X99.9Y16.78
X99.9Y14.28
T5
X1.69Y29.26
X1.96Y70.0
X1.96Y67.46
X1.96Y64.92
X1.96Y62.38
X1.96Y59.84
X1.96Y57.3
X1.96Y47.14
X1.96Y44.6
X1.96Y42.06
X1.96Y39.52
X1.96Y36.98
X1.96Y34.44
X1.96Y24.28
X1.96Y21.74
X1.96Y19.2
X1.96Y16.66
X1.96Y14.12
X1.96Y11.58
X4.23Y29.26
X4.5Y70.0
X4.5Y67.46
X4.5Y64.92
X4.5Y62.38
X4.5Y59.84
X4.5Y57.3
X4.5Y47.14
X4.5Y44.6
X4.5Y42.06
X4.5Y39.52
X4.5Y36.98
X4.5Y34.44
X4.5Y24.28
X4.5Y21.74
X4.5Y19.2
X4.5Y16.66
X4.5Y14.12
X4.5Y11.58
X11.19Y3.0
X13.73Y3.0
X16.27Y3.0
X18.81Y3.0
X21.345Y5.545
X21.345Y3.005
X23.885Y5.545
X23.885Y3.005
X26.425Y5.545
X26.425Y3.005
X28.965Y5.545
X28.965Y3.005
X31.505Y5.545
X31.505Y3.005
X34.045Y5.545
X34.045Y3.005
X36.58Y79.0
X36.62Y3.0
X39.12Y79.0
X39.16Y3.0
X41.66Y79.0
X41.7Y3.0
X44.2Y79.0
X44.205Y5.545
X44.205Y3.005
X46.74Y79.0
X46.745Y5.545
X46.745Y3.005
X49.28Y79.0
X49.285Y5.545
X49.285Y3.005
X51.82Y79.0
X51.825Y5.545
X51.825Y3.005
X54.36Y79.0
X54.365Y5.545
X54.365Y3.005
X56.9Y79.0
X56.905Y5.545
X56.905Y3.005
X59.44Y79.0
X59.465Y3.0
X61.98Y79.0
X62.005Y3.0
X64.52Y79.0
X64.545Y3.0
X67.06Y79.0
X67.065Y5.545
X67.065Y3.005
X69.6Y79.0
X69.605Y5.545
X69.605Y3.005
X72.14Y79.0
X72.145Y5.545
X72.145Y3.005
X74.68Y79.0
X74.685Y5.545
X74.685Y3.005
X77.22Y79.0
X77.225Y5.545
X77.225Y3.005
X79.76Y79.0
X79.765Y5.545
X79.765Y3.005
X82.3Y79.0
X82.3Y3.0
X84.84Y79.0
X84.84Y3.0
X87.38Y79.0
X87.38Y3.0
X89.92Y79.0
X89.92Y3.0
X92.46Y79.0
X92.46Y3.0
X95.0Y79.0
X95.0Y3.0
T6
X3.75Y77.25
X3.75Y3.75
X100.75Y77.25
X100.75Y3.75
T4
G00X22.63Y78.4
M15
G01X22.63Y79.6
M16
G05
G00X22.63Y74.6
M15
G01X22.63Y75.4
M16
G05
G00X33.87Y78.4
M15
G01X33.87Y79.6
M16
G05
G00X33.87Y74.6
M15
G01X33.87Y75.4
M16
G05
T0
M30

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,209 @@
{
"Header": {
"GenerationSoftware": {
"Vendor": "KiCad",
"Application": "Pcbnew",
"Version": "7.0.9-7.0.9~ubuntu22.04.1"
},
"CreationDate": "2023-12-15T15:20:45-05:00"
},
"GeneralSpecs": {
"ProjectId": {
"Name": "tinytapeout-demo",
"GUID": "74696e79-7461-4706-956f-75742d64656d",
"Revision": "0.9.4"
},
"Size": {
"X": 104.6,
"Y": 81.1
},
"LayerNumber": 4,
"BoardThickness": 1.5625,
"Finish": "None",
"ImpedanceControlled": true
},
"DesignRules": [
{
"Layers": "Outer",
"PadToPad": 0.18,
"PadToTrack": 0.18,
"TrackToTrack": 0.2,
"MinLineWidth": 0.16,
"TrackToRegion": 0.25,
"RegionToRegion": 0.25
},
{
"Layers": "Inner",
"PadToPad": 0.18,
"PadToTrack": 0.18,
"TrackToTrack": 0.2,
"MinLineWidth": 0.23,
"TrackToRegion": 0.25,
"RegionToRegion": 0.25
}
],
"FilesAttributes": [
{
"Path": "tinytapeout-demo-F_Cu.gbr",
"FileFunction": "Copper,L1,Top",
"FilePolarity": "Positive"
},
{
"Path": "tinytapeout-demo-In1_Cu.gbr",
"FileFunction": "Copper,L2,Inr",
"FilePolarity": "Positive"
},
{
"Path": "tinytapeout-demo-In2_Cu.gbr",
"FileFunction": "Copper,L3,Inr",
"FilePolarity": "Positive"
},
{
"Path": "tinytapeout-demo-B_Cu.gbr",
"FileFunction": "Copper,L4,Bot",
"FilePolarity": "Positive"
},
{
"Path": "tinytapeout-demo-F_Paste.gbr",
"FileFunction": "SolderPaste,Top",
"FilePolarity": "Positive"
},
{
"Path": "tinytapeout-demo-B_Paste.gbr",
"FileFunction": "SolderPaste,Bot",
"FilePolarity": "Positive"
},
{
"Path": "tinytapeout-demo-F_Silkscreen.gbr",
"FileFunction": "Legend,Top",
"FilePolarity": "Positive"
},
{
"Path": "tinytapeout-demo-B_Silkscreen.gbr",
"FileFunction": "Legend,Bot",
"FilePolarity": "Positive"
},
{
"Path": "tinytapeout-demo-F_Mask.gbr",
"FileFunction": "SolderMask,Top",
"FilePolarity": "Negative"
},
{
"Path": "tinytapeout-demo-B_Mask.gbr",
"FileFunction": "SolderMask,Bot",
"FilePolarity": "Negative"
},
{
"Path": "tinytapeout-demo-Edge_Cuts.gbr",
"FileFunction": "Profile",
"FilePolarity": "Positive"
},
{
"Path": "tinytapeout-demo-User_2.gbr",
"FileFunction": "Other,User",
"FilePolarity": "Positive"
}
],
"MaterialStackup": [
{
"Type": "Legend",
"Color": "White",
"Name": "Top Silk Screen"
},
{
"Type": "SolderPaste",
"Name": "Top Solder Paste"
},
{
"Type": "SolderMask",
"Color": "Green",
"Thickness": 0.01,
"Name": "Top Solder Mask"
},
{
"Type": "Copper",
"Thickness": 0.035,
"Name": "F.Cu"
},
{
"Type": "Dielectric",
"Color": "R109G116B75",
"Thickness": 0.0681,
"Material": "R-1551(W)",
"DielectricConstant": "4.3",
"LossTangent": "0.02",
"Name": "F.Cu/In1.Cu (1/2)",
"Notes": "Type: dielectric layer 1 - 1/2 (from F.Cu to In1.Cu)"
},
{
"Type": "Dielectric",
"Color": "R109G116B75",
"Thickness": 0.0681,
"Material": "R-1551(W)",
"DielectricConstant": "4.3",
"LossTangent": "0.02",
"Name": "F.Cu/In1.Cu (2/2)",
"Notes": "Type: dielectric layer 1 - 2/2 (from F.Cu to In1.Cu)"
},
{
"Type": "Copper",
"Thickness": 0.035,
"Name": "In1.Cu"
},
{
"Type": "Dielectric",
"Color": "R109G116B75",
"Thickness": 1.13,
"Material": "R-1566(W)",
"DielectricConstant": "4.6",
"LossTangent": "0.02",
"Name": "In1.Cu/In2.Cu",
"Notes": "Type: dielectric layer 2 (from In1.Cu to In2.Cu)"
},
{
"Type": "Copper",
"Thickness": 0.035,
"Name": "In2.Cu"
},
{
"Type": "Dielectric",
"Color": "R109G116B75",
"Thickness": 0.0681,
"Material": "R-1551(W)",
"DielectricConstant": "4.3",
"LossTangent": "0.02",
"Name": "In2.Cu/B.Cu (1/2)",
"Notes": "Type: dielectric layer 3 - 1/2 (from In2.Cu to B.Cu)"
},
{
"Type": "Dielectric",
"Color": "R109G116B75",
"Thickness": 0.0681,
"Material": "R-1551(W)",
"DielectricConstant": "4.3",
"LossTangent": "0.02",
"Name": "In2.Cu/B.Cu (2/2)",
"Notes": "Type: dielectric layer 3 - 2/2 (from In2.Cu to B.Cu)"
},
{
"Type": "Copper",
"Thickness": 0.035,
"Name": "B.Cu"
},
{
"Type": "SolderMask",
"Color": "Green",
"Thickness": 0.01,
"Name": "Bottom Solder Mask"
},
{
"Type": "SolderPaste",
"Name": "Bottom Solder Paste"
},
{
"Type": "Legend",
"Color": "White",
"Name": "Bottom Silk Screen"
}
]
}

View file

@ -0,0 +1,314 @@
/*
* Self-contained boot harness for the KiCad Gerber viewer (gerbview) WASM,
* embedded in the blog post via an <iframe>. Adapted from the proven test
* harness (tests/apps/kicad/gerbview.html) and the React port
* (web/standalone/src/wasm/boot.ts) same global `Module`, same preRun steps,
* same wx.js -> wx-dom.js -> gerbview.js injection order.
*
* Classic script (NOT a module): the non-modularized WASM glue reads a GLOBAL
* `var Module` and a GLOBAL `mainWindow`, and publishes `FS` onto the global
* scope. Top-level `var`/`const` here share that global scope.
*
* Asset layout, split because the pthread worker script MUST be same-origin
* (a Worker can't be created from a cross-origin URL). So the small glue stays
* on the site origin and only the big binaries move to Cloudflare R2:
* GLUE_BASE : wx.js, wx-dom.js, gerbview.js, mainScriptUrlOrBlob (same-origin)
* BINARY_BASE : gerbview.wasm, kicad-resources.bin (R2 in prod, local in dev)
*
* BINARY_BASE auto-switches by hostname: localhost the local mirror (./wasm),
* any other host R2_BASE below. Override either base with ?glue=/?bin=.
* To point at R2, set R2_BASE to your bucket's public URL either a custom
* domain (https://assets.pcbjam.com) or the managed https://<id>.r2.dev URL.
*/
(function () {
"use strict";
// R2 bucket public URL (no trailing slash). Custom domain for the
// pcbjam-assets bucket (the managed URL https://pub-cecc0239e6f74d99ba7d06630bd87c64.r2.dev
// also still works).
var R2_BASE = "https://assets.pcbjam.com";
var params = new URLSearchParams(location.search);
var isLocal = /^(localhost|127\.0\.0\.1|\[?::1\]?)$/.test(location.hostname);
var GLUE_BASE = (params.get("glue") || "./wasm").replace(/\/+$/, "");
var BINARY_BASE = (params.get("bin") || (isLocal ? "./wasm" : R2_BASE)).replace(
/\/+$/,
""
);
// KiCad paths baked into the WASM build (see web/standalone/src/wasm/constants.ts).
var KICAD_VERSION_DIR = "9.99";
var KICAD_CONFIG_DIR =
"/home/kicad/.config/kicad/kicad/" + KICAD_VERSION_DIR;
var RESOURCE_PATH =
"/workspace/build-wasm/sysroot/share/kicad/resources";
// The board to auto-open: all tiny_tapeout layers (bottom→top for a sensible
// draw order). The .gbrjob is preloaded too but we open the individual layer
// files (gerbview opens gerber/drill files from argv deterministically).
var BOARD_DIR = "/home/kicad/demo";
var BOARD_FILES = [
"tinytapeout-demo-Edge_Cuts.gbr",
"tinytapeout-demo-B_Cu.gbr",
"tinytapeout-demo-In2_Cu.gbr",
"tinytapeout-demo-In1_Cu.gbr",
"tinytapeout-demo-F_Cu.gbr",
"tinytapeout-demo-B_Mask.gbr",
"tinytapeout-demo-F_Mask.gbr",
"tinytapeout-demo-B_Paste.gbr",
"tinytapeout-demo-F_Paste.gbr",
"tinytapeout-demo-B_Silkscreen.gbr",
"tinytapeout-demo-F_Silkscreen.gbr",
"tinytapeout-demo-User_2.gbr",
"tinytapeout-demo-PTH.drl",
"tinytapeout-demo-NPTH.drl",
"tinytapeout-demo-job.gbrjob",
];
var OPEN_FILES = BOARD_FILES.filter(function (f) {
return /\.(gbr|drl)$/.test(f);
});
var OPEN_ARGS = OPEN_FILES.map(function (f) {
return BOARD_DIR + "/" + f;
});
// ── Status UI ──────────────────────────────────────────────────────────────
var statusText = document.getElementById("status-text");
var progressBar = document.getElementById("progress-bar");
function setStatusUI(text) {
if (statusText) statusText.textContent = text;
var m = /(\d+)\/(\d+)/.exec(text || "");
if (m && progressBar) {
progressBar.style.width =
(parseInt(m[1], 10) / parseInt(m[2], 10)) * 100 + "%";
}
}
function hideStatus() {
var s = document.getElementById("status");
if (s) s.style.display = "none";
}
function showError(msg) {
console.error("[KICAD_ERROR] " + msg);
if (statusText) {
statusText.textContent = "Error: " + msg;
statusText.style.color = "#ff6b6b";
}
}
// The WASM reads the top frame geometry from a GLOBAL `mainWindow` element.
var mainWindow = document.getElementById("main-window");
window.mainWindow = mainWindow;
// ── Prefetch the heavy, non-wasm assets in parallel with the wasm download ──
// images.tar.gz (compiled-in KiCad resources) and the board layers. Both are
// ~10x smaller than gerbview.wasm, so they land before preRun runs; a run
// dependency guards the rare case where the wasm wins the race.
var resourceData = null;
// Served without a .gz extension on purpose (see sync-demo-wasm.sh) so no
// server adds Content-Encoding: gzip. Written into MEMFS as images.tar.gz.
fetch(BINARY_BASE + "/kicad-resources.bin")
.then(function (r) {
if (!r.ok) throw new Error("HTTP " + r.status);
return r.arrayBuffer();
})
.then(function (buf) {
resourceData = new Uint8Array(buf);
console.log("[KICAD] prefetched images.tar.gz (" + resourceData.length + " bytes)");
})
.catch(function (err) {
console.warn("[KICAD] images.tar.gz prefetch failed:", err.message);
});
var boardData = null; // { name: Uint8Array }
var boardPromise = Promise.all(
BOARD_FILES.map(function (name) {
return fetch("./board/" + name)
.then(function (r) {
if (!r.ok) throw new Error("HTTP " + r.status + " for " + name);
return r.arrayBuffer();
})
.then(function (buf) {
return [name, new Uint8Array(buf)];
});
})
)
.then(function (pairs) {
boardData = {};
pairs.forEach(function (p) {
boardData[p[0]] = p[1];
});
console.log("[KICAD] prefetched " + pairs.length + " board files");
})
.catch(function (err) {
console.error("[KICAD] board prefetch failed:", err.message);
});
// ── preRun steps ────────────────────────────────────────────────────────────
function createCanvas() {
var canvas = document.createElement("canvas");
canvas.id = "canvas";
canvas.style.display = "none";
canvas.style.width = window.innerWidth + "px";
canvas.style.height = window.innerHeight + "px";
canvas.oncontextmenu = function (e) {
e.preventDefault();
};
canvas.addEventListener(
"webglcontextlost",
function (e) {
showError("WebGL context lost — reload to retry.");
e.preventDefault();
},
false
);
mainWindow.appendChild(canvas);
Module.canvas = canvas;
console.log("[KICAD] canvas " + window.innerWidth + "x" + window.innerHeight);
}
function writeResources() {
FS.mkdirTree(RESOURCE_PATH);
if (resourceData) {
FS.writeFile(RESOURCE_PATH + "/images.tar.gz", resourceData);
console.log("[KICAD] wrote images.tar.gz");
} else {
console.warn("[KICAD] images.tar.gz not ready at preRun");
}
}
// Suppress the first-run setup wizard (its modal loop crashes Asyncify on our
// ephemeral MEMFS): make every settings provider report NeedsUserInput()==false.
function seedKicadConfig() {
FS.mkdirTree(KICAD_CONFIG_DIR);
var seed = function (path, contents) {
if (FS.analyzePath(path).exists) return;
FS.writeFile(path, contents);
};
seed(
KICAD_CONFIG_DIR + "/kicad_common.json",
JSON.stringify(
{
do_not_show_again: {
update_check_prompt: true,
data_collection_prompt: true,
},
},
null,
2
)
);
seed(KICAD_CONFIG_DIR + "/sym-lib-table", "(sym_lib_table\n (version 7)\n)\n");
seed(KICAD_CONFIG_DIR + "/fp-lib-table", "(fp_lib_table\n (version 7)\n)\n");
seed(
KICAD_CONFIG_DIR + "/design-block-lib-table",
"(design_block_lib_table\n (version 7)\n)\n"
);
console.log("[KICAD] seeded config (wizard suppressed)");
}
// Write the board into MEMFS and point argv at it so gerbview auto-opens it.
// argv must be set before main(); a run dependency keeps main() waiting if the
// board fetch hasn't landed yet.
function preloadBoard() {
Module.arguments = OPEN_ARGS;
var writeBoard = function () {
if (!boardData) return;
FS.mkdirTree(BOARD_DIR);
Object.keys(boardData).forEach(function (name) {
FS.writeFile(BOARD_DIR + "/" + name, boardData[name]);
});
console.log("[KICAD] wrote board into " + BOARD_DIR + "; argv=" + OPEN_ARGS.length + " files");
};
if (boardData) {
writeBoard();
return;
}
var add = window.addRunDependency;
var rm = window.removeRunDependency;
if (typeof add === "function" && typeof rm === "function") {
add("board-files");
boardPromise.then(function () {
writeBoard();
rm("board-files");
});
} else {
// Best-effort fallback (board << wasm, so this path is unlikely to lose).
boardPromise.then(writeBoard);
}
}
// ── Module config (global) ───────────────────────────────────────────────────
var Module = {
thisProgram: "/usr/bin/gerbview", // argv[0] for KiCad's DEBUG check
arguments: OPEN_ARGS,
preRun: [createCanvas, writeResources, seedKicadConfig, preloadBoard],
postRun: [],
print: function () {
console.log("[KICAD_OUT] " + Array.prototype.join.call(arguments, " "));
},
printErr: function () {
console.error("[KICAD_ERR] " + Array.prototype.join.call(arguments, " "));
},
setStatus: function (text) {
if (text) console.log("[KICAD_STATUS] " + text);
setStatusUI(text);
},
totalDependencies: 0,
monitorRunDependencies: function (left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(
left
? "Preparing… (" + (this.totalDependencies - left) + "/" + this.totalDependencies + ")"
: "All downloads complete."
);
},
onRuntimeInitialized: function () {
console.log("[KICAD] runtime initialized");
if (Module.canvas) Module.canvas.style.display = "block";
hideStatus();
},
onAbort: function (what) {
showError("aborted: " + (what === undefined ? "" : String(what)));
},
// Route the .wasm to BINARY_BASE; everything else (the pthread worker) to GLUE_BASE.
locateFile: function (path) {
return (/\.wasm$/.test(path) ? BINARY_BASE : GLUE_BASE) + "/" + path;
},
// Pin the pthread worker to the same-origin glue (workers cannot be cross-origin).
mainScriptUrlOrBlob: GLUE_BASE + "/gerbview.js",
};
window.Module = Module;
Module.setStatus("Downloading…");
window.onerror = function (msg, url, line) {
showError(msg + " @ " + url + ":" + line);
return false;
};
// ── Inject glue scripts in the required order ────────────────────────────────
function loadScript(src) {
return new Promise(function (resolve, reject) {
var s = document.createElement("script");
s.src = src;
s.onload = resolve;
s.onerror = function () {
reject(new Error("failed to load " + src));
};
document.body.appendChild(s);
});
}
loadScript(GLUE_BASE + "/wx.js")
.then(function () {
return loadScript(GLUE_BASE + "/wx-dom.js");
})
.then(function () {
return loadScript(GLUE_BASE + "/gerbview.js");
})
.then(function () {
console.log("[KICAD] injected wx.js + wx-dom.js + gerbview.js (glue=" + GLUE_BASE + ", bin=" + BINARY_BASE + ")");
})
.catch(function (err) {
showError(err.message);
});
})();

View file

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>PCBJam — Gerber viewer demo</title>
<style>
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #0b1020;
}
/* The canvas must have no border/padding or mouse coords go wrong. */
.emscripten {
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
}
div.emscripten {
text-align: center;
}
canvas.emscripten {
border: 0px none;
}
/* wxWidgets-DOM per-window overlays (created by wx.js, appended to
#window-container). Mirror the proven harness exactly. */
.window {
position: absolute;
pointer-events: none;
z-index: 10;
background-color: black;
overflow: hidden;
width: 0;
height: 0;
}
.window-canvas {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
#status {
position: fixed;
left: 12px;
bottom: 12px;
z-index: 1000;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 12px;
color: #e7ecf5;
background: rgba(11, 16, 32, 0.85);
border: 1px solid #233052;
padding: 8px 10px;
border-radius: 8px;
}
#progress {
width: 240px;
height: 6px;
margin-top: 6px;
background: #233052;
border-radius: 3px;
overflow: hidden;
}
#progress-bar {
height: 100%;
width: 0%;
background: #4f7cff;
transition: width 0.25s;
}
</style>
</head>
<body>
<div
id="main-window"
style="width: 100vw; height: 100vh; position: absolute; top: 0; left: 0"
></div>
<div id="window-container"></div>
<div id="status">
<div id="status-text">Initializing…</div>
<div id="progress"><div id="progress-bar"></div></div>
</div>
<!-- Classic script (not a module): the WASM glue needs global Module / mainWindow / FS. -->
<script src="./boot.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

18
site/scripts/r2-cors.json Normal file
View file

@ -0,0 +1,18 @@
{
"rules": [
{
"allowed": {
"origins": [
"https://pcbjam.com",
"https://www.pcbjam.com",
"https://*.vercel.app",
"http://localhost:4321"
],
"methods": ["GET", "HEAD"],
"headers": ["*"]
},
"exposeHeaders": ["Content-Length", "Content-Encoding", "Content-Type"],
"maxAgeSeconds": 86400
}
]
}

59
site/scripts/r2-deploy.sh Executable file
View file

@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Deploy the heavy Gerber-viewer binaries to Cloudflare R2.
#
# Prereqs (one-time, done by you — see the chat guide):
# 1. R2 enabled on your Cloudflare account (dashboard; needs a payment method).
# 2. `wrangler login` (browser auth).
#
# This script then (idempotent — safe to re-run after a rebuild):
# - creates the bucket (skips if it already exists)
# - sets CORS (site/scripts/r2-cors.json)
# - enables the public r2.dev URL and prints it
# - gzips gerbview.wasm and uploads it (Content-Encoding: gzip) + kicad-resources.bin
#
# Usage: site/scripts/r2-deploy.sh [bucket-name] (default: pcbjam-assets)
set -euo pipefail
BUCKET="${1:-pcbjam-assets}"
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # = site/
SRC="$ROOT/public/gerber-demo/wasm"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
command -v wrangler >/dev/null || { echo "wrangler not found (npm i -g wrangler)"; exit 1; }
wrangler whoami >/dev/null 2>&1 || { echo "Not logged in. Run: wrangler login"; exit 1; }
[[ -f "$SRC/gerbview.wasm" ]] || { echo "Missing $SRC/gerbview.wasm — run scripts/sync-demo-wasm.sh"; exit 1; }
[[ -f "$SRC/kicad-resources.bin" ]] || { echo "Missing $SRC/kicad-resources.bin — run scripts/sync-demo-wasm.sh"; exit 1; }
echo "==> Bucket: $BUCKET"
wrangler r2 bucket create "$BUCKET" 2>/dev/null \
&& echo " created" \
|| echo " already exists (continuing)"
echo "==> CORS"
wrangler r2 bucket cors set "$BUCKET" --file "$ROOT/scripts/r2-cors.json"
echo "==> Public r2.dev URL (makes the bucket's objects publicly readable)"
wrangler r2 bucket dev-url enable "$BUCKET" || true
wrangler r2 bucket dev-url get "$BUCKET" || true
echo "==> gzip gerbview.wasm"
gzip -9 -c "$SRC/gerbview.wasm" > "$TMP/gerbview.wasm.gz"
echo " $(du -h "$SRC/gerbview.wasm" | cut -f1) -> $(du -h "$TMP/gerbview.wasm.gz" | cut -f1) gzipped"
echo "==> Upload gerbview.wasm (gzip, application/wasm)"
wrangler r2 object put "$BUCKET/gerbview.wasm" --remote \
--file "$TMP/gerbview.wasm.gz" \
--content-type "application/wasm" \
--content-encoding "gzip" \
--cache-control "public, max-age=3600"
echo "==> Upload kicad-resources.bin (octet-stream, no encoding)"
wrangler r2 object put "$BUCKET/kicad-resources.bin" --remote \
--file "$SRC/kicad-resources.bin" \
--content-type "application/octet-stream" \
--cache-control "public, max-age=3600"
echo ""
echo "Done. Copy the r2.dev URL printed above (https://<hash>.r2.dev) into"
echo "site/public/gerber-demo/boot.js -> R2_BASE, then redeploy the site."
echo "(Or bind a custom domain: wrangler r2 bucket domain add $BUCKET --domain assets.pcbjam.com)"

36
site/scripts/sync-demo-wasm.sh Executable file
View file

@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Mirror the gerbview WASM build outputs into the blog demo's local asset dir.
#
# Phase A (local verification) serves the viewer from these files. They are
# git-ignored (see site/.gitignore): the 52 MB gerbview.wasm must never be
# committed or shipped to Vercel — in production the heavy binaries live on
# Cloudflare R2 (see the blog-demo plan, Phase B). Re-run this after rebuilding
# gerbview to refresh the local copy.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SRC="$ROOT/output"
DST="$ROOT/site/public/gerber-demo/wasm"
mkdir -p "$DST"
for f in wx.js wx-dom.js gerbview.js gerbview.wasm images.tar.gz; do
if [[ ! -f "$SRC/$f" ]]; then
echo "ERROR: missing $SRC/$f — build gerbview first (docker/build.sh)." >&2
exit 1
fi
done
cp "$SRC/wx.js" "$DST/wx.js"
cp "$SRC/wx-dom.js" "$DST/wx-dom.js"
cp "$SRC/gerbview.js" "$DST/gerbview.js"
cp "$SRC/gerbview.wasm" "$DST/gerbview.wasm"
# Store the resources tarball WITHOUT a .gz extension: dev servers (Vite/sirv)
# and some CDNs auto-add `Content-Encoding: gzip` for `*.gz`, which makes the
# browser pre-decompress it — then KiCad's own gunzip fails. boot.js fetches
# this name and writes it into MEMFS as images.tar.gz.
cp "$SRC/images.tar.gz" "$DST/kicad-resources.bin"
for f in wx.js wx-dom.js gerbview.js gerbview.wasm kicad-resources.bin; do
echo "synced $f ($(du -h "$DST/$f" | cut -f1))"
done
echo "Local demo WASM ready at $DST"

View file

@ -0,0 +1,291 @@
---
/**
* Interactive, lazily-loaded KiCad Gerber viewer embedded in a blog post.
*
* Nothing heavy loads on page view: the ~22 MB (gzipped) WASM is fetched only
* when the reader activates the viewer, which mounts an <iframe> pointing at the
* self-contained boot page (public/gerber-demo/index.html).
*
* The live viewer needs cross-origin isolation (SharedArrayBuffer + pthreads)
* and WebGL2. We feature-detect and degrade gracefully: capable browsers get the
* live viewer; everything else gets a static poster + a short note. COOP/COEP are
* applied to this route (dev: astro.config.mjs; prod: vercel.json); because
* Astro's <ClientRouter/> can deliver this page via a soft SPA swap (which is
* NOT cross-origin isolated), the client script does one guarded reload so the
* route headers take effect.
*/
const poster = "/gerber-demo/poster.png"; // captured screenshot; optional fallback art
const caption =
"KiCad's Gerber viewer (gerbview), compiled to WebAssembly, running the tiny_tapeout demo board entirely in your browser.";
---
<figure class="gerber-demo not-prose" data-state="idle">
<div class="gerber-demo__frame">
<!-- Poster / launch surface. The href is a real fallback: with JS off (or if
we choose to), it opens the standalone demo page. JS intercepts it to
embed inline. -->
<a
class="gerber-demo__launch"
href="/gerber-demo/index.html"
style={`background-image:url(${poster})`}
>
<span class="gerber-demo__play" aria-hidden="true">▶</span>
<span class="gerber-demo__cta">Launch Gerber viewer</span>
<span class="gerber-demo__sub">~22 MB · loads on demand · runs locally in your browser</span>
</a>
<!-- Shown only when the browser can't run the live viewer. -->
<div class="gerber-demo__fallback" hidden>
<p class="gerber-demo__fallback-title">Live demo unavailable in this browser</p>
<p class="gerber-demo__sub">
The in-browser viewer needs a recent desktop <strong>Chrome</strong>,
<strong>Firefox</strong>, or <strong>Safari 16+</strong> (it relies on
SharedArrayBuffer + WebGL2). Here's a screenshot of what it renders.
</p>
</div>
<!-- Status line while the WASM downloads/boots. -->
<div class="gerber-demo__status" hidden>
<span class="gerber-demo__spinner" aria-hidden="true"></span>
<span class="gerber-demo__status-text">Loading the viewer…</span>
</div>
</div>
<figcaption class="gerber-demo__caption">{caption}</figcaption>
</figure>
<script>
const BOOT_SRC = "/gerber-demo/index.html";
const RELOAD_KEY = "gerber-demo-coi-reload";
function hasWebGL2(): boolean {
try {
return !!document.createElement("canvas").getContext("webgl2");
} catch {
return false;
}
}
function launch(root: HTMLElement): void {
if (root.dataset.state === "loading" || root.dataset.state === "ready") return;
root.dataset.state = "loading";
const status = root.querySelector<HTMLElement>(".gerber-demo__status");
if (status) status.hidden = false;
const iframe = document.createElement("iframe");
iframe.src = BOOT_SRC;
iframe.title = "KiCad Gerber viewer";
// Delegate cross-origin isolation to the (same-origin) iframe so it can use
// SharedArrayBuffer / pthreads.
iframe.allow = "cross-origin-isolated; fullscreen";
iframe.setAttribute("allowfullscreen", "");
iframe.className = "gerber-demo__iframe";
// Size via INLINE styles, not the scoped stylesheet: this element is created
// in JS so it lacks Astro's data-astro-cid scope attribute, and the scoped
// `.gerber-demo__iframe` rule would not match it (it would fall back to the
// 300x150 iframe default, shrinking the whole viewer).
iframe.style.cssText =
"position:absolute;inset:0;width:100%;height:100%;border:0;display:block;";
iframe.addEventListener("load", () => {
root.dataset.state = "ready";
if (status) status.hidden = true;
});
const frame = root.querySelector(".gerber-demo__frame");
frame?.appendChild(iframe);
}
function showFallback(root: HTMLElement): void {
root.dataset.state = "unsupported";
const launchEl = root.querySelector<HTMLElement>(".gerber-demo__launch");
const fallback = root.querySelector<HTMLElement>(".gerber-demo__fallback");
// Keep the poster (it's the <a> background); hide the play affordance, show note.
launchEl?.querySelector(".gerber-demo__play")?.setAttribute("hidden", "");
launchEl?.querySelector(".gerber-demo__cta")?.setAttribute("hidden", "");
launchEl?.querySelector(".gerber-demo__sub")?.setAttribute("hidden", "");
if (launchEl) {
launchEl.removeAttribute("href"); // not clickable
launchEl.style.cursor = "default";
}
if (fallback) fallback.hidden = false;
}
function initDemo(): void {
const root = document.querySelector<HTMLElement>(".gerber-demo");
if (!root) return; // page without the demo
const isolated = window.crossOriginIsolated === true;
// Soft SPA navigation lands here NOT cross-origin isolated; force one real
// load so the route's COOP/COEP headers apply. Guard against reload loops.
if (!isolated) {
if (!sessionStorage.getItem(RELOAD_KEY)) {
sessionStorage.setItem(RELOAD_KEY, "1");
location.reload();
return;
}
// Already retried and still not isolated → genuinely unsupported (or no
// headers, e.g. a preview without vercel.json). Fall back.
} else {
sessionStorage.removeItem(RELOAD_KEY);
}
const supported =
isolated && typeof SharedArrayBuffer !== "undefined" && hasWebGL2();
if (!supported) {
showFallback(root);
return;
}
const launchEl = root.querySelector<HTMLElement>(".gerber-demo__launch");
launchEl?.addEventListener("click", (e) => {
e.preventDefault(); // intercept the href; embed inline instead
launch(root);
});
}
// Run on first load and on every SPA navigation (ClientRouter).
document.addEventListener("astro:page-load", initDemo);
</script>
<style>
.gerber-demo {
margin: 2rem 0;
}
.gerber-demo__frame {
position: relative;
width: 100%;
aspect-ratio: 16 / 10;
min-height: 360px;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
background: var(--bg-soft);
}
/* Launch surface (poster + CTA). */
.gerber-demo__launch {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.5rem;
text-decoration: none;
color: var(--fg);
background-size: cover;
background-position: center;
background-color: var(--bg-soft);
transition: filter var(--dur) var(--ease);
}
.gerber-demo__launch::before {
/* Dim the poster so the CTA reads clearly. */
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
180deg,
rgba(11, 16, 32, 0.45),
rgba(11, 16, 32, 0.7)
);
}
.gerber-demo__launch:hover {
filter: brightness(1.06);
text-decoration: none;
}
.gerber-demo__play {
position: relative;
display: grid;
place-items: center;
width: 64px;
height: 64px;
border-radius: 999px;
background: var(--brand-600);
color: #fff;
font-size: 1.5rem;
padding-left: 4px;
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
}
.gerber-demo__cta {
position: relative;
font-weight: 600;
font-size: 1.05rem;
}
.gerber-demo__sub {
position: relative;
font-family: var(--font-mono);
font-size: 0.78rem;
color: var(--fg-muted);
text-align: center;
max-width: 32rem;
padding: 0 1rem;
}
.gerber-demo__iframe {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: 0;
display: block;
}
.gerber-demo__status {
position: absolute;
left: 50%;
bottom: 1rem;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 0.5rem;
font-family: var(--font-mono);
font-size: 0.8rem;
color: var(--fg);
background: rgba(11, 16, 32, 0.85);
border: 1px solid var(--border);
padding: 0.4rem 0.7rem;
border-radius: var(--radius);
}
.gerber-demo__spinner {
width: 12px;
height: 12px;
border: 2px solid var(--border);
border-top-color: var(--accent);
border-radius: 999px;
animation: gerber-spin 0.8s linear infinite;
}
@keyframes gerber-spin {
to {
transform: rotate(360deg);
}
}
.gerber-demo__fallback {
position: absolute;
inset: auto 0 0 0;
padding: 1rem 1.25rem;
background: rgba(11, 16, 32, 0.9);
border-top: 1px solid var(--border);
text-align: center;
}
.gerber-demo__fallback-title {
margin: 0 0 0.25rem;
font-weight: 600;
}
.gerber-demo__fallback .gerber-demo__sub {
position: static;
margin: 0 auto;
}
.gerber-demo__caption {
margin-top: 0.75rem;
font-size: 0.85rem;
color: var(--fg-muted);
line-height: 1.5;
}
@media (prefers-reduced-motion: reduce) {
.gerber-demo__spinner {
animation: none;
}
.gerber-demo__launch {
transition: none;
}
}
</style>

View file

@ -4,7 +4,7 @@ import { glob } from 'astro/loaders';
// Content Layer API (Astro 5+). Markdown files live in src/content/blog/.
// NOTE: this config file is src/content.config.ts — NOT src/content/config.ts.
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string(),

View file

@ -1,7 +0,0 @@
---
title: "Porting KiCad's Graphics to WebGL with Claude"
description: "Coming soon."
pubDate: 2026-06-07
---
Coming soon.

View file

@ -0,0 +1,22 @@
---
title: "Porting KiCad's Graphics to WebGL with Claude"
description: "Coming soon."
pubDate: 2026-06-07
---
import GerberDemo from '../../components/GerberDemo.astro';
{/* TODO(author): intro — the gerbview port and the graphics-abstraction-layer → WebGL work. */}
Coming soon.
## See it run: a Gerber viewer in your browser
The viewer below is KiCad's `gerbview` compiled to WebAssembly. Click to load it
(it streams ~22&nbsp;MB on demand) and it renders a real multi-layer board —
the [tiny_tapeout](https://tinytapeout.com/) demo — entirely client-side, with no
server doing the rasterization.
<GerberDemo />
{/* TODO(author): the rest of the article. */}

23
site/src/middleware.ts Normal file
View file

@ -0,0 +1,23 @@
import { defineMiddleware } from 'astro:middleware';
/**
* Dev-only cross-origin isolation for Astro-rendered pages.
*
* Vite's `server.headers` (astro.config.mjs) already cover static assets in
* public/ (the boot page + WASM), but Astro's dev page renderer writes its own
* response headers and drops them so the blog post itself wouldn't be
* cross-origin isolated, and the embedded Gerber viewer's SharedArrayBuffer
* would be unavailable. Set them here for `npm run dev`.
*
* Production is static (output: 'static'); these headers come from vercel.json
* scoped to the post + /gerber-demo routes, so we no-op outside dev to keep the
* prerender/build output untouched.
*/
export const onRequest = defineMiddleware(async (_context, next) => {
const response = await next();
if (import.meta.env.DEV) {
response.headers.set('Cross-Origin-Opener-Policy', 'same-origin');
response.headers.set('Cross-Origin-Embedder-Policy', 'require-corp');
}
return response;
});

19
site/vercel.json Normal file
View file

@ -0,0 +1,19 @@
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"headers": [
{
"source": "/blog/porting-kicad-graphics-to-webgl-with-claude",
"headers": [
{ "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
{ "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" }
]
},
{
"source": "/gerber-demo/(.*)",
"headers": [
{ "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
{ "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" }
]
}
]
}