diff --git a/frontend/package.json b/frontend/package.json index 849420d..a013e26 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,7 +8,7 @@ "generate:metadata": "cd .. && npx tsx scripts/generate-component-metadata.ts", "generate:favicons": "node ../scripts/generate-favicons.mjs", "generate:og-image": "node ../scripts/generate-og-image.mjs", - "generate:sitemap": "cd .. && npx tsx scripts/generate-sitemap.ts", + "generate:sitemap": "npx tsx scripts/generate-sitemap.ts", "dev": "npm run generate:metadata && vite", "build": "npm run generate:metadata && npm run generate:sitemap && tsc -b && vite build", "build:docker": "npm run generate:sitemap && vite build", diff --git a/frontend/scripts/generate-sitemap.ts b/frontend/scripts/generate-sitemap.ts new file mode 100644 index 0000000..4c2620e --- /dev/null +++ b/frontend/scripts/generate-sitemap.ts @@ -0,0 +1,62 @@ +/** + * Auto-generates public/sitemap.xml from seoRoutes.ts + * Run: npx tsx scripts/generate-sitemap.ts + * Integrated into: npm run build (via generate:sitemap) + */ + +import { writeFileSync } from 'fs'; +import { resolve, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +// Import the single source of truth +import { SEO_ROUTES } from '../src/seoRoutes'; + +const __dirname_resolved = dirname(fileURLToPath(import.meta.url)); + +const DOMAIN = 'https://velxio.dev'; +const TODAY = new Date().toISOString().slice(0, 10); // YYYY-MM-DD + +const indexableRoutes = SEO_ROUTES.filter((r) => !r.noindex); + +const xml = ` + +${indexableRoutes + .map( + (r) => ` + + ${DOMAIN}${r.path} + ${TODAY} + ${r.changefreq ?? 'monthly'} + ${r.priority ?? 0.5} + ` + ) + .join('')} + + +`; + +const outPath = resolve(__dirname_resolved, '../public/sitemap.xml'); +writeFileSync(outPath, xml.trimStart(), 'utf-8'); +console.log(`sitemap.xml generated → ${indexableRoutes.length} URLs (${TODAY})`); + +// Also ping Google & Bing sitemap endpoints (non-blocking, best-effort) +const SITEMAP_URL = `${DOMAIN}/sitemap.xml`; +const PING_URLS = [ + `https://www.google.com/ping?sitemap=${encodeURIComponent(SITEMAP_URL)}`, + `https://www.bing.com/ping?sitemap=${encodeURIComponent(SITEMAP_URL)}`, +]; + +if (process.argv.includes('--ping')) { + console.log('Pinging search engines...'); + Promise.allSettled( + PING_URLS.map(async (url) => { + try { + const res = await fetch(url); + console.log(` ${res.ok ? 'OK' : 'FAIL'} ${url.split('?')[0]}`); + } catch (e: any) { + console.log(` FAIL ${url.split('?')[0]}: ${e.message}`); + } + }) + ); +} diff --git a/frontend/src/pages/Velxio2Page.css b/frontend/src/pages/Velxio2Page.css index 6c8c80b..d1c227c 100644 --- a/frontend/src/pages/Velxio2Page.css +++ b/frontend/src/pages/Velxio2Page.css @@ -175,6 +175,56 @@ white-space: nowrap; } +/* ── Multi-board visual ──────────────────────────────── */ +.v2-multiboard-visual { + display: flex; + align-items: center; + justify-content: center; + gap: 0; + flex-wrap: wrap; + padding: 32px 16px; + background: #121214; + border: 1px solid #1c1c1e; + border-radius: 12px; +} + +.v2-mb-node { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 16px 20px; + border: 2px solid; + border-radius: 10px; + background: #0d0d0f; + transition: transform 0.15s, border-color 0.2s; +} + +.v2-mb-node:hover { + transform: translateY(-3px); +} + +.v2-mb-node img { + height: 56px; + width: auto; + object-fit: contain; +} + +.v2-mb-node span { + font-size: 0.75rem; + font-weight: 600; + color: #e6edf3; + white-space: nowrap; +} + +.v2-mb-wire { + flex-shrink: 0; +} + +.v2-mb-wire svg { + display: block; +} + /* ── Changelog ───────────────────────────────────────── */ .v2-changelog { display: grid; @@ -322,4 +372,16 @@ .v2-board-card img { height: 48px; } + + .v2-mb-wire { + display: none; + } + + .v2-multiboard-visual { + gap: 10px; + } + + .v2-mb-node img { + height: 40px; + } } diff --git a/frontend/src/pages/Velxio2Page.tsx b/frontend/src/pages/Velxio2Page.tsx index 3cf416b..292d568 100644 --- a/frontend/src/pages/Velxio2Page.tsx +++ b/frontend/src/pages/Velxio2Page.tsx @@ -100,6 +100,16 @@ const IcoStar = () => ( ); +const IcoMultiBoard = () => ( + + + + + + + +); + const IcoRefresh = () => ( @@ -396,6 +406,55 @@ export const Velxio2Page: React.FC = () => { + {/* ── Multi-board ── */} +
+

Multiple boards in one circuit

+

+ Most simulators limit you to one board at a time. Velxio lets you place multiple boards + on the same canvas and wire them together — just like a real workbench. +

+
+
+
+ ESP32 + ESP32 +
+
+ + + +
+
+ Raspberry Pi Pico + Pi Pico +
+
+ + + +
+
+ Arduino Uno + Arduino +
+
+
+
+

Mix architectures

+

Connect an ESP32 to a Raspberry Pi 3, wire three Arduinos together, or combine a Pico with an ESP32-C3. Any combination works.

+
+
+

Real inter-board communication

+

Boards communicate through wired connections — UART, I2C, SPI, or simple GPIO signals, just like real hardware.

+
+
+

No other simulator does this

+

Traditional simulators emulate one board in isolation. Velxio simulates entire systems with multiple boards running simultaneously.

+
+
+
+ + {/* ── Changelog ── */}

What's new in 2.0