arduino-emulator.html이 코드에디터 안 iframe으로 뜰 때, 미디어쿼리가 창 폭이 아니라 iframe 폭으로 평가되어 넓은 화면에서도 모바일 서랍 레이아웃이 나오던 버그 수정 (브레이크포인트 1024px→760px, JS 판정도 matchMedia로 통일해 CSS와 어긋나지 않게 함). 코드에디터에 에뮬레이터 세션 강제 재시작 버튼 추가, 패널 높이를 고정값→75vh로. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
855 lines
66 KiB
HTML
855 lines
66 KiB
HTML
<!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="컴파일 & 실행 (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="브레드보드 & 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>
|