Files
homeclaw/web-ui/html/arduino-emulator.html
T
kimandClaude Sonnet 5 61885cb923 fix: 코드에디터 iframe 안 에뮬레이터 모바일 레이아웃 오판 + 리셋 버튼
arduino-emulator.html이 코드에디터 안 iframe으로 뜰 때, 미디어쿼리가 창
폭이 아니라 iframe 폭으로 평가되어 넓은 화면에서도 모바일 서랍 레이아웃이
나오던 버그 수정 (브레이크포인트 1024px→760px, JS 판정도 matchMedia로
통일해 CSS와 어긋나지 않게 함). 코드에디터에 에뮬레이터 세션 강제
재시작 버튼 추가, 패널 높이를 고정값→75vh로.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 13:13:28 +09:00

855 lines
66 KiB
HTML
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.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Arduino Emulator</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Noto+Sans+KR:wght@400;500;600&display=swap');
:root {
--bg:#f0f4f8; --panel:#ffffff; --board-bg:#f7fafc;
--accent:#2563eb; --green:#16a34a; --red:#dc2626; --yellow:#d97706; --muted:#64748b; --orange:#ea580c;
--bdr:#cbd5e1; --bdr-in:#e2e8f0; --bdr2:#94a3b8;
--ctrl:#e8eef5;
--board-tint:none;
--text:#1e293b; --text2:#475569;
}
[data-theme="cosmic"] {
--bg:#10101e; --panel:#1e1e38; --board-bg:#181830;
--accent:#5b9cf8; --green:#34d058; --red:#f87171; --yellow:#fcd34d; --muted:#9098b4; --orange:#fb923c;
--bdr:#3a3a60; --bdr-in:#2c2c52; --bdr2:#4a4a72;
--ctrl:#252548;
--board-tint:none;
--text:#e5e7eb; --text2:#9ca3af;
}
[data-theme="matrix"] {
--bg:#061006; --panel:#0e200e; --board-bg:#0b1a0b;
--accent:#4ade80; --green:#86efac; --red:#f87171; --yellow:#fde68a; --muted:#6ab06a; --orange:#86efac;
--bdr:#246024; --bdr-in:#184818; --bdr2:#2e762e;
--ctrl:#102810;
--board-tint:sepia(1) hue-rotate(85deg) saturate(2.5);
--text:#d1fae5; --text2:#6ab06a;
}
[data-theme="amber"] {
--bg:#fffbeb; --panel:#fef3c7; --board-bg:#fefce8;
--accent:#b45309; --green:#15803d; --red:#dc2626; --yellow:#d97706; --muted:#92400e; --orange:#c2410c;
--bdr:#d97706; --bdr-in:#fde68a; --bdr2:#b45309;
--ctrl:#fde68a;
--board-tint:none;
--text:#451a03; --text2:#92400e;
}
[data-theme="ice"] {
--bg:#f0f9ff; --panel:#e0f2fe; --board-bg:#f0f9ff;
--accent:#0369a1; --green:#0891b2; --red:#dc2626; --yellow:#d97706; --muted:#0284c7; --orange:#0284c7;
--bdr:#7dd3fc; --bdr-in:#bae6fd; --bdr2:#38bdf8;
--ctrl:#e0f2fe;
--board-tint:none;
--text:#0c4a6e; --text2:#0369a1;
}
* { margin:0; padding:0; box-sizing:border-box; }
body { background:var(--bg); color:var(--text); font-family:'IBM Plex Mono','Noto Sans KR',monospace; height:100vh; display:flex; flex-direction:column; overflow:hidden; }
/* Header */
.header { display:flex; align-items:center; gap:12px; padding:10px 16px; background:var(--panel); border-bottom:1px solid var(--bdr); flex-shrink:0; }
.header h1 { font-size:14px; font-weight:600; color:var(--text); }
.header .file { font-size:12px; color:var(--accent); }
.status { font-size:11px; padding:2px 8px; border-radius:4px; font-weight:600; }
.status.loading { background:#1e3a5f; color:#60a5fa; }
.status.ready { background:#1a3a2a; color:var(--green); }
.status.running { background:#3a3a1a; color:var(--yellow); animation: pulse 1.5s infinite; }
.status.error { background:#3a1a1a; color:var(--red); }
.status.stopped { background:#2a2a2a; color:var(--muted); }
@keyframes pulse { 0%,100% { opacity:1 } 50% { opacity:.6 } }
.toolbar { display:flex; gap:8px; margin-left:auto; align-items:center; }
.toolbar button { background:var(--ctrl); border:1px solid var(--bdr2); color:var(--text); border-radius:6px; padding:6px 14px; font-size:12px; cursor:pointer; font-family:inherit; transition:all .15s; }
.toolbar button:hover { filter:brightness(0.92); }
.toolbar button:disabled { opacity:.4; cursor:not-allowed; }
/* 보드 SVG 테마 틴트 */
.board-svg svg { filter:var(--board-tint); transition:filter .4s; }
/* SVG 보드 인터랙션 */
#power-switch { cursor:pointer; }
#power-switch:hover #pw-bg { stroke:#5a5a9a; }
#reset-btn { cursor:pointer; }
#reset-btn:hover circle { stroke:#8888cc; fill:#3a3a7a; }
/* 테마 닷 */
.theme-picker { display:flex; gap:4px; align-items:center; margin-left:4px; }
.tdot { width:13px; height:13px; border-radius:50%; background:var(--c,#888); border:2px solid transparent; cursor:pointer; padding:0; flex-shrink:0; transition:transform .12s, border-color .12s; }
.tdot:hover { transform:scale(1.3); }
.tdot.active { border-color:#fff; }
.speed-label { font-size:11px; color:var(--muted); }
select { background:var(--ctrl); border:1px solid var(--bdr2); color:var(--text); border-radius:6px; padding:5px 10px; font-size:12px; font-family:inherit; cursor:pointer; }
/* Main layout */
.main { flex:1; display:flex; gap:0; overflow:hidden; }
/* Editor open: board | serial | drag | code — serial & code share space */
.main.editor-open {
display: flex;
}
.main.editor-open .board-panel { flex:none; }
.main.editor-open .serial-panel { flex:1; min-width:120px; }
.main.editor-open #code-drag { flex-shrink:0; display:block; }
.main.editor-open #code-editor-panel { flex:1; min-width:120px; }
/* When BOTH editor and sch-panel are open: three equal columns */
.main.editor-open .sch-panel.open { flex:1; min-width:120px; }
/* Board panel — resizable */
.board-panel { width:220px; min-width:140px; max-width:480px; flex-shrink:0; background:var(--board-bg); border-right:1px solid var(--bdr); display:flex; flex-direction:column; overflow-y:auto; overflow-x:hidden; resize:horizontal; }
.board-title { padding:10px 14px; font-size:11px; color:var(--muted); font-weight:600; letter-spacing:.08em; border-bottom:1px solid var(--bdr-in); flex-shrink:0; }
.board-svg { padding:10px 14px; flex-shrink:0; }
.board-svg svg { width:100%; }
/* LCD display */
.lcd-section { padding:10px 14px; flex-shrink:0; border-bottom:1px solid var(--bdr-in); }
.lcd-section-title { font-size:10px; color:var(--muted); margin-bottom:8px; font-weight:600; letter-spacing:.08em; display:flex; align-items:center; gap:6px; }
.lcd-wrap { background:#1a2a0a; border:1px solid #2a4a1a; border-radius:5px; padding:8px 10px; display:inline-block; }
.lcd-row { display:flex; gap:2px; margin-bottom:2px; }
.lcd-row:last-child { margin-bottom:0; }
.lcd-cell { width:9px; height:14px; background:#0d1a05; border-radius:1px; display:flex; align-items:center; justify-content:center; font-size:9px; color:#6aff20; line-height:1; font-family:'IBM Plex Mono',monospace; }
.lcd-cell.lit { background:#1a3a08; }
.lcd-off .lcd-cell { color:#0d1a05; background:#0d1a05; }
.lcd-off .lcd-wrap { filter:brightness(.4); }
/* SSD1306 OLED */
.oled-section { padding:10px 14px; flex-shrink:0; border-bottom:1px solid var(--bdr-in); }
.oled-section-title { font-size:10px; color:var(--muted); margin-bottom:6px; font-weight:600; letter-spacing:.08em; display:flex; align-items:center; gap:6px; }
.oled-dot { width:6px; height:6px; border-radius:50%; background:#333; flex-shrink:0; }
.oled-dot.on { background:#60a5fa; box-shadow:0 0 4px #60a5fa; }
.oled-wrap { background:#000; border:2px solid #222; border-radius:4px; padding:4px; position:relative; }
.oled-placeholder { position:absolute; inset:4px; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px; pointer-events:none; }
.oled-placeholder span { font-size:10px; color:#333; letter-spacing:.05em; }
.oled-placeholder .oled-ph-grid { width:100%; height:2px; background:repeating-linear-gradient(90deg,#1a1a1a 0,#1a1a1a 4px,transparent 4px,transparent 8px); }
/* TFT LCD display */
.tft-section { padding:10px 14px; flex-shrink:0; border-bottom:1px solid var(--bdr-in); }
.tft-section-title { font-size:10px; color:var(--muted); margin-bottom:6px; font-weight:600; letter-spacing:.08em; display:flex; align-items:center; gap:6px; }
.tft-dot { width:6px; height:6px; border-radius:50%; background:#333; flex-shrink:0; }
.tft-dot.on { background:#22c55e; box-shadow:0 0 4px #22c55e; }
.tft-wrap { background:#000; border:2px solid #222; border-radius:4px; padding:4px; overflow:hidden; }
#tft-canvas { display:block; width:100%; height:auto; image-rendering:pixelated; cursor:zoom-in; }
#oled-canvas { display:block; max-width:100%; height:auto; image-rendering:pixelated; cursor:zoom-in; }
/* 확대 오버레이 */
#canvas-zoom-overlay {
display:none; position:fixed; inset:0; z-index:9999;
background:rgba(0,0,0,.85); align-items:center; justify-content:center;
cursor:zoom-out;
}
#canvas-zoom-overlay.open { display:flex; }
#canvas-zoom-img {
image-rendering:pixelated;
border:2px solid #333; border-radius:4px;
box-shadow:0 8px 40px rgba(0,0,0,.8);
}
#canvas-zoom-hint {
position:fixed; bottom:24px; left:50%; transform:translateX(-50%);
font-size:11px; color:#555; pointer-events:none;
}
/* DHT sensor controls */
.dht-section { padding:10px 14px; flex-shrink:0; border-bottom:1px solid var(--bdr-in); }
.dht-section-title { font-size:10px; color:var(--muted); margin-bottom:6px; font-weight:600; letter-spacing:.08em; display:flex; align-items:center; gap:6px; }
.dht-dot { width:6px; height:6px; border-radius:50%; background:#333; flex-shrink:0; }
.dht-dot.on { background:#0891b2; box-shadow:0 0 4px #0891b2; }
.dht-controls { display:flex; flex-direction:column; gap:4px; }
.dht-row { display:flex; align-items:center; gap:6px; font-size:11px; }
.dht-param { width:28px; color:var(--muted); flex-shrink:0; }
.dht-row input[type=range] { flex:1; height:4px; accent-color:var(--accent); }
.dht-val { width:42px; text-align:right; color:var(--accent); font-weight:600; font-size:10px; }
/* Servo display */
.servo-row { display:flex; align-items:center; gap:8px; font-size:11px; margin-bottom:4px; }
.servo-pin-label { width:36px; color:var(--muted); flex-shrink:0; }
.servo-arc-canvas { flex-shrink:0; }
.servo-angle-val { width:38px; text-align:right; color:#dc2626; font-weight:600; font-size:10px; }
/* Sensor/actuator panels (IMU, BME280, DS18B20, Relay, Motor) */
.sensor-section { padding:10px 12px; border-bottom:1px solid var(--bdr-in); display:none; }
.sensor-section-title { display:flex; align-items:center; gap:6px; font-size:11px; font-weight:600; color:#e5e7eb; margin-bottom:8px; }
.sensor-dot { width:8px; height:8px; border-radius:50%; }
.sensor-row { display:flex; align-items:center; gap:8px; font-size:11px; margin-bottom:5px; }
.sensor-param { width:48px; color:var(--muted); flex-shrink:0; }
.sensor-row input[type=range] { flex:1; height:4px; accent-color:var(--accent); }
.sensor-val { width:48px; text-align:right; color:var(--accent); font-weight:600; font-size:10px; }
.sensor-info { font-size:10px; color:var(--muted); margin-top:4px; }
.relay-grid { display:grid; grid-template-columns:repeat(2, 1fr); gap:6px; }
.relay-row { display:flex; align-items:center; gap:6px; font-size:11px; }
/* WiFi status panel */
.wifi-section { padding:10px 12px; border-bottom:1px solid var(--bdr-in); display:none; }
.wifi-section-title { display:flex; align-items:center; gap:6px; font-size:11px; font-weight:600; color:#e5e7eb; margin-bottom:8px; }
.wifi-dot { width:8px; height:8px; border-radius:50%; background:#333; }
.wifi-dot.on { background:#22c55e; box-shadow:0 0 4px #22c55e; }
.wifi-row { display:flex; align-items:center; gap:6px; font-size:11px; margin-bottom:4px; }
.wifi-label { width:46px; color:var(--muted); flex-shrink:0; }
.wifi-val { flex:1; text-align:right; color:var(--text); }
.wifi-btn { flex:1; padding:4px 6px; border:1px solid var(--bdr2); background:var(--panel); color:var(--text); border-radius:4px; cursor:pointer; font-size:10px; }
.wifi-btn.on { background:#2563eb; border-color:#2563eb; color:#fff; }
.wifi-input { flex:1; background:var(--panel); border:1px solid var(--bdr2); color:var(--text); border-radius:4px; padding:4px 6px; font-size:10px; font-family:inherit; }
.relay-btn { flex:1; padding:4px 6px; border:1px solid var(--bdr2); background:var(--panel); color:var(--text); border-radius:4px; cursor:pointer; font-size:10px; }
.relay-btn.on { background:#dc2626; border-color:#dc2626; color:#fff; }
.motor-channel { border:1px solid var(--bdr2); border-radius:6px; padding:6px; margin-bottom:6px; }
.motor-label { font-size:10px; color:var(--muted); margin-bottom:4px; }
.motor-dir-row { display:flex; gap:4px; margin-top:4px; }
.motor-dir-btn { flex:1; padding:3px 4px; border:1px solid var(--bdr2); background:var(--panel); color:var(--text); border-radius:4px; cursor:pointer; font-size:9px; }
.motor-dir-btn.active { background:#2563eb; border-color:#2563eb; color:#fff; }
/* Pin grid (shared by popup) */
.pins-header { font-size:10px; color:var(--muted); margin-bottom:6px; font-weight:600; letter-spacing:.08em; }
.pin-row { display:flex; align-items:center; gap:8px; padding:2px 0; font-size:11px; }
.pin-label { width:32px; color:var(--muted); flex-shrink:0; }
.pin-led { width:10px; height:10px; border-radius:50%; background:#2a2a3a; border:1px solid #3a3a5a; flex-shrink:0; transition:background .08s, box-shadow .08s; }
.pin-led.on { background:var(--green); box-shadow:0 0 5px var(--green); border-color:var(--green); }
.pin-led.pwm { background:var(--yellow); box-shadow:0 0 5px var(--yellow); border-color:var(--yellow); }
.pin-led.in { background:var(--accent); box-shadow:0 0 5px var(--accent); border-color:var(--accent); }
.pin-val { color:#9ca3af; font-size:10px; }
.pin-sep { height:1px; background:#1e1e36; margin:4px 0; }
/* Pin popup floating window */
.pin-popup { position:fixed; top:58px; right:18px; width:210px; max-height:72vh; background:var(--panel); border:1px solid var(--bdr2); border-radius:8px; display:none; flex-direction:column; z-index:50; box-shadow:0 6px 24px rgba(0,0,0,.5); }
.pin-popup.open { display:flex; }
.pin-popup-hdr { padding:7px 12px; font-size:11px; font-weight:600; color:#e5e7eb; background:var(--ctrl); border-bottom:1px solid var(--bdr); cursor:move; display:flex; align-items:center; user-select:none; flex-shrink:0; border-radius:8px 8px 0 0; }
.pin-popup-hdr button { margin-left:auto; background:none; border:none; color:var(--muted); cursor:pointer; font-size:14px; line-height:1; padding:0 2px; }
.pin-popup-hdr button:hover { color:#e5e7eb; }
.pin-popup-body { padding:10px 12px; overflow-y:auto; flex:1; }
/* Serial monitor */
.serial-panel { flex:1; display:flex; flex-direction:column; }
.serial-header { padding:10px 16px; font-size:11px; color:var(--muted); font-weight:600; letter-spacing:.08em; border-bottom:1px solid var(--bdr-in); display:flex; align-items:center; gap:12px; flex-shrink:0; }
.serial-header .baud { font-size:11px; color:#60a5fa; }
.serial-clear { margin-left:auto; background:none; border:none; color:var(--muted); cursor:pointer; font-size:11px; font-family:inherit; }
.serial-clear:hover { color:#e5e7eb; }
.serial-output { flex:1; overflow-y:auto; padding:12px 16px; font-size:13px; line-height:1.7; white-space:pre-wrap; word-break:break-all; }
.serial-output .line { color:var(--text); }
.serial-output .sys { color:var(--muted); font-style:italic; }
.serial-output .error { color:var(--red); }
.serial-input-row { display:flex; gap:8px; padding:10px 16px; border-top:1px solid var(--bdr-in); flex-shrink:0; }
.serial-input { flex:1; background:var(--panel); border:1px solid var(--bdr2); color:var(--text); border-radius:6px; padding:7px 12px; font-family:inherit; font-size:13px; }
.serial-input:focus { outline:none; border-color:var(--accent); }
.serial-send { background:#1a3a2a; border:1px solid #2d5a3a; color:var(--green); border-radius:6px; padding:7px 14px; font-family:inherit; font-size:12px; cursor:pointer; }
.serial-send:hover { background:#224a3a; }
/* Footer */
.footer { padding:7px 16px; border-top:1px solid var(--bdr); font-size:11px; color:var(--muted); background:var(--panel); flex-shrink:0; display:flex; justify-content:space-between; }
.footer .cycles { color:#60a5fa; }
/* Compile overlay */
.overlay { position:fixed; inset:0; background:rgba(0,0,0,.7); display:flex; align-items:center; justify-content:center; z-index:100; }
.overlay-box { background:var(--panel); border:1px solid var(--bdr2); border-radius:10px; padding:28px 36px; text-align:center; min-width:300px; }
.overlay-box h2 { font-size:15px; margin-bottom:12px; }
.overlay-box p { font-size:12px; color:var(--muted); margin-bottom:18px; white-space:pre-wrap; text-align:left; max-height:200px; overflow-y:auto; }
.overlay-box button { background:#1a3a2a; border:1px solid #2d5a3a; color:var(--green); border-radius:6px; padding:8px 20px; font-family:inherit; font-size:13px; cursor:pointer; }
.spinner { width:28px; height:28px; border:3px solid #3a3a5a; border-top-color:var(--accent); border-radius:50%; animation:spin .8s linear infinite; margin:0 auto 16px; }
@keyframes spin { to { transform:rotate(360deg) } }
/* ── Inline schematic panel ─────────────────────────────────────────────────── */
.bb-popup { display:none; } /* legacy stub, panel moved inline */
.sch-panel { flex:1; min-width:200px; display:none; flex-direction:column; border-left:1px solid var(--bdr); background:var(--panel); overflow:hidden; }
.sch-panel.open { display:flex; }
.sch-drag { width:5px; flex-shrink:0; cursor:col-resize; background:var(--bdr); transition:background .15s; }
.sch-drag:hover, .sch-drag.active { background:var(--accent); }
.bb-hdr { padding:6px 10px; font-size:11px; font-weight:600; color:var(--text); background:var(--ctrl); border-bottom:1px solid var(--bdr); display:flex; align-items:center; gap:6px; user-select:none; flex-shrink:0; }
.bb-hdr .bb-close { display:none; }
.sch-file-btn { background:var(--ctrl); border:1px solid var(--bdr2); color:var(--text); border-radius:4px; padding:3px 8px; font-size:10px; cursor:pointer; font-family:inherit; }
.sch-file-btn:hover { filter:brightness(.88); }
.bb-tabs-row { display:flex; gap:2px; margin-left:8px; }
.bb-tab { background:none; border:none; color:var(--muted); cursor:pointer; font-size:11px; padding:3px 10px; border-radius:4px; font-family:inherit; }
.bb-tab:hover { color:var(--text); }
.bb-tab.active { background:var(--bdr); color:var(--text); }
.bb-body { flex:1; display:flex; overflow:hidden; }
.bb-content { display:none; flex:1; flex-direction:column; overflow:hidden; }
.bb-content.active { display:flex; }
/* Schematic tab */
.sch-toolbar { display:flex; gap:4px; padding:7px 10px; flex-shrink:0; border-bottom:1px solid var(--bdr-in); flex-wrap:wrap; align-items:center; }
.sch-tool { background:var(--ctrl); border:1px solid var(--bdr2); color:var(--text); border-radius:5px; padding:4px 9px; font-size:11px; cursor:pointer; font-family:inherit; transition:all .12s; }
.sch-tool:hover { filter:brightness(0.92); box-shadow:0 1px 4px rgba(0,0,0,.12); }
.sch-tool.active { background:var(--accent); color:#fff; border-color:var(--accent); }
.sch-canvas-wrap { flex:1; overflow:hidden; background:#fff; position:relative; }
#sch-canvas { cursor:crosshair; display:block; }
.sch-hint { font-size:10px; color:var(--muted); padding:2px 6px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:260px; }
/* SPICE tab */
.spice-ctrl { padding:8px 10px; display:flex; gap:8px; align-items:center; flex-shrink:0; border-bottom:1px solid var(--bdr-in); }
.spice-ctrl button { background:var(--ctrl); border:1px solid var(--bdr2); color:var(--text); border-radius:5px; padding:4px 10px; font-size:11px; cursor:pointer; font-family:inherit; }
.spice-ctrl button:hover { filter:brightness(0.92); }
.spice-split { flex:1; display:flex; overflow:hidden; }
.spice-netlist { flex:1; background:#f8fafc; border:none; color:#1e40af; font-family:'IBM Plex Mono',monospace; font-size:11px; padding:10px; resize:none; outline:none; border-right:1px solid var(--bdr-in); }
.spice-results { width:270px; flex-shrink:0; overflow-y:auto; padding:10px; font-size:11px; }
.spice-node { display:flex; justify-content:space-between; padding:3px 0; border-bottom:1px solid var(--bdr-in); }
.spice-node-name { color:var(--muted); font-size:10px; }
.spice-node-val { color:var(--accent); font-weight:600; }
.spice-section { font-size:10px; font-weight:600; color:var(--accent); margin:10px 0 4px; letter-spacing:.05em; }
/* Parts list tab */
.parts-list { flex:1; overflow-y:auto; padding:8px; display:flex; flex-direction:column; gap:4px; }
.parts-cat { font-size:10px; font-weight:700; color:var(--accent); letter-spacing:.08em; padding:8px 4px 4px; border-bottom:1px solid var(--bdr-in); margin-bottom:2px; }
.parts-item { display:flex; align-items:center; gap:8px; padding:5px 8px; border-radius:5px; cursor:pointer; border:1px solid transparent; transition:all .12s; }
.parts-item:hover { background:var(--ctrl); border-color:var(--bdr2); }
.parts-item.selected { background:var(--accent); color:#fff; border-color:var(--accent); }
.parts-item.selected .parts-desc { color:rgba(255,255,255,.8); }
.parts-icon { font-size:18px; width:28px; text-align:center; flex-shrink:0; }
.parts-info { flex:1; min-width:0; }
.parts-name { font-size:11px; font-weight:600; color:var(--text); }
.parts-item.selected .parts-name { color:#fff; }
.parts-desc { font-size:10px; color:var(--muted); margin-top:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.parts-shortcut { font-size:9px; background:var(--bdr-in); color:var(--muted); border-radius:3px; padding:1px 4px; flex-shrink:0; }
/* Code editor panel */
.code-editor-panel { display:flex; flex-direction:column; background:var(--panel); border-left:1px solid var(--bdr2); flex:1; min-width:200px; }
.code-editor-header { display:flex; align-items:center; gap:8px; padding:6px 12px; background:var(--ctrl); border-bottom:1px solid var(--bdr); font-size:12px; font-weight:600; color:var(--text2); flex-shrink:0; }
.code-editor-filename { font-family:'IBM Plex Mono',monospace; font-size:11px; color:var(--accent); flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.code-editor-close { background:none; border:none; color:var(--muted); font-size:16px; cursor:pointer; padding:2px 6px; border-radius:4px; }
.code-editor-close:hover { background:var(--bdr-in); color:var(--text); }
.code-editor-textarea { flex:1; width:100%; resize:none; font-family:'IBM Plex Mono',monospace; font-size:12px; line-height:1.5; padding:10px 12px; border:none; outline:none; background:var(--bg); color:var(--text); tab-size:4; -moz-tab-size:4; }
.code-editor-textarea:focus { background:color-mix(in srgb, var(--bg) 97%, var(--accent)); }
.code-editor-status { padding:3px 12px; font-size:10px; color:var(--muted); background:var(--ctrl); border-top:1px solid var(--bdr); flex-shrink:0; }
#btn-code.active { background:var(--accent); color:#fff; border-color:var(--accent); }
#btn-compile { background:#1a3a2a; color:var(--green); border:1px solid #2d5a3a; }
#btn-compile:hover { background:#245a3a; }
#btn-compile:disabled { opacity:.5; cursor:wait; }
#btn-stop { background:#3a1a1a; color:#ef4444; border:1px solid #5a2d2d; }
#btn-stop:hover:not(:disabled) { background:#4a2020; }
#btn-reset { background:#1a2a3a; color:#67e8f9; border:1px solid #1a4a6a; }
#btn-reset:hover:not(:disabled) { background:#1f3a4a; }
/* CodeMirror 스케치 에디터 */
.CodeMirror { height:100% !important; font-family:'IBM Plex Mono',monospace !important; font-size:12px !important; line-height:1.5 !important; border:none !important; background:var(--bg) !important; color:var(--text) !important; }
.CodeMirror-scroll { height:100% !important; }
.CodeMirror-gutters { background:var(--board-bg) !important; border-right:1px solid var(--bdr-in) !important; }
.CodeMirror-linenumber { color:var(--muted) !important; padding:0 6px !important; }
.CodeMirror-cursor { border-left-color:var(--accent) !important; }
.CodeMirror-selected { background:rgba(37,99,235,.15) !important; }
.cm-s-default .cm-keyword { color:var(--accent) !important; }
.cm-s-default .cm-string { color:var(--green) !important; }
.cm-s-default .cm-number { color:var(--orange) !important; }
.cm-s-default .cm-comment { color:var(--muted) !important; font-style:italic !important; }
.cm-s-default .cm-def { color:var(--yellow) !important; }
/* 속도 슬라이더 */
#speed-range { width:70px; height:4px; accent-color:var(--accent); vertical-align:middle; cursor:pointer; }
/* ── mob-tab (inline since styles.css not loaded) ── */
.mob-tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;border:none;background:transparent;color:var(--muted);font-family:inherit;font-size:10px;font-weight:700;letter-spacing:.04em;cursor:pointer;padding:6px 4px;border-top:2px solid transparent;transition:color .15s,border-color .15s;-webkit-tap-highlight-color:transparent;}
.mob-tab.active{color:var(--accent);border-top-color:var(--accent);}
/* ── Mobile ──
주의: 이 페이지는 코드 에디터 안 iframe으로도 뜬다(code.js `_emuPanelLoad`).
미디어 쿼리는 창이 아니라 iframe 폭으로 평가되므로, 브레이크포인트가 크면
에디터 사이드 패널에서도 모바일 서랍 레이아웃이 나온다. 보드 220px +
시리얼 120px면 충분히 나란히 들어가므로 760px로 잡음.
JS 쪽 판정(mobToggleBoard)은 matchMedia로 이 값을 그대로 읽는다. */
@media(max-width:760px){
.header{flex-wrap:wrap;padding:7px 10px;gap:5px;}
.header h1{font-size:13px;}
.toolbar{gap:4px;}
.board-panel{
position:fixed;top:0;left:0;bottom:52px;z-index:500;
width:84vw;max-width:300px;
transform:translateX(-100%);transition:transform .25s ease;
box-shadow:4px 0 24px rgba(0,0,0,.5);
resize:none;
}
.board-panel.mob-open{transform:translateX(0);}
#mob-backdrop{display:none;position:fixed;inset:0 0 52px 0;background:rgba(0,0,0,.55);z-index:499;}
#mob-backdrop.show{display:block;}
#mob-tabbar{display:flex !important;position:fixed;bottom:0;left:0;right:0;z-index:600;background:var(--panel);border-top:1px solid var(--bdr);height:52px;}
.main{padding-bottom:52px;}
input,select{font-size:16px !important;}
}
@media(min-width:761px){
#mob-backdrop{display:none !important;}
#mob-tabbar{display:none !important;}
}
</style>
<link rel="stylesheet" href="/vendor/codemirror/lib/codemirror.css">
<script src="/vendor/codemirror/lib/codemirror.js"></script>
<script src="/vendor/codemirror/mode/clike/clike.js"></script>
</head>
<body>
<div id="mob-backdrop" onclick="mobCloseBoard()"></div>
<div id="mob-tabbar" style="display:none">
<button class="mob-tab" id="mobtab-board" onclick="mobToggleBoard()"><span style="font-size:18px">🔌</span>보드</button>
<button class="mob-tab active" id="mobtab-serial" onclick="mobCloseBoard()"><span style="font-size:18px">📟</span>시리얼</button>
</div>
<div class="header">
<h1>🔌 Arduino Emulator</h1>
<span class="file" id="filename">sketch.ino</span>
<span class="status loading" id="status">컴파일 중...</span>
<div class="toolbar">
<button onclick="toggleCodeEditor()" id="btn-code" title="코드 에디터">📝 코드</button>
<button onclick="compileAndRun()" id="btn-compile" title="컴파일 &amp; 실행 (Ctrl+Enter)">▶ 컴파일</button>
<button onclick="togglePower()" id="btn-stop" title="정지 / 시작" disabled>▶ 시작</button>
<button onclick="resetEmulator()" id="btn-reset" title="에뮬레이션 재시작 (리셋)" disabled>↺ 재시작</button>
<span id="rp2040-led" title="GPIO25 (내장 LED)" style="display:none;width:12px;height:12px;border-radius:50%;background:#1a2e1a;border:1px solid #2d4a2d;margin:0 2px;flex-shrink:0"></span>
<button onclick="toggleBB()" id="btn-bb" title="브레드보드 &amp; SPICE 시뮬레이터">🔌 회로</button>
<div class="theme-picker" title="테마">
<button class="tdot active" id="td-light" onclick="setTheme('light')" style="--c:#e5e7eb;border:1px solid #94a3b8" title="라이트"></button>
<button class="tdot" id="td-cosmic" onclick="setTheme('cosmic')" style="--c:#3b82f6" title="Cosmic"></button>
<button class="tdot" id="td-matrix" onclick="setTheme('matrix')" style="--c:#22c55e" title="Matrix"></button>
<button class="tdot" id="td-amber" onclick="setTheme('amber')" style="--c:#f59e0b" title="Amber"></button>
<button class="tdot" id="td-ice" onclick="setTheme('ice')" style="--c:#06b6d4" title="Ice"></button>
</div>
<span class="speed-label">속도 <span id="speed-val-label">1x</span></span>
<input type="range" id="speed-range" min="0" max="100" value="0" oninput="setSpeedSlider(this.value)" title="에뮬레이션 속도 (1x ~ 최대)">
</div>
</div>
<div class="main">
<!-- Board / Pin panel -->
<div class="board-panel">
<div class="board-title">ARDUINO UNO · ATmega328P</div>
<div class="board-svg">
<svg viewBox="0 0 80 140" xmlns="http://www.w3.org/2000/svg">
<!-- PCB body -->
<rect x="1" y="1" width="78" height="138" rx="5" fill="#1a6030" stroke="#3a9050" stroke-width="1.5"/>
<!-- Digital pin header (top) -->
<text x="30" y="8.5" text-anchor="middle" fill="#a0d8a0" font-size="4" font-family="monospace">DIGITAL</text>
<rect x="2" y="10" width="56" height="4.5" rx="1" fill="#0d3018" stroke="#3a7a3a" stroke-width="0.5"/>
<!-- D0-D13 pins - 원형 핀 헤더 자체가 LED 로 점등 -->
<circle id="board-d0-led" cx="4" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-d1-led" cx="7.9" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-d2-led" cx="11.8" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-d3-led" cx="15.7" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-d4-led" cx="19.6" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-d5-led" cx="23.5" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-d6-led" cx="27.4" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-d7-led" cx="31.3" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-d8-led" cx="35.2" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-d9-led" cx="39.1" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-d10-led" cx="43" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-d11-led" cx="46.9" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-d12-led" cx="50.8" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-d13-led" cx="54.7" cy="12.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<!-- Reset button (clickable) -->
<g id="reset-btn" onclick="resetEmulator()">
<circle cx="68" cy="14" r="4.5" fill="#cc3030" stroke="#ff6060" stroke-width="0.8"/>
<text x="68" y="22" text-anchor="middle" fill="#ffaaaa" font-size="3.2" font-family="monospace">RST</text>
</g>
<!-- Crystal 16MHz -->
<rect x="6" y="30" width="7.5" height="15" rx="2.5" fill="#d0c890" stroke="#e8e080" stroke-width="0.5"/>
<line x1="9.75" y1="30" x2="9.75" y2="45" stroke="#b0a870" stroke-width="0.5"/>
<!-- ATmega328P chip -->
<rect x="18" y="28" width="44" height="46" rx="2.5" fill="#1a1a1a" stroke="#666" stroke-width="1"/>
<!-- chip pin stubs left -->
<line x1="18" y1="33" x2="14" y2="33" stroke="#c0c090" stroke-width="1.2"/>
<line x1="18" y1="38" x2="14" y2="38" stroke="#c0c090" stroke-width="1.2"/>
<line x1="18" y1="43" x2="14" y2="43" stroke="#c0c090" stroke-width="1.2"/>
<line x1="18" y1="48" x2="14" y2="48" stroke="#c0c090" stroke-width="1.2"/>
<line x1="18" y1="53" x2="14" y2="53" stroke="#c0c090" stroke-width="1.2"/>
<line x1="18" y1="58" x2="14" y2="58" stroke="#c0c090" stroke-width="1.2"/>
<line x1="18" y1="63" x2="14" y2="63" stroke="#c0c090" stroke-width="1.2"/>
<!-- chip pin stubs right -->
<line x1="62" y1="33" x2="66" y2="33" stroke="#c0c090" stroke-width="1.2"/>
<line x1="62" y1="38" x2="66" y2="38" stroke="#c0c090" stroke-width="1.2"/>
<line x1="62" y1="43" x2="66" y2="43" stroke="#c0c090" stroke-width="1.2"/>
<line x1="62" y1="48" x2="66" y2="48" stroke="#c0c090" stroke-width="1.2"/>
<line x1="62" y1="53" x2="66" y2="53" stroke="#c0c090" stroke-width="1.2"/>
<line x1="62" y1="58" x2="66" y2="58" stroke="#c0c090" stroke-width="1.2"/>
<line x1="62" y1="63" x2="66" y2="63" stroke="#c0c090" stroke-width="1.2"/>
<!-- notch on chip -->
<ellipse cx="40" cy="28" rx="3" ry="1.5" fill="#333"/>
<text x="40" y="48" text-anchor="middle" fill="#bbb" font-size="5" font-family="monospace">ATmega</text>
<text x="40" y="55" text-anchor="middle" fill="#bbb" font-size="4.5" font-family="monospace">328P</text>
<!-- PWR LED (실행 중 점등) -->
<circle id="board-pwr-led" cx="68" cy="36" r="2.8" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.8"/>
<text x="68" y="32.5" text-anchor="middle" fill="#80cc80" font-size="3" font-family="monospace">PWR</text>
<!-- L LED (D13) -->
<circle id="board-led-13" cx="68" cy="50" r="2.8" fill="#2a1a1a" stroke="#553333" stroke-width="0.8"/>
<text x="68" y="46.5" text-anchor="middle" fill="#cc9090" font-size="3" font-family="monospace">L</text>
<!-- ICSP header -->
<rect x="60" y="80" width="15" height="10" rx="1" fill="#0d1a0d" stroke="#3a6a3a" stroke-width="0.5"/>
<circle cx="63" cy="83" r="0.9" fill="#c0c090"/><circle cx="67" cy="83" r="0.9" fill="#c0c090"/><circle cx="71" cy="83" r="0.9" fill="#c0c090"/>
<circle cx="63" cy="87" r="0.9" fill="#c0c090"/><circle cx="67" cy="87" r="0.9" fill="#c0c090"/><circle cx="71" cy="87" r="0.9" fill="#c0c090"/>
<text x="67.5" y="94" text-anchor="middle" fill="#80c880" font-size="3" font-family="monospace">ICSP</text>
<!-- Analog / Power header (bottom) -->
<text x="26" y="111" text-anchor="middle" fill="#a0d8a0" font-size="3.5" font-family="monospace">ANALOG / POWER</text>
<rect x="2" y="112" width="56" height="4.5" rx="1" fill="#0d1820" stroke="#3a5a7a" stroke-width="0.5"/>
<!-- A0-A5 pins - 원형 핀 헤더 자체가 LED 로 점등 (A6=5V, A7=GND 는 제외) -->
<circle id="board-a0-led" cx="4" cy="114.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-a1-led" cx="10.7" cy="114.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-a2-led" cx="17.4" cy="114.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-a3-led" cx="24.1" cy="114.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-a4-led" cx="30.8" cy="114.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<circle id="board-a5-led" cx="37.5" cy="114.2" r="1.6" fill="#0d2a0d" stroke="#1a5a1a" stroke-width="0.4"/>
<!-- A6=5V, A7=GND (전원 핀) -->
<circle cx="44.2" cy="114.2" r="1.6" fill="#c0c090" stroke="#e0e0a0" stroke-width="0.4"/>
<circle cx="50.9" cy="114.2" r="1.6" fill="#c0c090" stroke="#e0e0a0" stroke-width="0.4"/>
<!-- Power button (원형 파워 버튼, ICSP 하단) -->
<g id="power-switch" onclick="togglePower()">
<circle id="pw-bg" cx="68" cy="103" r="7.5" fill="#1a0808" stroke="#803030" stroke-width="1.2"/>
<circle cx="68" cy="103" r="5.5" fill="none" id="pw-ring" stroke="#602020" stroke-width="0.8"/>
<path id="pw-arc" d="M 64.8 100.5 A 4 4 0 1 0 71.2 100.5"
stroke="#c0404088" stroke-width="1.6" fill="none" stroke-linecap="round"/>
<line id="pw-line" x1="68" y1="98.5" x2="68" y2="102.5"
stroke="#c0404088" stroke-width="1.6" stroke-linecap="round"/>
<circle cx="68" cy="103" r="7.5" fill="transparent" stroke="transparent"/>
</g>
<!-- USB-B connector -->
<rect x="27" y="126" width="26" height="13" rx="2" fill="#999" stroke="#bbb" stroke-width="0.5"/>
<rect x="30" y="128" width="20" height="9" rx="1.5" fill="#666"/>
<!-- Power barrel jack -->
<ellipse cx="7" cy="131" rx="5.5" ry="7" fill="#444" stroke="#777" stroke-width="0.8"/>
<circle cx="7" cy="131" r="2.5" fill="#222"/>
</svg>
</div>
<!-- SSD1306 OLED -->
<div class="oled-section" id="oled-section" style="display:none">
<div class="oled-section-title">
<span class="oled-dot" id="oled-dot"></span>
SSD1306 OLED 128×64
</div>
<div class="oled-wrap">
<canvas id="oled-canvas" width="256" height="128"></canvas>
</div>
</div>
<!-- TFT LCD display -->
<div class="tft-section" id="tft-section" style="display:none">
<div class="tft-section-title">
<span class="tft-dot" id="tft-dot"></span>
<span id="tft-label">TFT LCD</span>
</div>
<div class="tft-wrap">
<canvas id="tft-canvas" width="128" height="160"></canvas>
</div>
</div>
<!-- LCD display -->
<div class="lcd-section" id="lcd-section" style="display:none">
<div class="lcd-section-title">LCD <span id="lcd-size-label" style="color:#60a5fa"></span> <span id="lcd-backlight-dot" style="font-size:8px">●</span></div>
<div class="lcd-wrap" id="lcd-wrap">
<div id="lcd-display"></div>
</div>
</div>
<!-- Servo motor display -->
<div class="dht-section" id="servo-section" style="display:none">
<div class="dht-section-title">
<span class="dht-dot" style="background:#dc2626"></span>
<span>서보 모터</span>
</div>
<div id="servo-rows"></div>
</div>
<!-- DHT sensor controls -->
<div class="dht-section" id="dht-section" style="display:none">
<div class="dht-section-title">
<span class="dht-dot" id="dht-dot"></span>
<span id="dht-label">DHT 온습도</span>
</div>
<div class="dht-controls">
<div class="dht-row">
<span class="dht-param">온도</span>
<input type="range" id="dht-temp" min="-40" max="80" value="25" oninput="updateDHTValues()">
<span class="dht-val" id="dht-temp-val">25°C</span>
</div>
<div class="dht-row">
<span class="dht-param">습도</span>
<input type="range" id="dht-humid" min="0" max="100" value="50" oninput="updateDHTValues()">
<span class="dht-val" id="dht-humid-val">50%</span>
</div>
</div>
</div>
<!-- ADC / Analog Input controls (AVR analogRead) -->
<div class="dht-section" id="adc-section" style="display:none">
<div class="dht-section-title">
<span class="dht-dot" id="adc-dot" style="background:#8b5cf6"></span>
<span>아날로그 입력 (A0–A5)</span>
</div>
<div id="adc-controls"></div>
</div>
<!-- MPU-6050 IMU controls -->
<div class="sensor-section" id="imu-section" style="display:none">
<div class="sensor-section-title">
<span class="sensor-dot" style="background:#0891b2"></span>
<span>MPU-6050 IMU</span>
<span class="sensor-info" id="imu-info"></span>
</div>
<div class="sensor-row"><span class="sensor-param">Ax</span><input type="range" id="imu-ax" min="-2" max="2" step="0.01" value="0" oninput="updateIMUValues()"><span class="sensor-val" id="imu-ax-val">0g</span></div>
<div class="sensor-row"><span class="sensor-param">Ay</span><input type="range" id="imu-ay" min="-2" max="2" step="0.01" value="0" oninput="updateIMUValues()"><span class="sensor-val" id="imu-ay-val">0g</span></div>
<div class="sensor-row"><span class="sensor-param">Az</span><input type="range" id="imu-az" min="-2" max="2" step="0.01" value="1" oninput="updateIMUValues()"><span class="sensor-val" id="imu-az-val">1g</span></div>
<div class="sensor-row"><span class="sensor-param">Gx</span><input type="range" id="imu-gx" min="-250" max="250" step="1" value="0" oninput="updateIMUValues()"><span class="sensor-val" id="imu-gx-val">0°/s</span></div>
<div class="sensor-row"><span class="sensor-param">Gy</span><input type="range" id="imu-gy" min="-250" max="250" step="1" value="0" oninput="updateIMUValues()"><span class="sensor-val" id="imu-gy-val">0°/s</span></div>
<div class="sensor-row"><span class="sensor-param">Gz</span><input type="range" id="imu-gz" min="-250" max="250" step="1" value="0" oninput="updateIMUValues()"><span class="sensor-val" id="imu-gz-val">0°/s</span></div>
<div class="sensor-row"><span class="sensor-param">Temp</span><input type="range" id="imu-temp" min="-40" max="85" step="0.5" value="25" oninput="updateIMUValues()"><span class="sensor-val" id="imu-temp-val">25°C</span></div>
</div>
<!-- BME280 environmental controls -->
<div class="sensor-section" id="bme-section" style="display:none">
<div class="sensor-section-title">
<span class="sensor-dot" style="background:#16a34a"></span>
<span>BME280 환경</span>
<span class="sensor-info" id="bme-info"></span>
</div>
<div class="sensor-row"><span class="sensor-param">온도</span><input type="range" id="bme-t" min="-40" max="85" step="0.5" value="25" oninput="updateBMEValues()"><span class="sensor-val" id="bme-t-val">25°C</span></div>
<div class="sensor-row"><span class="sensor-param">습도</span><input type="range" id="bme-h" min="0" max="100" step="1" value="50" oninput="updateBMEValues()"><span class="sensor-val" id="bme-h-val">50%</span></div>
<div class="sensor-row"><span class="sensor-param">기압</span><input type="range" id="bme-p" min="300" max="1100" step="1" value="1013" oninput="updateBMEValues()"><span class="sensor-val" id="bme-p-val">1013hPa</span></div>
</div>
<!-- DS18B20 temperature controls -->
<div class="sensor-section" id="ds18b20-section" style="display:none">
<div class="sensor-section-title">
<span class="sensor-dot" style="background:#6d28d9"></span>
<span>DS18B20 온도</span>
<span class="sensor-info" id="ds18b20-info"></span>
</div>
<div class="sensor-row"><span class="sensor-param">온도</span><input type="range" id="ds18b20-t" min="-55" max="125" step="0.5" value="25" oninput="updateDS18B20Values()"><span class="sensor-val" id="ds18b20-t-val">25°C</span></div>
</div>
<!-- Relay module indicators -->
<div class="sensor-section" id="relay-section" style="display:none">
<div class="sensor-section-title">
<span class="sensor-dot" style="background:#d97706"></span>
<span>릴레이 모듈</span>
</div>
<div class="relay-grid" id="relay-rows"></div>
</div>
<!-- L298N motor controls -->
<div class="sensor-section" id="motor-section" style="display:none">
<div class="sensor-section-title">
<span class="sensor-dot" style="background:#a16207"></span>
<span>L298N 모터</span>
</div>
<div id="motor-rows"></div>
</div>
<!-- WiFi status panel -->
<div class="wifi-section" id="wifi-section" style="display:none">
<div class="wifi-section-title">
<span class="wifi-dot" id="wifi-dot"></span>
<span>WiFi</span>
</div>
<div class="wifi-row"><span class="wifi-label">상태</span><span class="wifi-val" id="wifi-status">연결 안 됨</span></div>
<div class="wifi-row"><span class="wifi-label">SSID</span><span class="wifi-val" id="wifi-ssid">-</span></div>
<div class="wifi-row"><span class="wifi-label">IP</span><span class="wifi-val" id="wifi-ip">-</span></div>
<div class="wifi-row"><span class="wifi-label">RSSI</span><span class="wifi-val" id="wifi-rssi">-</span></div>
<div class="wifi-row" id="wifi-http-row" style="display:none"><span class="wifi-label">HTTP</span><a id="wifi-http-link" class="wifi-val" href="#" target="_blank" style="color:#60a5fa;text-decoration:underline">-</a></div>
<div class="wifi-row" style="margin-top:8px">
<input class="wifi-input" id="wifi-ssid-input" placeholder="SSID" />
<input class="wifi-input" id="wifi-pass-input" type="password" placeholder="비밀번호" />
</div>
<div class="wifi-row">
<button class="wifi-btn" id="wifi-connect-btn" onclick="_wifiConnectClicked()">연결</button>
<button class="wifi-btn" onclick="_wifiDisconnectClicked()">끊기</button>
</div>
<!-- Scan network mock config -->
<div style="margin-top:8px;border-top:1px solid var(--bdr-in);padding-top:8px">
<div style="font-size:10px;color:var(--muted);font-weight:600;letter-spacing:.06em;margin-bottom:5px">SCAN 목록 (scanNetworks 모의)</div>
<div id="wifi-scan-list" style="display:flex;flex-direction:column;gap:3px;margin-bottom:5px"></div>
<div style="display:flex;gap:4px;margin-bottom:4px">
<button class="wifi-btn" onclick="_wifiScanAddNet()" style="flex:none;padding:2px 8px;font-size:10px">+ 추가</button>
<button class="wifi-btn" onclick="_wifiScanApply()" style="flex:none;padding:2px 8px;font-size:10px;background:var(--accent);border-color:var(--accent);color:#fff">적용</button>
</div>
</div>
</div>
</div>
<!-- Serial monitor -->
<div class="serial-panel">
<div class="serial-header">
SERIAL MONITOR
<span class="baud" id="baud-label">9600 baud</span>
<button class="serial-clear" onclick="clearSerial()">지우기</button>
</div>
<div class="serial-output" id="serial-output">
<span class="sys">에뮬레이터 준비 중...</span>
</div>
<div class="serial-input-row">
<input class="serial-input" id="serial-input" placeholder="직렬 입력 (Enter 전송)" onkeydown="if(event.key==='Enter')sendSerial()"/>
<button class="serial-send" onclick="sendSerial()">전송</button>
</div>
</div>
<!-- Code editor drag handle + panel -->
<div class="sch-drag" id="code-drag"></div>
<!-- Code editor panel (sketch editor, side by side with serial monitor) -->
<div class="code-editor-panel" id="code-editor-panel" style="display:none">
<div class="code-editor-header">
<span>📝 스케치 편집</span>
<span class="code-editor-filename" id="code-editor-filename">sketch.ino</span>
<button class="code-editor-close" onclick="schLoadSketchFile()" title="프로젝트 파일 열기" style="font-size:12px;padding:2px 8px">📂 파일</button>
<button class="code-editor-close" onclick="schSaveSketchFile()" title="현재 스케치를 파일에 저장" style="font-size:12px;padding:2px 8px">💾 저장</button>
<button class="code-editor-close" onclick="toggleCodeEditor()">✕</button>
</div>
<div id="cm-sketch-wrap" style="flex:1;overflow:hidden;min-height:0;position:relative"></div>
<textarea class="code-editor-textarea" id="code-editor-textarea" spellcheck="false" style="display:none"></textarea>
<div class="code-editor-status" id="code-editor-status"></div>
</div>
<!-- Drag handle + inline schematic panel -->
<div class="sch-drag" id="sch-drag"></div>
<div class="sch-panel" id="sch-panel">
<div class="bb-hdr" id="bb-hdr">
🔌 회로도
<div class="bb-tabs-row">
<button class="bb-tab active" id="bb-tab-btn-board" onclick="bbTab('board')">회로도</button>
<button class="bb-tab" id="bb-tab-btn-parts" onclick="bbTab('parts')">부품 목록</button>
<button class="bb-tab" id="bb-tab-btn-spice" onclick="bbTab('spice')">SPICE</button>
</div>
<span style="margin-left:auto;display:flex;gap:4px;align-items:center">
<button class="sch-file-btn" id="sch-gen-btn" onclick="schGenFromCode()" title="스케치 코드를 분석해서 회로도 자동 생성">⚡ 코드→회로</button>
<span id="sch-log" style="font-size:11px;color:#e8c860;white-space:nowrap"></span>
<button class="sch-file-btn" id="sch-gen-code-btn" onclick="schGenCodeFromCircuit()" title="회로도를 분석해서 Arduino 스케치 자동 생성">🤖 회로→코드</button>
<button class="sch-file-btn" onclick="schSaveProject()" title="circuit.json을 프로젝트 폴더에 저장">💾 저장</button>
<button class="sch-file-btn" onclick="schLoadLocal()" title="저장된 회로도 불러오기">📂 불러오기</button>
<button class="sch-file-btn" onclick="schLoadSketchFile()" title="프로젝트의 .ino 파일 열기">📝 스케치</button>
<button class="sch-file-btn" onclick="toggleSchPanel()" style="color:var(--muted)" title="닫기">✕</button>
</span>
</div>
<div class="bb-body" id="bb-body-inner">
<!-- SCHEMATIC TAB -->
<div class="bb-content active" id="bb-content-board">
<div class="sch-toolbar">
<button class="sch-tool" id="stool-select" onclick="schSetTool('select')" title="선택/이동 (V)">↖ 선택</button>
<button class="sch-tool active" id="stool-wire" onclick="schSetTool('wire')" title="와이어 (W)">— 와이어</button>
<button class="sch-tool" id="stool-res" onclick="schSetTool('res')" title="저항 (R)">⊡ R</button>
<button class="sch-tool" id="stool-cap" onclick="schSetTool('cap')" title="커패시터 (C)">⊢⊣ C</button>
<button class="sch-tool" id="stool-led" onclick="schSetTool('led')" title="LED">▷ LED</button>
<button class="sch-tool" id="stool-diode" onclick="schSetTool('diode')" title="다이오드">▷| D</button>
<button class="sch-tool" id="stool-bjt" onclick="schSetTool('bjt')" title="BJT 트랜지스터">⊙ BJT</button>
<button class="sch-tool" id="stool-btn" onclick="schSetTool('btn')" title="버튼">⊙ BTN</button>
<button class="sch-tool" id="stool-vcc" onclick="schSetTool('vcc')" title="+5V 전원" style="color:#dc2626">+VCC</button>
<button class="sch-tool" id="stool-gnd" onclick="schSetTool('gnd')" title="접지 GND" style="color:#2563eb">⏚ GND</button>
<button class="sch-tool" id="stool-ard" onclick="schSetTool('ard')" title="Arduino 핀" style="color:#ea580c">📌 핀</button>
<span style="margin-left:auto;display:flex;gap:6px;align-items:center">
<span class="sch-hint" id="sch-hint">와이어: 클릭→끝점 | R: 회전 | F: 좌우반전 | G: 상하반전 | Del: 삭제 | Esc: 취소 | 우클릭: 편집</span>
<button class="sch-tool" onclick="schRotateSelected()" title="선택 부품 회전 90° (R)">↻</button>
<button class="sch-tool" onclick="schFlipHSelected()" title="선택 부품 좌우 반전 (F)">↔</button>
<button class="sch-tool" onclick="schFlipVSelected()" title="선택 부품 상하 반전 (G)">↕</button>
<button class="sch-tool" onclick="schUndo()" title="되돌리기 (Ctrl+Z)">↩</button>
<button class="sch-tool" onclick="schExport()" title="JSON 내보내기">⬇</button>
<button class="sch-tool" onclick="schImport()" title="JSON 가져오기">⬆</button>
<button class="sch-tool" onclick="schClear()" style="color:var(--red)" title="초기화">🗑</button>
</span>
</div>
<div class="sch-canvas-wrap" id="sch-canvas-wrap">
<canvas id="sch-canvas"></canvas>
</div>
</div>
<!-- PARTS LIST TAB -->
<div class="bb-content" id="bb-content-parts">
<div class="parts-list" id="parts-list">
<div class="parts-cat">기본 수동 부품</div>
<div class="parts-item" onclick="bbSetTool('wire')"><span class="parts-icon">🔌</span><div class="parts-info"><div class="parts-name">와이어 (Wire)</div><div class="parts-desc">두 점을 연결하는 도선</div></div><span class="parts-shortcut">W</span></div>
<div class="parts-item" onclick="bbSetTool('res')"><span class="parts-icon">⊡</span><div class="parts-info"><div class="parts-name">저항 (Resistor)</div><div class="parts-desc">전류 제한. 220Ω, 1kΩ, 10kΩ 등</div></div><span class="parts-shortcut">R</span></div>
<div class="parts-item" onclick="bbSetTool('cap')"><span class="parts-icon">⊢⊣</span><div class="parts-info"><div class="parts-name">커패시터 (Capacitor)</div><div class="parts-desc">전하 저장. 100nF, 10µF 등</div></div><span class="parts-shortcut">C</span></div>
<div class="parts-cat">출력 부품</div>
<div class="parts-item" onclick="bbSetTool('led')"><span class="parts-icon">💡</span><div class="parts-info"><div class="parts-name">LED</div><div class="parts-desc">발광 다이오드</div></div><span class="parts-shortcut">L</span></div>
<div class="parts-item" onclick="bbSetTool('seg7')" id="tool-parts-seg7"><span class="parts-icon">🔢</span><div class="parts-info"><div class="parts-name">7-세그먼트 디스플레이</div><div class="parts-desc">숫자 표시용 LED 배열</div></div></div>
<div class="parts-item" onclick="bbSetTool('buzzer')" id="tool-parts-buzzer"><span class="parts-icon">🔔</span><div class="parts-info"><div class="parts-name">부저 (Buzzer)</div><div class="parts-desc">PWM 신호로 소리 출력</div></div></div>
<div class="parts-item" onclick="bbSetTool('rgb_led')" id="tool-parts-rgb"><span class="parts-icon">🌈</span><div class="parts-info"><div class="parts-name">RGB LED</div><div class="parts-desc">R/G/B 채널 독립 제어</div></div></div>
<div class="parts-item" onclick="bbSetTool('tft_lcd')" id="tool-parts-tft"><span class="parts-icon">🖥</span><div class="parts-info"><div class="parts-name">TFT 컬러 LCD</div><div class="parts-desc">ILI9341/ST7735/ST7789 SPI 디스플레이</div></div></div>
<div class="parts-cat">입력 부품</div>
<div class="parts-item" onclick="bbSetTool('btn')"><span class="parts-icon">⊙</span><div class="parts-info"><div class="parts-name">푸시버튼 (Push Button)</div><div class="parts-desc">누르는 동안 회로 연결</div></div><span class="parts-shortcut">B</span></div>
<div class="parts-item" onclick="bbSetTool('pot')" id="tool-parts-pot"><span class="parts-icon">🎛</span><div class="parts-info"><div class="parts-name">가변저항 (Potentiometer)</div><div class="parts-desc">0~5V 아날로그 전압 분배</div></div></div>
<div class="parts-item" onclick="bbSetTool('sw_toggle')" id="tool-parts-sw"><span class="parts-icon">🔀</span><div class="parts-info"><div class="parts-name">토글 스위치 (Toggle Switch)</div><div class="parts-desc">ON/OFF 유지형 스위치</div></div></div>
<div class="parts-item" onclick="bbSetTool('ldr')" id="tool-parts-ldr"><span class="parts-icon">☀️</span><div class="parts-info"><div class="parts-name">조도 센서 (LDR/CdS)</div><div class="parts-desc">빛에 따라 저항 변화</div></div></div>
<div class="parts-item" onclick="bbSetTool('ntc')" id="tool-parts-ntc"><span class="parts-icon">🌡</span><div class="parts-info"><div class="parts-name">NTC 서미스터</div><div class="parts-desc">온도에 따른 저항 변화</div></div></div>
<div class="parts-cat">반도체</div>
<div class="parts-item" onclick="bbSetTool('bjt')"><span class="parts-icon">🔺</span><div class="parts-info"><div class="parts-name">BJT 트랜지스터 (NPN/PNP)</div><div class="parts-desc">전류 증폭/스위칭</div></div><span class="parts-shortcut">T</span></div>
<div class="parts-item" onclick="bbSetTool('mosfet')" id="tool-parts-mosfet"><span class="parts-icon">📐</span><div class="parts-info"><div class="parts-name">MOSFET (N채널/P채널)</div><div class="parts-desc">전압 구동 스위칭</div></div></div>
<div class="parts-item" onclick="bbSetTool('diode')" id="tool-parts-diode"><span class="parts-icon">▷|</span><div class="parts-info"><div class="parts-name">다이오드 (1N4148/1N4007)</div><div class="parts-desc">단방향 전류</div></div></div>
<div class="parts-item" onclick="bbSetTool('zener')" id="tool-parts-zener"><span class="parts-icon">⚡</span><div class="parts-info"><div class="parts-name">제너 다이오드</div><div class="parts-desc">정전압 기준</div></div></div>
<div class="parts-cat">집적회로 (IC)</div>
<div class="parts-item" onclick="bbSetTool('ic_555')" id="tool-parts-555"><span class="parts-icon">🟥</span><div class="parts-info"><div class="parts-name">555 타이머 IC</div><div class="parts-desc">단안정/비안정 멀티바이브레이터</div></div></div>
<div class="parts-item" onclick="bbSetTool('ic_741')" id="tool-parts-741"><span class="parts-icon">🟦</span><div class="parts-info"><div class="parts-name">741 연산증폭기 (Op-Amp)</div><div class="parts-desc">증폭, 비교기, 필터</div></div></div>
<div class="parts-item" onclick="bbSetTool('ic_7400')" id="tool-parts-7400"><span class="parts-icon">🟩</span><div class="parts-info"><div class="parts-name">74 시리즈 로직 게이트</div><div class="parts-desc">AND/OR/NOT/NAND 등</div></div></div>
<div class="parts-item" onclick="bbSetTool('ic_shift')" id="tool-parts-shift"><span class="parts-icon">↔️</span><div class="parts-info"><div class="parts-name">74HC595 시프트 레지스터</div><div class="parts-desc">SPI 3선→8비트 병렬 출력</div></div></div>
<div class="parts-item" onclick="bbSetTool('ic_l293')" id="tool-parts-l293"><span class="parts-icon">🔄</span><div class="parts-info"><div class="parts-name">L293D 모터 드라이버</div><div class="parts-desc">DC모터 H브리지 구동</div></div></div>
<div class="parts-cat">센서 모듈</div>
<div class="parts-item" onclick="bbSetTool('dht')" id="tool-parts-dht"><span class="parts-icon">💧</span><div class="parts-info"><div class="parts-name">DHT11/DHT22 온습도 센서</div><div class="parts-desc">디지털 1-wire</div></div></div>
<div class="parts-item" onclick="bbSetTool('hcsr')" id="tool-parts-hcsr"><span class="parts-icon">📡</span><div class="parts-info"><div class="parts-name">HC-SR04 초음파 거리 센서</div><div class="parts-desc">Trig/Echo, 2~400cm</div></div></div>
<div class="parts-item" onclick="bbSetTool('pir')" id="tool-parts-pir"><span class="parts-icon">👁</span><div class="parts-info"><div class="parts-name">PIR 동작 감지 센서</div><div class="parts-desc">적외선 수동 감지</div></div></div>
<div class="parts-item" onclick="bbSetTool('ir')" id="tool-parts-ir"><span class="parts-icon">🔴</span><div class="parts-info"><div class="parts-name">IR 적외선 송수신</div><div class="parts-desc">리모컨 수신, 장애물 감지</div></div></div>
<div class="parts-item" onclick="bbSetTool('sw520')" id="tool-parts-sw520"><span class="parts-icon">🔵</span><div class="parts-info"><div class="parts-name">기울기 센서 (SW-520D)</div><div class="parts-desc">각도 감지</div></div></div>
<div class="parts-item" onclick="bbSetTool('servo')" id="tool-parts-servo"><span class="parts-icon">⚙</span><div class="parts-info"><div class="parts-name">서보 모터 (Servo)</div><div class="parts-desc">PWM 제어, SG90/MG996R</div></div></div>
<div class="parts-item" onclick="bbSetTool('relay')" id="tool-parts-relay"><span class="parts-icon">🔌</span><div class="parts-info"><div class="parts-name">릴레이 모듈</div><div class="parts-desc">5V 코일, NO/NC 스위칭</div></div></div>
<div class="parts-item" onclick="bbSetTool('ds18b20')" id="tool-parts-ds18"><span class="parts-icon">🌡</span><div class="parts-info"><div class="parts-name">DS18B20 온도 (1-Wire)</div><div class="parts-desc">-55~125°C, 방수형 가능</div></div></div>
<div class="parts-item" onclick="bbSetTool('mpu6050')" id="tool-parts-mpu"><span class="parts-icon">📐</span><div class="parts-info"><div class="parts-name">MPU-6050 자이로/가속도</div><div class="parts-desc">I2C, 6축 IMU (0x68)</div></div></div>
<div class="parts-item" onclick="bbSetTool('l298n')" id="tool-parts-l298n"><span class="parts-icon">🔄</span><div class="parts-info"><div class="parts-name">L298N 모터 드라이버</div><div class="parts-desc">DC모터 2채널, 2A×2</div></div></div>
<div class="parts-item" onclick="bbSetTool('bme280')" id="tool-parts-bme"><span class="parts-icon">🌤</span><div class="parts-info"><div class="parts-name">BME280 환경 센서</div><div class="parts-desc">I2C, 온도/습도/기압</div></div></div>
<div class="parts-cat">통신 모듈</div>
<div class="parts-item" onclick="bbSetTool('hc05')" id="tool-parts-hc05"><span class="parts-icon">📶</span><div class="parts-info"><div class="parts-name">HC-05/HC-06 블루투스</div><div class="parts-desc">UART 직렬 통신</div></div></div>
<div class="parts-item" onclick="bbSetTool('nrf')" id="tool-parts-nrf"><span class="parts-icon">📻</span><div class="parts-info"><div class="parts-name">nRF24L01 2.4GHz 무선</div><div class="parts-desc">SPI 인터페이스</div></div></div>
<div class="parts-item" onclick="bbSetTool('esp01')" id="tool-parts-esp01"><span class="parts-icon">🌐</span><div class="parts-info"><div class="parts-name">ESP8266 ESP-01 Wi-Fi</div><div class="parts-desc">UART AT 명령</div></div></div>
<div class="parts-cat">개발 보드</div>
<div class="parts-item" onclick="bbSetTool('board_esp32')" id="tool-parts-board32"><span class="parts-icon">🔲</span><div class="parts-info"><div class="parts-name">ESP32 DevKit</div><div class="parts-desc">ESP32 30핀 개발보드</div></div></div>
<div class="parts-item" onclick="bbSetTool('board_esp8266')" id="tool-parts-board8266"><span class="parts-icon">🔳</span><div class="parts-info"><div class="parts-name">ESP8266 개발보드</div><div class="parts-desc">NodeMCU / D1 Mini / ESP-01</div></div></div>
<div class="parts-item" onclick="bbSetTool('board_uno')" id="tool-parts-boarduno"><span class="parts-icon">🟧</span><div class="parts-info"><div class="parts-name">Arduino UNO R3</div><div class="parts-desc">ATmega328P · D0-D13, A0-A5</div></div></div>
<div class="parts-item" onclick="bbSetTool('board_nano')" id="tool-parts-boardnano"><span class="parts-icon">🟨</span><div class="parts-info"><div class="parts-name">Arduino Nano</div><div class="parts-desc">ATmega328P · D0-D13, A0-A7</div></div></div>
<div class="parts-item" onclick="bbSetTool('board_mega')" id="tool-parts-boardmega"><span class="parts-icon">🟦</span><div class="parts-info"><div class="parts-name">Arduino Mega 2560</div><div class="parts-desc">ATmega2560 · D0-D53, A0-A15</div></div></div>
<div class="parts-cat">전원 부품</div>
<div class="parts-item" onclick="bbSetTool('vcc')"><span class="parts-icon" style="color:#dc2626">+</span><div class="parts-info"><div class="parts-name">+5V 전원 (VCC)</div><div class="parts-desc">아두이노 5V 레일</div></div></div>
<div class="parts-item" onclick="bbSetTool('gnd')"><span class="parts-icon" style="color:#2563eb">⏚</span><div class="parts-info"><div class="parts-name">GND (접지)</div><div class="parts-desc">기준 전위 0V</div></div></div>
<div class="parts-item" onclick="bbSetTool('ard')"><span class="parts-icon" style="color:#ea580c">📌</span><div class="parts-info"><div class="parts-name">Arduino 핀 연결</div><div class="parts-desc">D0~D13, A0~A5, 5V, GND</div></div></div>
<div class="parts-item" onclick="bbSetTool('lm7805')" id="tool-parts-vreg"><span class="parts-icon">🔋</span><div class="parts-info"><div class="parts-name">LM7805 전압 레귤레이터</div><div class="parts-desc">7~35V→5V, 1A</div></div></div>
</div>
</div>
<!-- SPICE TAB -->
<div class="bb-content" id="bb-content-spice">
<div class="spice-ctrl">
<span style="font-size:11px;color:var(--muted)">SPICE 넷리스트</span>
<select id="spice-analysis" style="font-size:11px;background:var(--bg2);color:var(--text);border:1px solid var(--bdr);border-radius:4px;padding:2px 6px">
<option value=".op">.op 동작점</option>
<option value=".dc V1 0 5 0.1">.dc DC 스윕</option>
<option value=".tran 1us 1ms">.tran 과도해석</option>
<option value=".ac dec 10 1 1meg">.ac 주파수</option>
</select>
<button onclick="bbRunSpice()">▶ ngspice 실행</button>
<button onclick="bbExportNetlist()">⬇ .cir 내보내기</button>
<span id="spice-status" style="font-size:10px;color:var(--muted);margin-left:4px"></span>
</div>
<div class="spice-split">
<textarea class="spice-netlist" id="spice-netlist" spellcheck="false"></textarea>
<div class="spice-results" id="spice-results">
<div style="color:var(--muted);font-size:11px">▶ ngspice 실행하면<br>노드 전압과 전류가<br>여기에 표시됩니다.</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="footer">
<span id="footer-info">avr8js · ATmega328P @ 16MHz</span>
<span class="cycles" id="footer-cycles">0 cycles</span>
</div>
<!-- Compile overlay -->
<div class="overlay" id="overlay">
<div class="overlay-box">
<div class="spinner" id="overlay-spinner"></div>
<h2 id="overlay-title">컴파일 중...</h2>
<p id="overlay-msg" style="display:none"></p>
<button id="overlay-btn" onclick="closeOverlay()" style="display:none">닫기</button>
</div>
</div>
<!-- GPIO Pin popup -->
<div class="pin-popup" id="pin-popup">
<div class="pin-popup-hdr" id="pin-popup-hdr">
📌 GPIO 핀 상태
<button onclick="togglePinPopup()">✕</button>
</div>
<div class="pin-popup-body" id="pin-popup-body">
<div class="pins-header">DIGITAL PINS</div>
<div id="digital-pins"></div>
<div class="pin-sep"></div>
<div class="pins-header" style="margin-top:8px">ANALOG PINS</div>
<div id="analog-pins"></div>
</div>
</div>
<!-- bb-popup stub: content moved inline into .main as .sch-panel -->
<div id="bb-popup" style="display:none"></div>
<!-- avr8js from CDN -->
<script type="module" src="/js/arduino/arduino-emulator.js"></script>
</body>
</html>