velxio/frontend/src/pages/DocsPage.tsx

2894 lines
91 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react';
import { Link, useParams, useNavigate } from 'react-router-dom';
import { useTranslation, Trans } from 'react-i18next';
import { AppHeader } from '../components/layout/AppHeader';
import { CodeBlock } from '../components/layout/CodeBlock';
import { useLocalizedHref } from '../i18n/useLocalizedNavigate';
import './DocsPage.css';
const GITHUB_URL = 'https://github.com/davidmonterocrespo24/velxio';
const BASE_URL = 'https://velxio.dev';
const AUTHOR = {
'@type': 'Person',
name: 'David Montero Crespo',
url: 'https://github.com/davidmonterocrespo24',
} as const;
/* ── Icons ─────────────────────────────────────────────── */
const IcoGitHub = () => (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.385-1.335-1.755-1.335-1.755-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 21.795 24 17.295 24 12c0-6.63-5.37-12-12-12z" />
</svg>
);
/* ── Doc sections ──────────────────────────────────────── */
type SectionId =
| 'intro'
| 'getting-started'
| 'emulator'
| 'riscv-emulation'
| 'esp32-emulation'
| 'rp2040-emulation'
| 'raspberry-pi3-emulation'
| 'components'
| 'roadmap'
| 'architecture'
| 'third-party'
| 'mcp'
| 'setup'
| 'build-qemu';
const VALID_SECTIONS: SectionId[] = [
'intro',
'getting-started',
'emulator',
'riscv-emulation',
'esp32-emulation',
'rp2040-emulation',
'raspberry-pi3-emulation',
'components',
'roadmap',
'architecture',
'third-party',
'mcp',
'setup',
'build-qemu',
];
interface NavItem {
id: SectionId;
labelKey: string;
}
const NAV_ITEMS: NavItem[] = [
{ id: 'intro', labelKey: 'docs.nav.intro' },
{ id: 'getting-started', labelKey: 'docs.nav.gettingStarted' },
{ id: 'emulator', labelKey: 'docs.nav.emulator' },
{ id: 'riscv-emulation', labelKey: 'docs.nav.riscvEmulation' },
{ id: 'esp32-emulation', labelKey: 'docs.nav.esp32Emulation' },
{ id: 'rp2040-emulation', labelKey: 'docs.nav.rp2040Emulation' },
{ id: 'raspberry-pi3-emulation', labelKey: 'docs.nav.raspberryPi3Emulation' },
{ id: 'components', labelKey: 'docs.nav.components' },
{ id: 'architecture', labelKey: 'docs.nav.architecture' },
{ id: 'third-party', labelKey: 'docs.nav.thirdParty' },
{ id: 'mcp', labelKey: 'docs.nav.mcp' },
{ id: 'setup', labelKey: 'docs.nav.setup' },
{ id: 'build-qemu', labelKey: 'docs.nav.buildQemu' },
{ id: 'roadmap', labelKey: 'docs.nav.roadmap' },
];
/* ── Per-section SEO metadata ──────────────────────────── */
interface SectionMeta {
titleKey: string;
descriptionKey: string;
}
const SECTION_META: Record<SectionId, SectionMeta> = {
intro: {
titleKey: 'docs.sectionMeta.intro.title',
descriptionKey: 'docs.sectionMeta.intro.description',
},
'getting-started': {
titleKey: 'docs.sectionMeta.gettingStarted.title',
descriptionKey: 'docs.sectionMeta.gettingStarted.description',
},
emulator: {
titleKey: 'docs.sectionMeta.emulator.title',
descriptionKey: 'docs.sectionMeta.emulator.description',
},
'riscv-emulation': {
titleKey: 'docs.sectionMeta.riscvEmulation.title',
descriptionKey: 'docs.sectionMeta.riscvEmulation.description',
},
'esp32-emulation': {
titleKey: 'docs.sectionMeta.esp32Emulation.title',
descriptionKey: 'docs.sectionMeta.esp32Emulation.description',
},
components: {
titleKey: 'docs.sectionMeta.components.title',
descriptionKey: 'docs.sectionMeta.components.description',
},
roadmap: {
titleKey: 'docs.sectionMeta.roadmap.title',
descriptionKey: 'docs.sectionMeta.roadmap.description',
},
architecture: {
titleKey: 'docs.sectionMeta.architecture.title',
descriptionKey: 'docs.sectionMeta.architecture.description',
},
'third-party': {
titleKey: 'docs.sectionMeta.thirdParty.title',
descriptionKey: 'docs.sectionMeta.thirdParty.description',
},
mcp: {
titleKey: 'docs.sectionMeta.mcp.title',
descriptionKey: 'docs.sectionMeta.mcp.description',
},
setup: {
titleKey: 'docs.sectionMeta.setup.title',
descriptionKey: 'docs.sectionMeta.setup.description',
},
'rp2040-emulation': {
titleKey: 'docs.sectionMeta.rp2040Emulation.title',
descriptionKey: 'docs.sectionMeta.rp2040Emulation.description',
},
'raspberry-pi3-emulation': {
titleKey: 'docs.sectionMeta.raspberryPi3Emulation.title',
descriptionKey: 'docs.sectionMeta.raspberryPi3Emulation.description',
},
'build-qemu': {
titleKey: 'docs.sectionMeta.buildQemu.title',
descriptionKey: 'docs.sectionMeta.buildQemu.description',
},
};
/* ── Section content ───────────────────────────────────── */
const IntroSection: React.FC = () => {
const { t } = useTranslation();
return (
<div className="docs-section">
<span className="docs-label">{t('docs.intro.label')}</span>
<h1>{t('docs.intro.heading')}</h1>
<p>
<Trans i18nKey="docs.intro.lead" components={{ strong: <strong />, code: <code /> }} />
</p>
<h2>{t('docs.intro.whyHeading')}</h2>
<ul>
<li>
<Trans i18nKey="docs.intro.whyNoInstall" components={{ strong: <strong /> }} />
</li>
<li>
<Trans i18nKey="docs.intro.whyRealEmulation" components={{ strong: <strong /> }} />
</li>
<li>
<Trans i18nKey="docs.intro.whyInteractive" components={{ strong: <strong /> }} />
</li>
<li>
<Trans i18nKey="docs.intro.whyOpenSource" components={{ strong: <strong /> }} />
</li>
</ul>
<h2>{t('docs.intro.boardsHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.intro.thBoard')}</th>
<th>{t('docs.intro.thCpu')}</th>
<th>{t('docs.intro.thEmulator')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.intro.boardArduinoUno')}</td>
<td>{t('docs.intro.cpuAtmega328p')}</td>
<td>{t('docs.intro.emuAvr8js')}</td>
</tr>
<tr>
<td>{t('docs.intro.boardArduinoNano')}</td>
<td>{t('docs.intro.cpuAtmega328p')}</td>
<td>{t('docs.intro.emuAvr8js')}</td>
</tr>
<tr>
<td>{t('docs.intro.boardArduinoMega')}</td>
<td>{t('docs.intro.cpuAtmega2560')}</td>
<td>{t('docs.intro.emuAvr8js')}</td>
</tr>
<tr>
<td>{t('docs.intro.boardPiPico')}</td>
<td>{t('docs.intro.cpuRp2040')}</td>
<td>{t('docs.intro.emuRp2040js')}</td>
</tr>
<tr>
<td>{t('docs.intro.boardEsp32C3')}</td>
<td>{t('docs.intro.cpuRv32imc')}</td>
<td>{t('docs.intro.emuEsp32C3')}</td>
</tr>
<tr>
<td>{t('docs.intro.boardEsp32')}</td>
<td>{t('docs.intro.cpuXtensa')}</td>
<td>{t('docs.intro.emuQemu')}</td>
</tr>
</tbody>
</table>
<div className="docs-callout">
<Trans
i18nKey="docs.intro.liveDemoCallout"
components={{
strong: <strong />,
a: <a href="https://velxio.dev" target="_blank" rel="noopener noreferrer" />,
}}
/>
</div>
</div>
);
};
const GettingStartedSection: React.FC = () => {
const { t } = useTranslation();
return (
<div className="docs-section">
<span className="docs-label">{t('docs.gettingStarted.label')}</span>
<h1>{t('docs.gettingStarted.heading')}</h1>
<p>{t('docs.gettingStarted.lead')}</p>
<h2>{t('docs.gettingStarted.option1Heading')}</h2>
<p>
<Trans
i18nKey="docs.gettingStarted.option1Body"
components={{
a: <a href="https://velxio.dev" target="_blank" rel="noopener noreferrer" />,
}}
/>
</p>
<h2>{t('docs.gettingStarted.option2Heading')}</h2>
<p>{t('docs.gettingStarted.option2Body')}</p>
<CodeBlock language="bash">{`docker run -d \\
--name velxio \\
-p 3080:80 \\
-v $(pwd)/data:/app/data \\
ghcr.io/davidmonterocrespo24/velxio:master`}</CodeBlock>
<p>
<Trans i18nKey="docs.gettingStarted.option2After" components={{ strong: <strong /> }} />
</p>
<h2>{t('docs.gettingStarted.option3Heading')}</h2>
<p>
<Trans
i18nKey="docs.gettingStarted.option3Prereq"
components={{ strong: <strong />, code: <code /> }}
/>
</p>
<h3>{t('docs.gettingStarted.option3Step1')}</h3>
<CodeBlock language="bash">{`git clone https://github.com/davidmonterocrespo24/velxio.git
cd velxio`}</CodeBlock>
<h3>{t('docs.gettingStarted.option3Step2')}</h3>
<CodeBlock language="bash">{`cd backend
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8001`}</CodeBlock>
<h3>{t('docs.gettingStarted.option3Step3')}</h3>
<CodeBlock language="bash">{`cd frontend
npm install
npm run dev`}</CodeBlock>
<p>
<Trans i18nKey="docs.gettingStarted.option3Step3After" components={{ strong: <strong /> }} />
</p>
<h3>{t('docs.gettingStarted.option3Step4')}</h3>
<CodeBlock language="bash">{`arduino-cli core update-index
arduino-cli core install arduino:avr
# For Raspberry Pi Pico support:
arduino-cli config add board_manager.additional_urls \\
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
arduino-cli core install rp2040:rp2040`}</CodeBlock>
<h2>{t('docs.gettingStarted.firstSimulationHeading')}</h2>
<ol>
<li>
<Trans
i18nKey="docs.gettingStarted.firstSim1"
components={{
strong: <strong />,
a: <a href="https://velxio.dev/editor" target="_blank" rel="noopener noreferrer" />,
}}
/>
</li>
<li>
<Trans
i18nKey="docs.gettingStarted.firstSim2"
components={{ strong: <strong />, em: <em /> }}
/>
</li>
<li>
<Trans i18nKey="docs.gettingStarted.firstSim3" components={{ strong: <strong /> }} />
</li>
</ol>
<CodeBlock language="cpp">{`void setup() {
pinMode(13, OUTPUT);
}
void loop() {
digitalWrite(13, HIGH);
delay(500);
digitalWrite(13, LOW);
delay(500);
}`}</CodeBlock>
<ol start={4}>
<li>
<Trans
i18nKey="docs.gettingStarted.firstSim4"
components={{ strong: <strong />, code: <code /> }}
/>
</li>
<li>
<Trans i18nKey="docs.gettingStarted.firstSim5" components={{ strong: <strong /> }} />
</li>
<li>
<Trans i18nKey="docs.gettingStarted.firstSim6" components={{ strong: <strong /> }} />
</li>
<li>
<Trans i18nKey="docs.gettingStarted.firstSim7" components={{ strong: <strong /> }} />
</li>
</ol>
<h2>{t('docs.gettingStarted.troubleshootingHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.gettingStarted.thProblem')}</th>
<th>{t('docs.gettingStarted.thSolution')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<Trans
i18nKey="docs.gettingStarted.tsArduinoCliMissingProblem"
components={{ code: <code /> }}
/>
</td>
<td>
<Trans
i18nKey="docs.gettingStarted.tsArduinoCliMissingSolution"
components={{ code: <code /> }}
/>
</td>
</tr>
<tr>
<td>{t('docs.gettingStarted.tsLedNoBlinkProblem')}</td>
<td>{t('docs.gettingStarted.tsLedNoBlinkSolution')}</td>
</tr>
<tr>
<td>{t('docs.gettingStarted.tsSerialEmptyProblem')}</td>
<td>
<Trans
i18nKey="docs.gettingStarted.tsSerialEmptySolution"
components={{ code: <code /> }}
/>
</td>
</tr>
<tr>
<td>{t('docs.gettingStarted.tsCompileErrorsProblem')}</td>
<td>
<Trans
i18nKey="docs.gettingStarted.tsCompileErrorsSolution"
components={{ code: <code /> }}
/>
</td>
</tr>
</tbody>
</table>
</div>
);
};
const EmulatorSection: React.FC = () => {
const { t } = useTranslation();
return (
<div className="docs-section">
<span className="docs-label">{t('docs.emulator.label')}</span>
<h1>{t('docs.emulator.heading')}</h1>
<p>
<Trans i18nKey="docs.emulator.lead" components={{ strong: <strong /> }} />
</p>
<h2>{t('docs.emulator.dataFlowHeading')}</h2>
<CodeBlock language="text">{`User Code (Monaco Editor)
Zustand Store (useEditorStore)
FastAPI Backend ──► arduino-cli ──► .hex / .uf2 file
AVRSimulator / RP2040Simulator
│ loadHex()
CPU execution loop (~60 FPS via requestAnimationFrame)
Port listeners (PORTB / PORTC / PORTD)
PinManager ──► Component state ──► React re-renders`}</CodeBlock>
<h2>{t('docs.emulator.avr8Heading')}</h2>
<p>
<Trans
i18nKey="docs.emulator.avr8Body"
components={{
a: <a href="https://github.com/wokwi/avr8js" target="_blank" rel="noopener noreferrer" />,
}}
/>
</p>
<h3>{t('docs.emulator.execLoopHeading')}</h3>
<p>{t('docs.emulator.execLoopBody')}</p>
<CodeBlock language="typescript">{`avrInstruction(cpu); // decode and execute one AVR instruction
cpu.tick(); // advance peripheral timers and counters`}</CodeBlock>
<h3>{t('docs.emulator.peripheralsHeading')}</h3>
<table>
<thead>
<tr>
<th>{t('docs.emulator.thPeripheral')}</th>
<th>{t('docs.emulator.thDetails')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.emulator.perGpio')}</td>
<td>{t('docs.emulator.perGpioDetail')}</td>
</tr>
<tr>
<td>{t('docs.emulator.perTimers')}</td>
<td>
<Trans i18nKey="docs.emulator.perTimersDetail" components={{ code: <code /> }} />
</td>
</tr>
<tr>
<td>{t('docs.emulator.perUsart')}</td>
<td>{t('docs.emulator.perUsartDetail')}</td>
</tr>
<tr>
<td>{t('docs.emulator.perAdc')}</td>
<td>{t('docs.emulator.perAdcDetail')}</td>
</tr>
<tr>
<td>{t('docs.emulator.perSpi')}</td>
<td>{t('docs.emulator.perSpiDetail')}</td>
</tr>
<tr>
<td>{t('docs.emulator.perI2c')}</td>
<td>{t('docs.emulator.perI2cDetail')}</td>
</tr>
</tbody>
</table>
<h3>{t('docs.emulator.pinMappingHeading')}</h3>
<table>
<thead>
<tr>
<th>{t('docs.emulator.thArduinoPin')}</th>
<th>{t('docs.emulator.thAvrPort')}</th>
<th>{t('docs.emulator.thBit')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>07</td>
<td>PORTD</td>
<td>07</td>
</tr>
<tr>
<td>813</td>
<td>PORTB</td>
<td>05</td>
</tr>
<tr>
<td>A0A5</td>
<td>PORTC</td>
<td>05</td>
</tr>
</tbody>
</table>
<h2>{t('docs.emulator.rp2040Heading')}</h2>
<p>
<Trans
i18nKey="docs.emulator.rp2040Body"
components={{
a: <a href="https://github.com/wokwi/rp2040js" target="_blank" rel="noopener noreferrer" />,
}}
/>
</p>
<ul>
<li>{t('docs.emulator.rp2040Bullet1')}</li>
<li>{t('docs.emulator.rp2040Bullet2')}</li>
<li>{t('docs.emulator.rp2040Bullet3')}</li>
</ul>
<h2>{t('docs.emulator.hexHeading')}</h2>
<p>
<Trans
i18nKey="docs.emulator.hexBody"
components={{ strong: <strong />, code: <code /> }}
/>
</p>
<ol>
<li>
<Trans i18nKey="docs.emulator.hex1" components={{ code: <code /> }} />
</li>
<li>{t('docs.emulator.hex2')}</li>
<li>
<Trans i18nKey="docs.emulator.hex3" components={{ code: <code /> }} />
</li>
<li>
<Trans i18nKey="docs.emulator.hex4" components={{ code: <code /> }} />
</li>
</ol>
<h2>{t('docs.emulator.sourceFilesHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.emulator.thFile')}</th>
<th>{t('docs.emulator.thPurpose')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>frontend/src/simulation/AVRSimulator.ts</code>
</td>
<td>{t('docs.emulator.fileAvrSimDesc')}</td>
</tr>
<tr>
<td>
<code>frontend/src/simulation/PinManager.ts</code>
</td>
<td>{t('docs.emulator.filePinManagerDesc')}</td>
</tr>
<tr>
<td>
<code>frontend/src/utils/hexParser.ts</code>
</td>
<td>{t('docs.emulator.fileHexParserDesc')}</td>
</tr>
<tr>
<td>
<code>frontend/src/components/simulator/SimulatorCanvas.tsx</code>
</td>
<td>{t('docs.emulator.fileSimCanvasDesc')}</td>
</tr>
<tr>
<td>
<code>backend/app/services/arduino_cli.py</code>
</td>
<td>{t('docs.emulator.fileArduinoCliDesc')}</td>
</tr>
<tr>
<td>
<code>backend/app/api/routes/compile.py</code>
</td>
<td>{t('docs.emulator.fileCompileRouteDesc')}</td>
</tr>
</tbody>
</table>
</div>
);
};
const ComponentsSection: React.FC = () => {
const { t } = useTranslation();
return (
<div className="docs-section">
<span className="docs-label">{t('docs.components.label')}</span>
<h1>{t('docs.components.heading')}</h1>
<p>
<Trans
i18nKey="docs.components.lead"
components={{
strong: <strong />,
a: (
<a
href="https://github.com/wokwi/wokwi-elements"
target="_blank"
rel="noopener noreferrer"
/>
),
}}
/>
</p>
<h2>{t('docs.components.addingHeading')}</h2>
<ol>
<li>
<Trans i18nKey="docs.components.adding1" components={{ strong: <strong /> }} />
</li>
<li>
<Trans i18nKey="docs.components.adding2" components={{ strong: <strong /> }} />
</li>
<li>{t('docs.components.adding3')}</li>
<li>
<Trans i18nKey="docs.components.adding4" components={{ strong: <strong /> }} />
</li>
</ol>
<h2>{t('docs.components.connectingHeading')}</h2>
<ol>
<li>
<Trans i18nKey="docs.components.connecting1" components={{ strong: <strong /> }} />
</li>
<li>
<Trans i18nKey="docs.components.connecting2" components={{ strong: <strong /> }} />
</li>
<li>
<Trans i18nKey="docs.components.connecting3" components={{ strong: <strong /> }} />
</li>
</ol>
<table>
<thead>
<tr>
<th>{t('docs.components.thColor')}</th>
<th>{t('docs.components.thSignalType')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span className="wire-dot" style={{ background: '#ef4444' }} /> {t('docs.components.colorRed')}
</td>
<td>{t('docs.components.sigVcc')}</td>
</tr>
<tr>
<td>
<span className="wire-dot" style={{ background: '#374151' }} /> {t('docs.components.colorBlack')}
</td>
<td>{t('docs.components.sigGnd')}</td>
</tr>
<tr>
<td>
<span className="wire-dot" style={{ background: '#3b82f6' }} /> {t('docs.components.colorBlue')}
</td>
<td>{t('docs.components.sigAnalog')}</td>
</tr>
<tr>
<td>
<span className="wire-dot" style={{ background: '#22c55e' }} /> {t('docs.components.colorGreen')}
</td>
<td>{t('docs.components.sigDigital')}</td>
</tr>
<tr>
<td>
<span className="wire-dot" style={{ background: '#a855f7' }} /> {t('docs.components.colorPurple')}
</td>
<td>{t('docs.components.sigPwm')}</td>
</tr>
<tr>
<td>
<span className="wire-dot" style={{ background: '#eab308' }} /> {t('docs.components.colorGold')}
</td>
<td>{t('docs.components.sigI2c')}</td>
</tr>
<tr>
<td>
<span className="wire-dot" style={{ background: '#f97316' }} /> {t('docs.components.colorOrange')}
</td>
<td>{t('docs.components.sigSpi')}</td>
</tr>
<tr>
<td>
<span className="wire-dot" style={{ background: '#06b6d4' }} /> {t('docs.components.colorCyan')}
</td>
<td>{t('docs.components.sigUsart')}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.components.categoriesHeading')}</h2>
<h3>{t('docs.components.outputHeading')}</h3>
<table>
<thead>
<tr>
<th>{t('docs.components.thComponent')}</th>
<th>{t('docs.components.thDescription')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.components.compLed')}</td>
<td>{t('docs.components.compLedDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.compRgbLed')}</td>
<td>{t('docs.components.compRgbLedDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.comp7Seg')}</td>
<td>{t('docs.components.comp7SegDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.compLcd16x2')}</td>
<td>{t('docs.components.compLcd16x2Desc')}</td>
</tr>
<tr>
<td>{t('docs.components.compLcd20x4')}</td>
<td>{t('docs.components.compLcd20x4Desc')}</td>
</tr>
<tr>
<td>{t('docs.components.compIli9341')}</td>
<td>{t('docs.components.compIli9341Desc')}</td>
</tr>
<tr>
<td>{t('docs.components.compBuzzer')}</td>
<td>{t('docs.components.compBuzzerDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.compNeoPixel')}</td>
<td>{t('docs.components.compNeoPixelDesc')}</td>
</tr>
</tbody>
</table>
<h3>{t('docs.components.inputHeading')}</h3>
<table>
<thead>
<tr>
<th>{t('docs.components.thComponent')}</th>
<th>{t('docs.components.thDescription')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.components.compPushButton')}</td>
<td>{t('docs.components.compPushButtonDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.compSlideSwitch')}</td>
<td>{t('docs.components.compSlideSwitchDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.compPotentiometer')}</td>
<td>{t('docs.components.compPotentiometerDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.compRotaryEncoder')}</td>
<td>{t('docs.components.compRotaryEncoderDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.compKeypad')}</td>
<td>{t('docs.components.compKeypadDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.compJoystick')}</td>
<td>{t('docs.components.compJoystickDesc')}</td>
</tr>
</tbody>
</table>
<h3>{t('docs.components.sensorsHeading')}</h3>
<table>
<thead>
<tr>
<th>{t('docs.components.thComponent')}</th>
<th>{t('docs.components.thDescription')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.components.compHcSr04')}</td>
<td>{t('docs.components.compHcSr04Desc')}</td>
</tr>
<tr>
<td>{t('docs.components.compDht22')}</td>
<td>{t('docs.components.compDht22Desc')}</td>
</tr>
<tr>
<td>{t('docs.components.compPir')}</td>
<td>{t('docs.components.compPirDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.compPhoto')}</td>
<td>{t('docs.components.compPhotoDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.compIrRecv')}</td>
<td>{t('docs.components.compIrRecvDesc')}</td>
</tr>
</tbody>
</table>
<h3>{t('docs.components.passiveHeading')}</h3>
<table>
<thead>
<tr>
<th>{t('docs.components.thComponent')}</th>
<th>{t('docs.components.thDescription')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.components.compResistor')}</td>
<td>{t('docs.components.compResistorDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.compCapacitor')}</td>
<td>{t('docs.components.compCapacitorDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.compInductor')}</td>
<td>{t('docs.components.compInductorDesc')}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.components.propsHeading')}</h2>
<p>
<Trans i18nKey="docs.components.propsLead" components={{ strong: <strong /> }} />
</p>
<table>
<thead>
<tr>
<th>{t('docs.components.thProperty')}</th>
<th>{t('docs.components.thDescription')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.components.propArduinoPin')}</td>
<td>{t('docs.components.propArduinoPinDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.propColor')}</td>
<td>{t('docs.components.propColorDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.propValue')}</td>
<td>{t('docs.components.propValueDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.propRotation')}</td>
<td>{t('docs.components.propRotationDesc')}</td>
</tr>
<tr>
<td>{t('docs.components.propDelete')}</td>
<td>{t('docs.components.propDeleteDesc')}</td>
</tr>
</tbody>
</table>
</div>
);
};
const RoadmapSection: React.FC = () => {
const { t } = useTranslation();
return (
<div className="docs-section">
<span className="docs-label">{t('docs.roadmap.label')}</span>
<h1>{t('docs.roadmap.heading')}</h1>
<p>{t('docs.roadmap.lead')}</p>
<h2>{t('docs.roadmap.implementedHeading')}</h2>
<ul>
<li>{t('docs.roadmap.impMonaco')}</li>
<li>{t('docs.roadmap.impMultiFile')}</li>
<li>
<Trans i18nKey="docs.roadmap.impArduinoCli" components={{ code: <code /> }} />
</li>
<li>{t('docs.roadmap.impAvr')}</li>
<li>{t('docs.roadmap.impPeripherals')}</li>
<li>{t('docs.roadmap.impRp2040')}</li>
<li>{t('docs.roadmap.impWokwi')}</li>
<li>{t('docs.roadmap.impWires')}</li>
<li>{t('docs.roadmap.impSerial')}</li>
<li>{t('docs.roadmap.impLibMgr')}</li>
<li>{t('docs.roadmap.impAuth')}</li>
<li>{t('docs.roadmap.impProjects')}</li>
<li>{t('docs.roadmap.impExamples')}</li>
<li>{t('docs.roadmap.impDocker')}</li>
</ul>
<h2>{t('docs.roadmap.inProgressHeading')}</h2>
<ul>
<li>{t('docs.roadmap.ipWires')}</li>
<li>{t('docs.roadmap.ipWireErrors')}</li>
</ul>
<h2>{t('docs.roadmap.nearTermHeading')}</h2>
<ul>
<li>{t('docs.roadmap.ntUndoRedo')}</li>
<li>
<Trans i18nKey="docs.roadmap.ntExport" components={{ code: <code /> }} />
</li>
<li>{t('docs.roadmap.ntMoreBoards')}</li>
<li>{t('docs.roadmap.ntBreadboard')}</li>
</ul>
<h2>{t('docs.roadmap.midTermHeading')}</h2>
<ul>
<li>{t('docs.roadmap.mtTypeDoc')}</li>
<li>
<Trans i18nKey="docs.roadmap.mtGhPages" components={{ code: <code /> }} />
</li>
<li>{t('docs.roadmap.mtSensors')}</li>
<li>{t('docs.roadmap.mtEeprom')}</li>
<li>{t('docs.roadmap.mtScope')}</li>
</ul>
<h2>{t('docs.roadmap.longTermHeading')}</h2>
<ul>
<li>{t('docs.roadmap.ltMultiplayer')}</li>
<li>{t('docs.roadmap.ltTutorials')}</li>
<li>{t('docs.roadmap.ltSdk')}</li>
<li>{t('docs.roadmap.ltMobile')}</li>
</ul>
<div className="docs-callout">
<Trans
i18nKey="docs.roadmap.contributeCallout"
components={{
strong: <strong />,
a: <a href={GITHUB_URL} target="_blank" rel="noopener noreferrer" />,
}}
/>
</div>
</div>
);
};
/* ── Architecture Section ─────────────────────────────── */
const ArchitectureSection: React.FC = () => {
const { t } = useTranslation();
return (
<div className="docs-section">
<span className="docs-label">{t('docs.architecture.label')}</span>
<h1>{t('docs.architecture.heading')}</h1>
<p>{t('docs.architecture.lead')}</p>
<h2>{t('docs.architecture.overviewHeading')}</h2>
<CodeBlock language="text">{`Browser (React + Vite)
├── Monaco Editor ──► useEditorStore (Zustand)
├── SimulatorCanvas ──► useSimulatorStore (Zustand)
│ ├── AVRSimulator (avr8js) 16 MHz AVR8 CPU
│ ├── RP2040Simulator (rp2040js)
│ ├── PinManager pin → component mapping
│ ├── PartSimulationRegistry 16 interactive parts
│ └── 48+ wokwi-elements Lit Web Components
└── HTTP (Axios) ──► FastAPI Backend (port 8001)
└── ArduinoCLIService ──► arduino-cli subprocess`}</CodeBlock>
<h2>{t('docs.architecture.dataFlowsHeading')}</h2>
<h3>{t('docs.architecture.compilationHeading')}</h3>
<CodeBlock language="text">{`Click "Compile"
→ EditorToolbar reads all workspace files
→ POST /api/compile/ { files, board_fqbn }
→ Backend: ArduinoCLIService writes temp dir
→ arduino-cli compile --fqbn <board> --output-dir build/
→ Returns hex_content (Intel HEX string)
→ useSimulatorStore.setCompiledHex() → loadHex()`}</CodeBlock>
<h3>{t('docs.architecture.simLoopHeading')}</h3>
<CodeBlock language="text">{`Click "Run"
→ AVRSimulator.start()
→ requestAnimationFrame loop @ ~60 FPS
→ Each frame: Math.floor(267 000 × speed) cycles
├── avrInstruction(cpu) — decode + execute one AVR instruction
└── cpu.tick() — advance Timer0/1/2, USART, ADC
→ PORTB/C/D write listeners fire
→ PinManager.updatePort() → per-pin callbacks
→ PartSimulationRegistry.onPinStateChange()
→ wokwi-elements update visually`}</CodeBlock>
<h3>{t('docs.architecture.inputCompsHeading')}</h3>
<CodeBlock language="text">{`User presses button on canvas
→ wokwi web component fires 'button-press' event
→ DynamicComponent catches event
→ PartSimulationRegistry.attachEvents() handler
→ AVRSimulator.setPinState(arduinoPin, LOW)
→ AVRIOPort.setPin() injects external pin state
→ CPU reads pin in next instruction`}</CodeBlock>
<h2>{t('docs.architecture.storesHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.architecture.thStore')}</th>
<th>{t('docs.architecture.thKeyState')}</th>
<th>{t('docs.emulator.thPurpose')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>useEditorStore</code>
</td>
<td>{t('docs.architecture.storeEditorState')}</td>
<td>{t('docs.architecture.storeEditorPurpose')}</td>
</tr>
<tr>
<td>
<code>useSimulatorStore</code>
</td>
<td>{t('docs.architecture.storeSimState')}</td>
<td>{t('docs.architecture.storeSimPurpose')}</td>
</tr>
<tr>
<td>
<code>useAuthStore</code>
</td>
<td>{t('docs.architecture.storeAuthState')}</td>
<td>{t('docs.architecture.storeAuthPurpose')}</td>
</tr>
<tr>
<td>
<code>useProjectStore</code>
</td>
<td>{t('docs.architecture.storeProjectState')}</td>
<td>{t('docs.architecture.storeProjectPurpose')}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.architecture.routesHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.architecture.thRoute')}</th>
<th>{t('docs.components.thDescription')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>POST /api/compile/</code>
</td>
<td>{t('docs.architecture.routeCompileDesc')}</td>
</tr>
<tr>
<td>
<code>GET /api/compile/boards</code>
</td>
<td>{t('docs.architecture.routeBoardsDesc')}</td>
</tr>
<tr>
<td>
<code>GET/POST /api/auth/*</code>
</td>
<td>{t('docs.architecture.routeAuthDesc')}</td>
</tr>
<tr>
<td>
<code>GET/POST /api/projects/*</code>
</td>
<td>{t('docs.architecture.routeProjectsDesc')}</td>
</tr>
<tr>
<td>
<code>GET /api/libraries/*</code>
</td>
<td>{t('docs.architecture.routeLibsDesc')}</td>
</tr>
<tr>
<td>
<code>GET /health</code>
</td>
<td>{t('docs.architecture.routeHealthDesc')}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.architecture.wireSysHeading')}</h2>
<p>{t('docs.architecture.wireSysLead')}</p>
<CodeBlock language="typescript">{`{
id: string
start: { componentId, pinName, x, y }
end: { componentId, pinName, x, y }
color: string
signalType: 'digital' | 'analog' | 'power-vcc' | 'power-gnd'
}`}</CodeBlock>
<ul>
<li>{t('docs.architecture.wireBullet1')}</li>
<li>{t('docs.architecture.wireBullet2')}</li>
<li>{t('docs.architecture.wireBullet3')}</li>
<li>{t('docs.architecture.wireBullet4')}</li>
</ul>
<div className="docs-callout">
<Trans
i18nKey="docs.architecture.fullDetailsCallout"
components={{
strong: <strong />,
a: (
<a
href={`${GITHUB_URL}/blob/master/docs/ARCHITECTURE.md`}
target="_blank"
rel="noopener noreferrer"
/>
),
}}
/>
</div>
</div>
);
};
/* ── Wokwi Libraries Section ──────────────────────────── */
const WokwiLibsSection: React.FC = () => {
const { t } = useTranslation();
return (
<div className="docs-section">
<span className="docs-label">{t('docs.thirdParty.label')}</span>
<h1>{t('docs.thirdParty.heading')}</h1>
<p>
<Trans i18nKey="docs.thirdParty.lead" components={{ code: <code /> }} />
</p>
<h2>{t('docs.thirdParty.clonedHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.thirdParty.thLibrary')}</th>
<th>{t('docs.thirdParty.thLocation')}</th>
<th>{t('docs.emulator.thPurpose')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<a
href="https://github.com/wokwi/wokwi-elements"
target="_blank"
rel="noopener noreferrer"
>
@wokwi/elements
</a>
</td>
<td>
<code>npm</code>
</td>
<td>{t('docs.thirdParty.libElementsDesc')}</td>
</tr>
<tr>
<td>
<a href="https://github.com/wokwi/avr8js" target="_blank" rel="noopener noreferrer">
avr8js
</a>
</td>
<td>
<code>npm</code>
</td>
<td>{t('docs.thirdParty.libAvr8jsDesc')}</td>
</tr>
<tr>
<td>
<a href="https://github.com/wokwi/rp2040js" target="_blank" rel="noopener noreferrer">
rp2040js
</a>
</td>
<td>
<code>npm</code>
</td>
<td>{t('docs.thirdParty.libRp2040jsDesc')}</td>
</tr>
</tbody>
</table>
<p>
<Trans i18nKey="docs.thirdParty.npmIntro" components={{ code: <code /> }} />
</p>
<h2>{t('docs.thirdParty.updatingHeading')}</h2>
<p>
<Trans i18nKey="docs.thirdParty.updatingBody" components={{ code: <code /> }} />
</p>
<h3>{t('docs.thirdParty.afterBumpHeading')}</h3>
<p>
<Trans i18nKey="docs.thirdParty.afterBumpBody" components={{ code: <code /> }} />
</p>
<CodeBlock language="bash">{`git clone --depth=1 https://github.com/wokwi/wokwi-elements.git \\
third-party/wokwi-elements
cd frontend && npm run generate:metadata`}</CodeBlock>
<h2>{t('docs.thirdParty.availableHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.thirdParty.thCategory')}</th>
<th>{t('docs.thirdParty.thComponents')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.thirdParty.catBoards')}</td>
<td>{t('docs.thirdParty.catBoardsList')}</td>
</tr>
<tr>
<td>{t('docs.thirdParty.catSensors')}</td>
<td>{t('docs.thirdParty.catSensorsList')}</td>
</tr>
<tr>
<td>{t('docs.thirdParty.catDisplays')}</td>
<td>{t('docs.thirdParty.catDisplaysList')}</td>
</tr>
<tr>
<td>{t('docs.thirdParty.catInput')}</td>
<td>{t('docs.thirdParty.catInputList')}</td>
</tr>
<tr>
<td>{t('docs.thirdParty.catOutput')}</td>
<td>{t('docs.thirdParty.catOutputList')}</td>
</tr>
<tr>
<td>{t('docs.thirdParty.catMotors')}</td>
<td>{t('docs.thirdParty.catMotorsList')}</td>
</tr>
<tr>
<td>{t('docs.thirdParty.catPassive')}</td>
<td>{t('docs.thirdParty.catPassiveList')}</td>
</tr>
<tr>
<td>{t('docs.thirdParty.catOther')}</td>
<td>{t('docs.thirdParty.catOtherList')}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.thirdParty.howAvrHeading')}</h2>
<CodeBlock language="typescript">{`import { CPU, avrInstruction, AVRTimer, AVRUSART, AVRADC, AVRIOPort } from 'avr8js';
const cpu = new CPU(programMemory); // ATmega328p at 16 MHz
const portB = new AVRIOPort(cpu, portBConfig); // digital pins 8-13
const portC = new AVRIOPort(cpu, portCConfig); // analog pins A0-A5
const portD = new AVRIOPort(cpu, portDConfig); // digital pins 0-7
function runFrame() {
const cycles = Math.floor(267_000 * speed);
for (let i = 0; i < cycles; i++) {
avrInstruction(cpu); // execute one AVR instruction
cpu.tick(); // advance timers + peripherals
}
requestAnimationFrame(runFrame);
}`}</CodeBlock>
<div className="docs-callout">
<Trans
i18nKey="docs.thirdParty.fullDetailsCallout"
components={{
strong: <strong />,
a: (
<a
href={`${GITHUB_URL}/blob/master/docs/WOKWI_LIBS.md`}
target="_blank"
rel="noopener noreferrer"
/>
),
}}
/>
</div>
</div>
);
};
/* ── MCP Server Section ───────────────────────────────── */
const McpSection: React.FC = () => {
const { t } = useTranslation();
return (
<div className="docs-section">
<span className="docs-label">{t('docs.mcp.label')}</span>
<h1>{t('docs.mcp.heading')}</h1>
<p>
<Trans
i18nKey="docs.mcp.lead"
components={{
a: (
<a
href="https://modelcontextprotocol.io/"
target="_blank"
rel="noopener noreferrer"
/>
),
}}
/>
</p>
<h2>{t('docs.mcp.toolsHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.mcp.thTool')}</th>
<th>{t('docs.components.thDescription')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>compile_project</code>
</td>
<td>{t('docs.mcp.toolCompileDesc')}</td>
</tr>
<tr>
<td>
<code>run_project</code>
</td>
<td>{t('docs.mcp.toolRunDesc')}</td>
</tr>
<tr>
<td>
<code>import_wokwi_json</code>
</td>
<td>
<Trans i18nKey="docs.mcp.toolImportDesc" components={{ code: <code /> }} />
</td>
</tr>
<tr>
<td>
<code>export_wokwi_json</code>
</td>
<td>
<Trans i18nKey="docs.mcp.toolExportDesc" components={{ code: <code /> }} />
</td>
</tr>
<tr>
<td>
<code>create_circuit</code>
</td>
<td>{t('docs.mcp.toolCreateDesc')}</td>
</tr>
<tr>
<td>
<code>update_circuit</code>
</td>
<td>{t('docs.mcp.toolUpdateDesc')}</td>
</tr>
<tr>
<td>
<code>generate_code_files</code>
</td>
<td>
<Trans i18nKey="docs.mcp.toolGenerateDesc" components={{ code: <code /> }} />
</td>
</tr>
</tbody>
</table>
<h2>{t('docs.mcp.transportHeading')}</h2>
<h3>{t('docs.mcp.stdioHeading')}</h3>
<CodeBlock language="bash">{`cd backend
python mcp_server.py`}</CodeBlock>
<p>
<Trans i18nKey="docs.mcp.stdioConfigBody" components={{ code: <code /> }} />
</p>
<CodeBlock language="json">{`{
"mcpServers": {
"velxio": {
"command": "python",
"args": ["/absolute/path/to/velxio/backend/mcp_server.py"]
}
}
}`}</CodeBlock>
<h3>{t('docs.mcp.sseHeading')}</h3>
<CodeBlock language="bash">{`cd backend
python mcp_sse_server.py --port 8002`}</CodeBlock>
<p>{t('docs.mcp.sseConfigBody')}</p>
<CodeBlock language="json">{`{
"mcpServers": {
"velxio": { "url": "http://localhost:8002/sse" }
}
}`}</CodeBlock>
<h2>{t('docs.mcp.circuitFormatHeading')}</h2>
<p>{t('docs.mcp.circuitFormatLead')}</p>
<CodeBlock language="json">{`{
"board_fqbn": "arduino:avr:uno",
"version": 1,
"components": [
{ "id": "led1", "type": "wokwi-led", "left": 200, "top": 100,
"rotate": 0, "attrs": { "color": "red" } }
],
"connections": [
{ "from_part": "uno", "from_pin": "13",
"to_part": "led1", "to_pin": "A", "color": "green" }
]
}`}</CodeBlock>
<h3>{t('docs.mcp.fqbnHeading')}</h3>
<table>
<thead>
<tr>
<th>{t('docs.intro.thBoard')}</th>
<th>{t('docs.mcp.thFqbn')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.intro.boardArduinoUno')}</td>
<td>
<code>arduino:avr:uno</code>
</td>
</tr>
<tr>
<td>{t('docs.intro.boardArduinoMega')}</td>
<td>
<code>arduino:avr:mega</code>
</td>
</tr>
<tr>
<td>{t('docs.intro.boardArduinoNano')}</td>
<td>
<code>arduino:avr:nano</code>
</td>
</tr>
<tr>
<td>{t('docs.intro.boardPiPico')}</td>
<td>
<code>rp2040:rp2040:rpipico</code>
</td>
</tr>
</tbody>
</table>
<h2>{t('docs.mcp.exampleHeading')}</h2>
<CodeBlock language="json">{`// Step 1 — Create a circuit
{
"tool": "create_circuit",
"arguments": {
"board_fqbn": "arduino:avr:uno",
"components": [
{ "id": "led1", "type": "wokwi-led",
"left": 150, "top": 100, "attrs": { "color": "red" } },
{ "id": "r1", "type": "wokwi-resistor",
"left": 150, "top": 180, "attrs": { "value": "220" } }
],
"connections": [
{ "from_part": "uno", "from_pin": "13",
"to_part": "led1", "to_pin": "A", "color": "green" },
{ "from_part": "led1", "from_pin": "C",
"to_part": "r1", "to_pin": "1", "color": "black" },
{ "from_part": "r1", "from_pin": "2",
"to_part": "uno", "to_pin": "GND.1", "color": "black" }
]
}
}
// Step 2 — Generate code
{
"tool": "generate_code_files",
"arguments": {
"circuit": "<result from Step 1>",
"sketch_name": "blink",
"extra_instructions": "Blink the red LED every 500ms"
}
}
// Step 3 — Compile
{
"tool": "compile_project",
"arguments": {
"files": [
{
"name": "blink.ino",
"content": "void setup(){pinMode(13,OUTPUT);}\\nvoid loop(){digitalWrite(13,HIGH);delay(500);digitalWrite(13,LOW);delay(500);}"
}
],
"board": "arduino:avr:uno"
}
}`}</CodeBlock>
<h2>{t('docs.mcp.setupHeading')}</h2>
<CodeBlock language="bash">{`cd backend
pip install -r requirements.txt
# Ensure arduino-cli is installed
arduino-cli version
arduino-cli core update-index
arduino-cli core install arduino:avr
# Run tests
python -m pytest tests/test_mcp_tools.py -v`}</CodeBlock>
<div className="docs-callout">
<Trans
i18nKey="docs.mcp.fullRefCallout"
components={{
strong: <strong />,
a: (
<a
href={`${GITHUB_URL}/blob/master/docs/MCP.md`}
target="_blank"
rel="noopener noreferrer"
/>
),
}}
/>
</div>
</div>
);
};
/* ── Setup / Project Status Section ──────────────────── */
const SetupSection: React.FC = () => {
const { t } = useTranslation();
const ok = t('docs.setup.statWorking');
return (
<div className="docs-section">
<span className="docs-label">{t('docs.setup.label')}</span>
<h1>{t('docs.setup.heading')}</h1>
<p>{t('docs.setup.lead')}</p>
<h2>{t('docs.setup.avrHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.setup.thFeature')}</th>
<th>{t('docs.setup.thStatus')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.setup.featAtmega')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featTimers')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featUsart')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>
<Trans i18nKey="docs.setup.featAdc" components={{ code: <code /> }} />
</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featGpio')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.feat60fps')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featSpeed')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featPwm')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featInjection')}</td>
<td>{ok}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.setup.compSysHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.setup.thFeature')}</th>
<th>{t('docs.setup.thStatus')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.setup.featAst')}</td>
<td>{t('docs.setup.statAstDetected')}</td>
</tr>
<tr>
<td>{t('docs.setup.featPicker')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featCategories')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featDynRender')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featDnd')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featRotation')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featPropsDialog')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featPinOverlay')}</td>
<td>{ok}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.setup.partsHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.setup.thPart')}</th>
<th>{t('docs.setup.thType')}</th>
<th>{t('docs.setup.thStatus')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.setup.partLed')}</td>
<td>{t('docs.setup.typeOutput')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partRgb')}</td>
<td>{t('docs.setup.typeOutputDigPwm')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partLedBar')}</td>
<td>{t('docs.setup.typeOutput')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.part7Seg')}</td>
<td>{t('docs.setup.typeOutput')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partPushButton')}</td>
<td>{t('docs.setup.typeInput')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partPushButton6')}</td>
<td>{t('docs.setup.typeInput')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partSlide')}</td>
<td>{t('docs.setup.typeInput')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partDipSwitch')}</td>
<td>{t('docs.setup.typeInput')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partPot')}</td>
<td>{t('docs.setup.typeInputAdc')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partSlidePot')}</td>
<td>{t('docs.setup.typeInputAdc')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partPhoto')}</td>
<td>{t('docs.setup.typeInputOutput')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partJoystick')}</td>
<td>{t('docs.setup.typeInputAdcDigital')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partServo')}</td>
<td>{t('docs.setup.typeOutput')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partBuzzer')}</td>
<td>{t('docs.setup.typeOutputAudio')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partLcd1602')}</td>
<td>{t('docs.setup.typeOutputHd44780')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.partLcd2004')}</td>
<td>{t('docs.setup.typeOutputHd44780')}</td>
<td>{ok}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.setup.wireHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.setup.thFeature')}</th>
<th>{t('docs.setup.thStatus')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.setup.featPinClick')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featPreview')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featOrtho')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featSegEdit')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.feat8Colors')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featAutoUpdate')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featSnap')}</td>
<td>{ok}</td>
</tr>
<tr>
<td>{t('docs.setup.featSelDel')}</td>
<td>{ok}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.setup.examplesHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.setup.thExample')}</th>
<th>{t('docs.thirdParty.thCategory')}</th>
<th>{t('docs.setup.thDifficulty')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.setup.exBlink')}</td>
<td>{t('docs.setup.catBasics')}</td>
<td>{t('docs.setup.diffBeginner')}</td>
</tr>
<tr>
<td>{t('docs.setup.exTraffic')}</td>
<td>{t('docs.setup.catBasics')}</td>
<td>{t('docs.setup.diffBeginner')}</td>
</tr>
<tr>
<td>{t('docs.setup.exButton')}</td>
<td>{t('docs.setup.catBasics')}</td>
<td>{t('docs.setup.diffBeginner')}</td>
</tr>
<tr>
<td>{t('docs.setup.exFade')}</td>
<td>{t('docs.setup.catBasics')}</td>
<td>{t('docs.setup.diffBeginner')}</td>
</tr>
<tr>
<td>{t('docs.setup.exSerial')}</td>
<td>{t('docs.setup.catComm')}</td>
<td>{t('docs.setup.diffBeginner')}</td>
</tr>
<tr>
<td>{t('docs.setup.exRgbColors')}</td>
<td>{t('docs.setup.catBasics')}</td>
<td>{t('docs.setup.diffIntermediate')}</td>
</tr>
<tr>
<td>{t('docs.setup.exSimon')}</td>
<td>{t('docs.setup.catGames')}</td>
<td>{t('docs.setup.diffAdvanced')}</td>
</tr>
<tr>
<td>{t('docs.setup.exLcd')}</td>
<td>{t('docs.setup.catDisplaysCat')}</td>
<td>{t('docs.setup.diffIntermediate')}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.setup.troubleshootingHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.gettingStarted.thProblem')}</th>
<th>{t('docs.gettingStarted.thSolution')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<Trans i18nKey="docs.setup.tsModuleProblem" components={{ code: <code /> }} />
</td>
<td>
<pre style={{ margin: 0 }}>
<code>cd frontend{'\n'}npm install</code>
</pre>
</td>
</tr>
<tr>
<td>{t('docs.setup.tsLedProblem')}</td>
<td>{t('docs.setup.tsLedSolution')}</td>
</tr>
<tr>
<td>{t('docs.setup.tsNewCompProblem')}</td>
<td>
<Trans i18nKey="docs.setup.tsNewCompSolution" components={{ code: <code /> }} />
</td>
</tr>
</tbody>
</table>
<div className="docs-callout">
<Trans
i18nKey="docs.setup.fullStatusCallout"
components={{
strong: <strong />,
a: (
<a
href={`${GITHUB_URL}/blob/master/docs/SETUP_COMPLETE.md`}
target="_blank"
rel="noopener noreferrer"
/>
),
}}
/>
</div>
</div>
);
};
const RiscVEmulationSection: React.FC = () => {
const { t } = useTranslation();
return (
<div className="docs-section">
<span className="docs-label">{t('docs.riscv.label')}</span>
<h1>{t('docs.riscv.heading')}</h1>
<p>
<Trans i18nKey="docs.riscv.lead" components={{ strong: <strong />, code: <code /> }} />
</p>
<h2>{t('docs.riscv.boardsHeading')}</h2>
<div className="docs-board-gallery">
<div className="docs-board-card">
<img src="/boards/esp32-c3.svg" alt={t('docs.riscv.altEsp32C3')} />
<span>{t('docs.riscv.boardEsp32C3')}</span>
</div>
<div className="docs-board-card">
<img src="/boards/xiao-esp32-c3.svg" alt={t('docs.riscv.altXiaoC3')} />
<span>{t('docs.riscv.boardXiaoC3')}</span>
</div>
<div className="docs-board-card">
<img src="/boards/esp32c3-supermini.svg" alt={t('docs.riscv.altC3Super')} />
<span>{t('docs.riscv.boardC3Super')}</span>
</div>
</div>
<table>
<thead>
<tr>
<th>{t('docs.riscv.thBoard')}</th>
<th>{t('docs.riscv.thCpu')}</th>
<th>{t('docs.riscv.thFlash')}</th>
<th>{t('docs.riscv.thRam')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.riscv.boardEsp32C3Name')}</td>
<td>{t('docs.riscv.cpuValue')}</td>
<td>{t('docs.riscv.flashValue')}</td>
<td>{t('docs.riscv.ramValue')}</td>
</tr>
<tr>
<td>{t('docs.riscv.boardXiaoC3Name')}</td>
<td>{t('docs.riscv.cpuValue')}</td>
<td>{t('docs.riscv.flashValue')}</td>
<td>{t('docs.riscv.ramValue')}</td>
</tr>
<tr>
<td>{t('docs.riscv.boardC3SuperName')}</td>
<td>{t('docs.riscv.cpuValue')}</td>
<td>{t('docs.riscv.flashValue')}</td>
<td>{t('docs.riscv.ramValue')}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.riscv.memoryHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.riscv.thRegion')}</th>
<th>{t('docs.riscv.thBaseAddr')}</th>
<th>{t('docs.riscv.thSize')}</th>
<th>{t('docs.riscv.thDescription')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.riscv.regIrom')}</td>
<td>
<code>0x42000000</code>
</td>
<td>{t('docs.riscv.size4mb')}</td>
<td>{t('docs.riscv.regIromDesc')}</td>
</tr>
<tr>
<td>{t('docs.riscv.regDrom')}</td>
<td>
<code>0x3C000000</code>
</td>
<td>{t('docs.riscv.size4mb')}</td>
<td>{t('docs.riscv.regDromDesc')}</td>
</tr>
<tr>
<td>{t('docs.riscv.regDram')}</td>
<td>
<code>0x3FC80000</code>
</td>
<td>{t('docs.riscv.size384kb')}</td>
<td>{t('docs.riscv.regDramDesc')}</td>
</tr>
<tr>
<td>{t('docs.riscv.regIram')}</td>
<td>
<code>0x4037C000</code>
</td>
<td>{t('docs.riscv.size384kb')}</td>
<td>{t('docs.riscv.regIramDesc')}</td>
</tr>
<tr>
<td>{t('docs.riscv.regUart')}</td>
<td>
<code>0x60000000</code>
</td>
<td>{t('docs.riscv.size1kb')}</td>
<td>{t('docs.riscv.regUartDesc')}</td>
</tr>
<tr>
<td>{t('docs.riscv.regGpio')}</td>
<td>
<code>0x60004000</code>
</td>
<td>{t('docs.riscv.size512b')}</td>
<td>{t('docs.riscv.regGpioDesc')}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.riscv.isaHeading')}</h2>
<ul>
<li>
<Trans i18nKey="docs.riscv.isaRv32i" components={{ strong: <strong /> }} />
</li>
<li>
<Trans i18nKey="docs.riscv.isaRv32m" components={{ strong: <strong /> }} />
</li>
<li>
<Trans i18nKey="docs.riscv.isaRv32c" components={{ strong: <strong /> }} />
</li>
</ul>
<h2>{t('docs.riscv.compileFlowHeading')}</h2>
<p>
<Trans i18nKey="docs.riscv.compileFlowLead" components={{ strong: <strong /> }} />
</p>
<ol>
<li>
<Trans i18nKey="docs.riscv.compileStep1" components={{ code: <code /> }} />
</li>
<li>
<Trans
i18nKey="docs.riscv.compileStep2"
components={{ strong: <strong />, code: <code /> }}
/>
</li>
<li>
<Trans i18nKey="docs.riscv.compileStep3" components={{ code: <code /> }} />
</li>
<li>
<Trans i18nKey="docs.riscv.compileStep4" components={{ code: <code /> }} />
</li>
<li>{t('docs.riscv.compileStep5')}</li>
</ol>
<h2>{t('docs.riscv.gpioHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.riscv.thRegister')}</th>
<th>{t('docs.riscv.thOffset')}</th>
<th>{t('docs.riscv.thDescription')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>GPIO_OUT_REG</code>
</td>
<td>
<code>+0x04</code>
</td>
<td>{t('docs.riscv.regOutDesc')}</td>
</tr>
<tr>
<td>
<code>GPIO_OUT_W1TS</code>
</td>
<td>
<code>+0x08</code>
</td>
<td>{t('docs.riscv.regW1tsDesc')}</td>
</tr>
<tr>
<td>
<code>GPIO_OUT_W1TC</code>
</td>
<td>
<code>+0x0C</code>
</td>
<td>{t('docs.riscv.regW1tcDesc')}</td>
</tr>
<tr>
<td>
<code>GPIO_ENABLE_REG</code>
</td>
<td>
<code>+0x20</code>
</td>
<td>{t('docs.riscv.regEnableDesc')}</td>
</tr>
<tr>
<td>
<code>GPIO_IN_REG</code>
</td>
<td>
<code>+0x3C</code>
</td>
<td>{t('docs.riscv.regInDesc')}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.riscv.uart0Heading')}</h2>
<p>
<Trans i18nKey="docs.riscv.uart0Body" components={{ code: <code /> }} />
</p>
<h2>{t('docs.riscv.sourceFilesHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.riscv.thFile')}</th>
<th>{t('docs.riscv.thRole')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>simulation/RiscVCore.ts</code>
</td>
<td>{t('docs.riscv.fileRiscVCoreDesc')}</td>
</tr>
<tr>
<td>
<code>simulation/Esp32C3Simulator.ts</code>
</td>
<td>{t('docs.riscv.fileEsp32C3SimDesc')}</td>
</tr>
<tr>
<td>
<code>utils/esp32ImageParser.ts</code>
</td>
<td>{t('docs.riscv.fileImageParserDesc')}</td>
</tr>
</tbody>
</table>
<div className="docs-callout">
<Trans
i18nKey="docs.riscv.fullDetailsCallout"
components={{
strong: <strong />,
a: (
<a
href={`${GITHUB_URL}/blob/master/docs/RISCV_EMULATION.md`}
target="_blank"
rel="noopener noreferrer"
/>
),
}}
/>
</div>
</div>
);
};
const Esp32EmulationSection: React.FC = () => {
const { t } = useTranslation();
return (
<div className="docs-section">
<span className="docs-label">{t('docs.esp32.label')}</span>
<h1>{t('docs.esp32.heading')}</h1>
<p>
<Trans i18nKey="docs.esp32.lead" components={{ strong: <strong /> }} />
</p>
<div className="docs-callout">
<Trans i18nKey="docs.esp32.noteCallout" components={{ strong: <strong /> }} />
</div>
<h2>{t('docs.esp32.howHeading')}</h2>
<ol>
<li>
<Trans i18nKey="docs.esp32.how1" components={{ code: <code /> }} />
</li>
<li>
<Trans i18nKey="docs.esp32.how2" components={{ code: <code /> }} />
</li>
<li>{t('docs.esp32.how3')}</li>
<li>{t('docs.esp32.how4')}</li>
<li>{t('docs.esp32.how5')}</li>
</ol>
<h2>{t('docs.esp32.boardsHeading')}</h2>
<div className="docs-board-gallery">
<div className="docs-board-card">
<img src="/boards/esp32-devkit-c-v4.svg" alt={t('docs.esp32.altEsp32Devkit')} />
<span>{t('docs.esp32.boardEsp32Devkit')}</span>
</div>
<div className="docs-board-card">
<img src="/boards/esp32-s3.svg" alt={t('docs.esp32.altEsp32S3')} />
<span>{t('docs.esp32.boardEsp32S3')}</span>
</div>
<div className="docs-board-card">
<img src="/boards/esp32-cam.svg" alt={t('docs.esp32.altEsp32Cam')} />
<span>{t('docs.esp32.boardEsp32Cam')}</span>
</div>
<div className="docs-board-card">
<img src="/boards/xiao-esp32-s3.svg" alt={t('docs.esp32.altXiaoS3')} />
<span>{t('docs.esp32.boardXiaoS3')}</span>
</div>
<div className="docs-board-card">
<img src="/boards/arduino-nano-esp32.svg" alt={t('docs.esp32.altNanoEsp32')} />
<span>{t('docs.esp32.boardNanoEsp32')}</span>
</div>
</div>
<table>
<thead>
<tr>
<th>{t('docs.esp32.thBoard')}</th>
<th>{t('docs.esp32.thCpu')}</th>
<th>{t('docs.esp32.thEmulation')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.esp32.boardEsp32')}</td>
<td>{t('docs.esp32.cpuLx6')}</td>
<td>{t('docs.esp32.emuQemu')}</td>
</tr>
<tr>
<td>{t('docs.esp32.boardEsp32S3Name')}</td>
<td>{t('docs.esp32.cpuLx7')}</td>
<td>{t('docs.esp32.emuQemu')}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.esp32.peripheralsHeading')}</h2>
<ul>
<li>
<Trans i18nKey="docs.esp32.perGpio" components={{ strong: <strong /> }} />
</li>
<li>
<Trans
i18nKey="docs.esp32.perUart"
components={{ strong: <strong />, code: <code /> }}
/>
</li>
<li>
<Trans i18nKey="docs.esp32.perI2cSpi" components={{ strong: <strong /> }} />
</li>
<li>
<Trans i18nKey="docs.esp32.perRmt" components={{ strong: <strong /> }} />
</li>
<li>
<Trans i18nKey="docs.esp32.perLedc" components={{ strong: <strong /> }} />
</li>
<li>
<Trans i18nKey="docs.esp32.perWifi" components={{ strong: <strong /> }} />
</li>
</ul>
<h2>{t('docs.esp32.requirementsHeading')}</h2>
<p>
<Trans
i18nKey="docs.esp32.requirementsBody"
components={{
strong: <strong />,
a: <a href="https://velxio.dev" target="_blank" rel="noopener noreferrer" />,
}}
/>
</p>
<div className="docs-callout">
<Trans
i18nKey="docs.esp32.fullDetailsCallout"
components={{
strong: <strong />,
a: (
<a
href={`${GITHUB_URL}/blob/master/docs/ESP32_EMULATION.md`}
target="_blank"
rel="noopener noreferrer"
/>
),
}}
/>
</div>
</div>
);
};
/* ── RP2040 Emulation Section ─────────────────────────── */
const Rp2040EmulationSection: React.FC = () => {
const { t } = useTranslation();
return (
<div className="docs-section">
<span className="docs-label">{t('docs.rp2040.label')}</span>
<h1>{t('docs.rp2040.heading')}</h1>
<p>
<Trans
i18nKey="docs.rp2040.lead"
components={{
a: <a href="https://github.com/wokwi/rp2040js" target="_blank" rel="noopener noreferrer" />,
}}
/>
</p>
<h2>{t('docs.rp2040.boardsHeading')}</h2>
<div className="docs-board-gallery">
<div className="docs-board-card">
<img src="/boards/pi-pico.svg" alt={t('docs.rp2040.altPico')} />
<span>{t('docs.rp2040.boardPico')}</span>
</div>
<div className="docs-board-card">
<img src="/boards/pi-pico-w.svg" alt={t('docs.rp2040.altPicoW')} />
<span>{t('docs.rp2040.boardPicoW')}</span>
</div>
</div>
<table>
<thead>
<tr>
<th>{t('docs.rp2040.thBoard')}</th>
<th>{t('docs.rp2040.thFqbn')}</th>
<th>{t('docs.rp2040.thBuiltinLed')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.rp2040.boardPico')}</td>
<td>
<code>rp2040:rp2040:rpipico</code>
</td>
<td>{t('docs.rp2040.ledGpio25')}</td>
</tr>
<tr>
<td>{t('docs.rp2040.boardPicoW')}</td>
<td>
<code>rp2040:rp2040:rpipicow</code>
</td>
<td>{t('docs.rp2040.ledCyw43')}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.rp2040.binaryHeading')}</h2>
<p>
<Trans i18nKey="docs.rp2040.binaryBody1" components={{ code: <code /> }} />
</p>
<p>
<Trans i18nKey="docs.rp2040.binaryBody2" components={{ code: <code /> }} />
</p>
<h2>{t('docs.rp2040.peripheralsHeading')}</h2>
<table>
<thead>
<tr>
<th>{t('docs.rp2040.thPeripheral')}</th>
<th>{t('docs.rp2040.thSupport')}</th>
<th>{t('docs.rp2040.thNotes')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.rp2040.perGpio')}</td>
<td>{t('docs.rp2040.supFull')}</td>
<td>{t('docs.rp2040.perGpioNotes')}</td>
</tr>
<tr>
<td>{t('docs.rp2040.perUart')}</td>
<td>{t('docs.rp2040.supFull')}</td>
<td>{t('docs.rp2040.perUartNotes')}</td>
</tr>
<tr>
<td>{t('docs.rp2040.perAdc')}</td>
<td>{t('docs.rp2040.supFull')}</td>
<td>{t('docs.rp2040.perAdcNotes')}</td>
</tr>
<tr>
<td>{t('docs.rp2040.perI2c')}</td>
<td>{t('docs.rp2040.supPartial')}</td>
<td>{t('docs.rp2040.perI2cNotes')}</td>
</tr>
<tr>
<td>{t('docs.rp2040.perSpi')}</td>
<td>{t('docs.rp2040.supLoopback')}</td>
<td>{t('docs.rp2040.perSpiNotes')}</td>
</tr>
<tr>
<td>{t('docs.rp2040.perPwm')}</td>
<td>{t('docs.rp2040.supFreqOnly')}</td>
<td>{t('docs.rp2040.perPwmNotes')}</td>
</tr>
<tr>
<td>{t('docs.rp2040.perTimer')}</td>
<td>{t('docs.rp2040.supFull')}</td>
<td>
<Trans i18nKey="docs.rp2040.perTimerNotes" components={{ code: <code /> }} />
</td>
</tr>
</tbody>
</table>
<h2>{t('docs.rp2040.wfiHeading')}</h2>
<p>
<Trans
i18nKey="docs.rp2040.wfiBody"
components={{ strong: <strong />, code: <code /> }}
/>
</p>
<h2>{t('docs.rp2040.simLoopHeading')}</h2>
<p>
<Trans
i18nKey="docs.rp2040.simLoopBody"
components={{ strong: <strong />, code: <code /> }}
/>
</p>
<h2>{t('docs.rp2040.limitsHeading')}</h2>
<ul>
<li>{t('docs.rp2040.limit1')}</li>
<li>{t('docs.rp2040.limit2')}</li>
<li>{t('docs.rp2040.limit3')}</li>
<li>{t('docs.rp2040.limit4')}</li>
<li>
<Trans i18nKey="docs.rp2040.limit5" components={{ code: <code /> }} />
</li>
</ul>
<h2>{t('docs.rp2040.fullDocsHeading')}</h2>
<p>
<Trans
i18nKey="docs.rp2040.fullDocsBody"
components={{
a: (
<a
href="https://github.com/davidmonterocrespo24/velxio/blob/master/docs/RP2040_EMULATION.md"
target="_blank"
rel="noopener noreferrer"
/>
),
}}
/>
</p>
</div>
);
};
/* ── Raspberry Pi 3 Emulation Section ─────────────────── */
const RaspberryPi3EmulationSection: React.FC = () => {
const { t } = useTranslation();
return (
<div className="docs-section">
<span className="docs-label">{t('docs.rpi3.label')}</span>
<h1>{t('docs.rpi3.heading')}</h1>
<p>
<Trans i18nKey="docs.rpi3.lead" components={{ strong: <strong />, code: <code /> }} />
</p>
<h2>{t('docs.rpi3.boardsHeading')}</h2>
<div className="docs-board-gallery">
<div className="docs-board-card">
<img src="/boards/Raspberry_Pi_3.svg" alt={t('docs.rpi3.altRpi3')} />
<span>{t('docs.rpi3.boardRpi3')}</span>
</div>
</div>
<table>
<thead>
<tr>
<th>{t('docs.rpi3.thBoard')}</th>
<th>{t('docs.rpi3.thQemuMachine')}</th>
<th>{t('docs.rpi3.thCpu')}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{t('docs.rpi3.boardRpi3Name')}</td>
<td>
<code>raspi3b</code>
</td>
<td>{t('docs.rpi3.cpuBcm')}</td>
</tr>
</tbody>
</table>
<h2>{t('docs.rpi3.serialHeading')}</h2>
<p>{t('docs.rpi3.serialLead')}</p>
<ul>
<li>
<Trans
i18nKey="docs.rpi3.serialUser"
components={{ strong: <strong />, code: <code /> }}
/>
</li>
<li>
<Trans i18nKey="docs.rpi3.serialGpio" components={{ strong: <strong /> }} />
</li>
</ul>
<h2>{t('docs.rpi3.shimHeading')}</h2>
<p>
<Trans
i18nKey="docs.rpi3.shimBody1"
components={{ strong: <strong />, code: <code /> }}
/>
</p>
<p>
<Trans i18nKey="docs.rpi3.shimBody2" components={{ code: <code /> }} />
</p>
<h2>{t('docs.rpi3.vfsHeading')}</h2>
<p>{t('docs.rpi3.vfsBody')}</p>
<CodeBlock language="text">{`/home/pi/
├── script.py ← user's main Python script
└── lib/
└── helper.py ← optional helper library`}</CodeBlock>
<h2>{t('docs.rpi3.overlayHeading')}</h2>
<p>
<Trans i18nKey="docs.rpi3.overlayBody" components={{ strong: <strong /> }} />
</p>
<h2>{t('docs.rpi3.limitsHeading')}</h2>
<ul>
<li>{t('docs.rpi3.limit1')}</li>
<li>{t('docs.rpi3.limit2')}</li>
<li>{t('docs.rpi3.limit3')}</li>
<li>{t('docs.rpi3.limit4')}</li>
<li>{t('docs.rpi3.limit5')}</li>
<li>{t('docs.rpi3.limit6')}</li>
</ul>
<h2>{t('docs.rpi3.fullDocsHeading')}</h2>
<p>
<Trans
i18nKey="docs.rpi3.fullDocsBody"
components={{
a: (
<a
href="https://github.com/davidmonterocrespo24/velxio/blob/master/docs/RASPBERRYPI3_EMULATION.md"
target="_blank"
rel="noopener noreferrer"
/>
),
}}
/>
</p>
</div>
);
};
/* ── Build QEMU from source ───────────────────────────── */
//
// Transparency section. The Velxio docker image ships with prebuilt
// libqemu-xtensa / libqemu-riscv32 — anyone who'd rather not run
// third-party binaries can rebuild them from lcgamboa/qemu and drop
// them in. Body copy is intentionally hardcoded English: the value
// here is technical clarity and a clean link to the canonical
// docs/BUILD-QEMU.md, not a localised marketing surface.
const BuildQemuSection: React.FC = () => {
return (
<div className="docs-section">
<span className="docs-label">Self-hosting</span>
<h1>Build QEMU libraries from source</h1>
<p>
Velxio ships with prebuilt <code>libqemu-xtensa.so</code> and{' '}
<code>libqemu-riscv32.so</code> so ESP32 / ESP32-S3 / ESP32-C3
simulation works the moment you pull the docker image. The
prebuilts are a convenience Velxio is <strong>AGPLv3</strong>{' '}
and so is the QEMU fork it depends on, which means you can
always rebuild the libraries yourself from source and run
those instead.
</p>
<h2>Why you might want to</h2>
<ul>
<li>
<strong>Audit the supply chain.</strong> Regulated deployments
often require that every shared object on the box was built
from a known-good source tree.
</li>
<li>
<strong>Patch QEMU.</strong> Add a peripheral the upstream
fork doesn't emulate, or backport a fix from mainline QEMU.
</li>
<li>
<strong>You're on an unusual platform.</strong> We currently
publish Linux x86_64, Linux ARM64, macOS ARM64 and Windows
x86_64. BSDs, exotic libc, macOS Intel build your own.
</li>
<li>
<strong>Trust nothing.</strong> A valid reason. Drop our
binaries, rebuild from sources you've audited, and the
chain is your tree only.
</li>
</ul>
<h2>The short version</h2>
<p>
On a Linux box with a working C toolchain:
</p>
<pre><code>{`git clone https://github.com/lcgamboa/qemu.git
cd qemu
# ESP32 (Xtensa)
mkdir build-xtensa && cd build-xtensa
../configure --target-list=xtensa-softmmu --enable-shared-lib \\
--disable-werror --disable-tools --disable-docs
ninja
# → produces libqemu-xtensa.so (~46 MB)
cd ..
# ESP32-C3 (RISC-V)
mkdir build-riscv32 && cd build-riscv32
../configure --target-list=riscv32-softmmu --enable-shared-lib \\
--disable-werror --disable-tools --disable-docs
ninja
# → produces libqemu-riscv32.so (~45 MB)`}</code></pre>
<p>
Drop both <code>.so</code> files into <code>/app/lib/</code>{' '}
inside the running Velxio container (or whatever host path you
bind-mount to it) and restart. The backend dlopens whichever
library is on disk on the next simulation start, so this
replaces the shipped binaries cleanly.
</p>
<h2>The full guide</h2>
<p>
Step-by-step build instructions, dependency lists per OS
(Debian / Arch / macOS), the canonical commit ID we anchor the
prebuilts to, troubleshooting for the common configure / ninja
failures, and the licensing notes (QEMU is GPL-2.0, Velxio is
AGPLv3, the dlopen boundary keeps them orthogonal) live in the
full document:
</p>
<p>
<a
href="https://github.com/davidmonterocrespo24/velxio/blob/master/docs/BUILD-QEMU.md"
target="_blank"
rel="noopener noreferrer"
>
Read <code>docs/BUILD-QEMU.md</code> on GitHub →
</a>
</p>
<h2>Or use the prebuilts</h2>
<p>
If you don't have a 15-30 minute build in you, the
sha256-pinned prebuilts are available at{' '}
<a
href="https://velxio.dev/license/signup"
target="_blank"
rel="noopener noreferrer"
>
velxio.dev/license/signup
</a>{' '}
(free personal-use key, takes a minute), and the existing
public release at <code>github.com/davidmonterocrespo24/velxio
/releases/tag/qemu-prebuilt</code> still serves the same files
byte-for-byte. Both produce identical libraries to what this
guide builds there's no "blessed" version, just convenience
choices.
</p>
</div>
);
};
const SECTION_MAP: Record<SectionId, React.FC> = {
intro: IntroSection,
'getting-started': GettingStartedSection,
emulator: EmulatorSection,
'riscv-emulation': RiscVEmulationSection,
'esp32-emulation': Esp32EmulationSection,
'rp2040-emulation': Rp2040EmulationSection,
'raspberry-pi3-emulation': RaspberryPi3EmulationSection,
components: ComponentsSection,
roadmap: RoadmapSection,
architecture: ArchitectureSection,
'third-party': WokwiLibsSection,
mcp: McpSection,
setup: SetupSection,
'build-qemu': BuildQemuSection,
};
/* ── Page ─────────────────────────────────────────────── */
export const DocsPage: React.FC = () => {
const { section } = useParams<{ section?: string }>();
const navigate = useNavigate();
const { t } = useTranslation();
const localize = useLocalizedHref();
const [sidebarOpen, setSidebarOpen] = useState(false);
// Derive active section from URL; fall back to 'intro'
const activeSection: SectionId =
section && VALID_SECTIONS.includes(section as SectionId) ? (section as SectionId) : 'intro';
// Redirect bare /docs → /docs/intro so every section has a canonical URL
useEffect(() => {
if (!section) {
navigate(localize('/docs/intro'), { replace: true });
}
}, [section, navigate, localize]);
// Capture the original <head> values once on mount and restore them on unmount
useEffect(() => {
const origTitle = document.title;
// Helper to capture an element and its original attribute value
const captureAttr = <E extends Element>(selector: string, attr: string): [E | null, string] => {
const el = document.querySelector<E>(selector);
return [el, el?.getAttribute(attr) ?? ''];
};
const [descEl, origDesc] = captureAttr<HTMLMetaElement>('meta[name="description"]', 'content');
const [canonicalEl, origCanonical] = captureAttr<HTMLLinkElement>(
'link[rel="canonical"]',
'href',
);
const [ogTitleEl, origOgTitle] = captureAttr<HTMLMetaElement>(
'meta[property="og:title"]',
'content',
);
const [ogDescEl, origOgDesc] = captureAttr<HTMLMetaElement>(
'meta[property="og:description"]',
'content',
);
const [ogUrlEl, origOgUrl] = captureAttr<HTMLMetaElement>('meta[property="og:url"]', 'content');
const [twTitleEl, origTwTitle] = captureAttr<HTMLMetaElement>(
'meta[name="twitter:title"]',
'content',
);
const [twDescEl, origTwDesc] = captureAttr<HTMLMetaElement>(
'meta[name="twitter:description"]',
'content',
);
return () => {
document.title = origTitle;
descEl?.setAttribute('content', origDesc);
canonicalEl?.setAttribute('href', origCanonical);
ogTitleEl?.setAttribute('content', origOgTitle);
ogDescEl?.setAttribute('content', origOgDesc);
ogUrlEl?.setAttribute('content', origOgUrl);
twTitleEl?.setAttribute('content', origTwTitle);
twDescEl?.setAttribute('content', origTwDesc);
document.getElementById('docs-jsonld')?.remove();
};
}, []); // runs once on mount; cleanup runs once on unmount
// Update all head metadata + JSON-LD per section.
// No cleanup here — the mount effect above restores defaults on unmount,
// and on a section change the next run of this effect immediately overwrites.
useEffect(() => {
const meta = SECTION_META[activeSection];
const title = t(meta.titleKey);
const description = t(meta.descriptionKey);
const pageUrl = `${BASE_URL}/docs/${activeSection}`;
document.title = title;
const set = (selector: string, value: string) =>
document.querySelector<HTMLMetaElement>(selector)?.setAttribute('content', value);
set('meta[name="description"]', description);
set('meta[property="og:title"]', title);
set('meta[property="og:description"]', description);
set('meta[property="og:url"]', pageUrl);
set('meta[name="twitter:title"]', title);
set('meta[name="twitter:description"]', description);
const canonicalEl = document.querySelector<HTMLLinkElement>('link[rel="canonical"]');
if (canonicalEl) canonicalEl.setAttribute('href', pageUrl);
// Build the breadcrumb section label for JSON-LD
const activeNav = NAV_ITEMS.find((i) => i.id === activeSection);
const sectionLabel = activeNav ? t(activeNav.labelKey) : activeSection;
// Inject / update JSON-LD structured data for this doc page
const ldId = 'docs-jsonld';
let ldScript = document.getElementById(ldId) as HTMLScriptElement | null;
if (!ldScript) {
ldScript = document.createElement('script');
ldScript.id = ldId;
ldScript.type = 'application/ld+json';
document.head.appendChild(ldScript);
}
ldScript.textContent = JSON.stringify({
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'TechArticle',
headline: title,
description: description,
url: pageUrl,
isPartOf: { '@type': 'WebSite', url: `${BASE_URL}/`, name: 'Velxio' },
inLanguage: 'en-US',
author: AUTHOR,
},
{
'@type': 'BreadcrumbList',
itemListElement: [
{ '@type': 'ListItem', position: 1, name: 'Home', item: `${BASE_URL}/` },
{
'@type': 'ListItem',
position: 2,
name: 'Documentation',
item: `${BASE_URL}/docs/intro`,
},
{ '@type': 'ListItem', position: 3, name: sectionLabel, item: pageUrl },
],
},
],
});
}, [activeSection, t]);
const ActiveContent = SECTION_MAP[activeSection];
const activeIdx = NAV_ITEMS.findIndex((i) => i.id === activeSection);
return (
<div className="docs-page">
<AppHeader />
<div className="docs-mobile-bar">
<button
className="docs-sidebar-toggle"
onClick={() => setSidebarOpen((v) => !v)}
aria-label={t('docs.toggleSidebar')}
>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
>
<line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="18" x2="21" y2="18" />
</svg>
</button>
<span className="docs-mobile-bar-title">{t('docs.pageTitle')}</span>
</div>
<div className="docs-body">
{/* Sidebar */}
<aside className={`docs-sidebar${sidebarOpen ? ' docs-sidebar--open' : ''}`}>
<div className="docs-sidebar-title">{t('docs.pageTitle')}</div>
<nav className="docs-sidebar-nav">
{NAV_ITEMS.map((item) => (
<Link
key={item.id}
to={localize(`/docs/${item.id}`)}
className={`docs-sidebar-item${activeSection === item.id ? ' docs-sidebar-item--active' : ''}`}
onClick={() => setSidebarOpen(false)}
>
{t(item.labelKey)}
</Link>
))}
</nav>
<div className="docs-sidebar-divider" />
<div className="docs-sidebar-title docs-sidebar-title--pages">{t('docs.pages')}</div>
<nav className="docs-sidebar-nav">
<Link
to={localize('/')}
className="docs-sidebar-item docs-sidebar-link"
onClick={() => setSidebarOpen(false)}
>
{t('docs.pagesNav.home')}
</Link>
<Link
to={localize('/editor')}
className="docs-sidebar-item docs-sidebar-link"
onClick={() => setSidebarOpen(false)}
>
{t('docs.pagesNav.editor')}
</Link>
<Link
to={localize('/examples')}
className="docs-sidebar-item docs-sidebar-link"
onClick={() => setSidebarOpen(false)}
>
{t('docs.pagesNav.examples')}
</Link>
</nav>
<div className="docs-sidebar-footer">
<a
href={GITHUB_URL}
target="_blank"
rel="noopener noreferrer"
className="docs-sidebar-gh"
>
<IcoGitHub /> {t('docs.viewOnGitHub')}
</a>
</div>
</aside>
{/* Main content */}
<main className="docs-main">
<ActiveContent />
{/* Prev / Next navigation */}
<div className="docs-pagination">
{activeIdx > 0 && (
<Link
to={localize(`/docs/${NAV_ITEMS[activeIdx - 1].id}`)}
className="docs-pagination-btn docs-pagination-btn--prev"
onClick={() => window.scrollTo(0, 0)}
>
{t(NAV_ITEMS[activeIdx - 1].labelKey)}
</Link>
)}
{activeIdx < NAV_ITEMS.length - 1 && (
<Link
to={localize(`/docs/${NAV_ITEMS[activeIdx + 1].id}`)}
className="docs-pagination-btn docs-pagination-btn--next"
onClick={() => window.scrollTo(0, 0)}
>
{t(NAV_ITEMS[activeIdx + 1].labelKey)}
</Link>
)}
</div>
</main>
</div>
</div>
);
};