459 lines
26 KiB
HTML
459 lines
26 KiB
HTML
<!doctype html>
|
||
<html lang="ko">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<meta
|
||
name="description"
|
||
content="마이크 위치를 직접 편집하고 격자·선형·원형·나선·랜덤 배열을 생성하는 micarrsim Array Builder"
|
||
/>
|
||
<title>Array Builder · micarrsim</title>
|
||
<link rel="stylesheet" href="/packages/ui/app-shell.css" />
|
||
<link rel="stylesheet" href="/apps/array-builder/styles.css" />
|
||
</head>
|
||
<body>
|
||
<a class="skip-link" href="#array-canvas">배열 편집 화면으로 건너뛰기</a>
|
||
<div class="app-shell array-builder-shell">
|
||
<header class="app-header array-builder-header">
|
||
<a class="app-brand" href="/">
|
||
<strong>micarrsim</strong>
|
||
<span>Array Builder</span>
|
||
</a>
|
||
|
||
<nav class="array-workflow" aria-label="마이크 어레이 제작 순서">
|
||
<ol>
|
||
<li><span>1</span>크기·형상</li>
|
||
<li><span>2</span>생성·편집</li>
|
||
<li><span>3</span>검증</li>
|
||
<li><span>4</span>저장</li>
|
||
</ol>
|
||
</nav>
|
||
|
||
<div class="header-actions">
|
||
<label class="button button--ghost file-button">
|
||
JSON 열기
|
||
<input id="array-input" type="file" accept="application/json,.json" />
|
||
</label>
|
||
<a class="button button--ghost" href="/apps/acoustic-camera/" title="새 Camera 세션을 엽니다. 기존 Camera에서 편집을 시작했다면 원래 Camera 탭으로 돌아가 Array JSON을 선택하세요.">새 Camera 열기</a>
|
||
<button class="button button--primary" id="export-button" type="button">Array JSON 저장</button>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="array-builder-layout" id="array-builder-layout" data-split-layout>
|
||
<section class="array-workspace" id="array-builder-workspace" aria-label="마이크 배열 편집 화면">
|
||
<div class="workspace-heading">
|
||
<div>
|
||
<p class="eyebrow">마이크 배치</p>
|
||
<h1>Array 2D 편집 · 3D 검사</h1>
|
||
</div>
|
||
<p class="workspace-summary" id="workspace-summary">0.45 × 0.45 m · 12 channels</p>
|
||
</div>
|
||
|
||
<div class="workspace-toolbar" role="toolbar" aria-label="배열 편집 도구">
|
||
<div class="toolbar-group" aria-label="편집 도구">
|
||
<button class="tool-button is-active" id="select-tool-button" type="button" aria-pressed="true" aria-keyshortcuts="V">선택 <kbd>V</kbd></button>
|
||
<button class="tool-button" id="add-tool-button" type="button" aria-pressed="false" aria-keyshortcuts="A">추가 <kbd>A</kbd></button>
|
||
</div>
|
||
<div class="toolbar-group array-view-toggle" role="group" aria-label="배열 보기 방식">
|
||
<button class="tool-button is-active" id="array-view-2d-button" type="button" aria-pressed="true">2D 편집</button>
|
||
<button class="tool-button" id="array-view-3d-button" type="button" aria-pressed="false">3D 검사</button>
|
||
</div>
|
||
<div class="toolbar-group" aria-label="실행 취소와 다시 실행">
|
||
<button class="tool-button" id="undo-button" type="button" aria-keyshortcuts="Control+Z" disabled>실행 취소</button>
|
||
<button class="tool-button" id="redo-button" type="button" aria-keyshortcuts="Control+Shift+Z" disabled>다시 실행</button>
|
||
</div>
|
||
<div class="toolbar-group zoom-tools" aria-label="화면 확대와 맞춤">
|
||
<button class="icon-button" id="zoom-out-button" type="button" aria-label="축소" title="축소">−</button>
|
||
<button class="tool-button" id="fit-view-button" type="button">화면 맞춤</button>
|
||
<button class="icon-button" id="zoom-in-button" type="button" aria-label="확대" title="확대">+</button>
|
||
</div>
|
||
<div class="snap-controls">
|
||
<label class="check-field"><input id="snap-enabled" type="checkbox" checked /><span>격자 스냅</span></label>
|
||
<label class="compact-field"><span>간격</span><input id="snap-step" type="number" min="0.1" max="1000" step="0.1" value="5" inputmode="decimal" /><small>mm</small></label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="array-canvas-card">
|
||
<canvas
|
||
id="array-canvas"
|
||
tabindex="0"
|
||
aria-label="마이크 어레이 평면 편집 canvas"
|
||
aria-describedby="canvas-instructions"
|
||
></canvas>
|
||
<div class="canvas-instructions" id="canvas-instructions">
|
||
<strong>마이크를 클릭하거나 드래그해 위치를 편집하세요.</strong>
|
||
<span>추가 도구에서는 빈 공간을 클릭해 새 채널을 만듭니다.</span>
|
||
</div>
|
||
<div class="canvas-front-badge" id="canvas-front-badge">+Z FRONT · 화면 바깥쪽</div>
|
||
<div class="selection-status" id="selection-status" role="status" aria-live="polite">선택된 마이크 없음</div>
|
||
</div>
|
||
|
||
<section class="array-statistics" aria-label="배열 요약">
|
||
<article class="metric-card"><span>채널</span><strong id="microphone-count">12</strong><small>microphones</small></article>
|
||
<article class="metric-card"><span>최소 간격</span><strong id="minimum-spacing">—</strong><small>nearest pair</small></article>
|
||
<article class="metric-card"><span>무게중심</span><strong id="array-centroid">—</strong><small>x, y, z</small></article>
|
||
<article class="metric-card"><span>보정 채널</span><strong id="calibration-count">0</strong><small>sensitivity / phase / response</small></article>
|
||
</section>
|
||
|
||
<details class="beam-pattern-card" id="beam-pattern-panel" open>
|
||
<summary class="beam-pattern-heading">
|
||
<div>
|
||
<p class="eyebrow">배열 분석</p>
|
||
<h2>수신 빔 패턴</h2>
|
||
</div>
|
||
<span id="beam-pattern-summary">1 kHz · XZ/YZ/XY 동시</span>
|
||
</summary>
|
||
|
||
<div class="beam-pattern-body">
|
||
<div class="beam-pattern-controls">
|
||
<p class="beam-pattern-model">
|
||
Narrowband far-field conventional delay-and-sum
|
||
<span>geometry-only</span>
|
||
</p>
|
||
|
||
<div class="beam-pattern-view-switch" role="group" aria-label="빔 패턴 보기 방식">
|
||
<button class="tool-button is-active" id="beam-pattern-view-cuts-button" type="button" aria-pressed="true">세 평면</button>
|
||
<button class="tool-button" id="beam-pattern-view-3d-button" type="button" aria-pressed="false">3D 표면</button>
|
||
</div>
|
||
|
||
<div class="beam-pattern-fields">
|
||
<label class="field beam-frequency-field">
|
||
<span>아날로그 주파수 · 로그 슬라이더</span>
|
||
<div class="beam-frequency-control">
|
||
<input
|
||
id="beam-pattern-frequency"
|
||
type="range"
|
||
min="0"
|
||
max="6000"
|
||
step="1"
|
||
value="3000"
|
||
aria-valuemin="1"
|
||
aria-valuemax="1000000"
|
||
aria-valuenow="1000"
|
||
aria-valuetext="1.000 kHz"
|
||
/>
|
||
<output id="beam-pattern-frequency-value" for="beam-pattern-frequency">1.000 kHz</output>
|
||
</div>
|
||
<small>1 Hz–1 MHz · 이동 중 live preview</small>
|
||
</label>
|
||
<label class="field">
|
||
<span>음속 (m/s)</span>
|
||
<input
|
||
id="beam-pattern-speed-of-sound"
|
||
type="number"
|
||
min="250"
|
||
max="400"
|
||
step="0.1"
|
||
value="343"
|
||
inputmode="decimal"
|
||
/>
|
||
</label>
|
||
<label class="field" data-beam-mode="cuts">
|
||
<span>단면별 조향각 (deg)</span>
|
||
<input
|
||
id="beam-pattern-steering-angle"
|
||
type="number"
|
||
min="-360"
|
||
max="360"
|
||
step="0.5"
|
||
value="0"
|
||
inputmode="decimal"
|
||
/>
|
||
<small>각 XZ/YZ/XY 평면 안에서 독립 조향</small>
|
||
</label>
|
||
<label class="field" data-beam-mode="cuts">
|
||
<span>각도 샘플</span>
|
||
<select id="beam-pattern-samples">
|
||
<option value="auto">자동 · 위상 간격 기준</option>
|
||
<option value="360">360 · 1°</option>
|
||
<option value="720">720 · 0.5°</option>
|
||
<option value="1440">1,440 · 0.25°</option>
|
||
<option value="2880">2,880 · 0.125°</option>
|
||
<option value="5760">5,760 · 0.0625°</option>
|
||
</select>
|
||
</label>
|
||
<label class="field" data-beam-mode="surface" hidden>
|
||
<span>3D 조향 방위각 (deg)</span>
|
||
<input id="beam-pattern-steering-azimuth" type="number" min="-360" max="360" step="1" value="0" inputmode="decimal" />
|
||
<small>XY에서 +X→+Y</small>
|
||
</label>
|
||
<label class="field" data-beam-mode="surface" hidden>
|
||
<span>3D 조향 고도각 (deg)</span>
|
||
<input id="beam-pattern-steering-elevation" type="number" min="-90" max="90" step="1" value="90" inputmode="decimal" />
|
||
<small>+90°가 +Z FRONT</small>
|
||
</label>
|
||
<label class="field" data-beam-mode="surface" hidden>
|
||
<span>3D 구면 표본</span>
|
||
<select id="beam-pattern-surface-resolution">
|
||
<option value="5">5° · 72×37</option>
|
||
<option value="3" selected>3° · 120×61</option>
|
||
<option value="2.5">2.5° · 144×73</option>
|
||
</select>
|
||
</label>
|
||
<label class="field">
|
||
<span>표시 하한 (dB)</span>
|
||
<select id="beam-pattern-floor">
|
||
<option value="-30">−30 dB</option>
|
||
<option value="-40">−40 dB</option>
|
||
<option value="-60" selected>−60 dB</option>
|
||
<option value="-80">−80 dB</option>
|
||
<option value="-100">−100 dB</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="beam-pattern-metrics" aria-label="빔 패턴 수치">
|
||
<article><span>파장</span><strong id="beam-pattern-wavelength">—</strong></article>
|
||
<article><span>각도 해상도</span><strong id="beam-pattern-resolution">—</strong></article>
|
||
<article><span>조향 응답</span><strong id="beam-pattern-steering-response">—</strong></article>
|
||
<article><span>−3.0103 dB 폭</span><strong id="beam-pattern-hpbw">—</strong></article>
|
||
</div>
|
||
|
||
<p class="beam-pattern-status" id="beam-pattern-status" role="status" aria-live="polite">
|
||
현재 배열의 수신 array factor를 계산하고 있습니다.
|
||
</p>
|
||
<p class="beam-pattern-note" id="beam-pattern-model-note">
|
||
이상적으로 보정된 무지향성 점 마이크의 자유장 평면파 응답입니다. 세 평면은 각각
|
||
독립 조향한 great-circle 단면이며 3D 모드는 실제 구면 방향을 계산합니다. 근거리,
|
||
방·하우징, 마이크 주파수 응답은 포함하지 않습니다. ADC가 없으므로 Nyquist 초과
|
||
주파수도 접지 않고 원래 파장으로 계산합니다.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="beam-pattern-visuals">
|
||
<div class="beam-pattern-cut-grid" id="beam-pattern-cuts-view">
|
||
<figure class="beam-pattern-figure" data-cut="xz">
|
||
<div class="beam-pattern-figure-heading"><strong>XZ</strong><span>0° +Z · 90° +X</span></div>
|
||
<svg id="beam-pattern-plot-xz" viewBox="0 0 520 520" role="img" aria-label="XZ 수신 빔 패턴 polar plot" aria-describedby="beam-pattern-status beam-pattern-model-note beam-pattern-caption-xz"></svg>
|
||
<figcaption id="beam-pattern-caption-xz">계산 중…</figcaption>
|
||
</figure>
|
||
<figure class="beam-pattern-figure" data-cut="yz">
|
||
<div class="beam-pattern-figure-heading"><strong>YZ</strong><span>0° +Z · 90° +Y</span></div>
|
||
<svg id="beam-pattern-plot-yz" viewBox="0 0 520 520" role="img" aria-label="YZ 수신 빔 패턴 polar plot" aria-describedby="beam-pattern-status beam-pattern-model-note beam-pattern-caption-yz"></svg>
|
||
<figcaption id="beam-pattern-caption-yz">계산 중…</figcaption>
|
||
</figure>
|
||
<figure class="beam-pattern-figure" data-cut="xy">
|
||
<div class="beam-pattern-figure-heading"><strong>XY</strong><span>0° +X · 90° +Y</span></div>
|
||
<svg id="beam-pattern-plot-xy" viewBox="0 0 520 520" role="img" aria-label="XY 수신 빔 패턴 polar plot" aria-describedby="beam-pattern-status beam-pattern-model-note beam-pattern-caption-xy"></svg>
|
||
<figcaption id="beam-pattern-caption-xy">계산 중…</figcaption>
|
||
</figure>
|
||
</div>
|
||
|
||
<figure class="beam-pattern-surface-figure" id="beam-pattern-surface-view" hidden>
|
||
<div class="beam-pattern-figure-heading"><strong>3D 구면 응답</strong><span>drag하여 회전 · +Z FRONT</span></div>
|
||
<canvas id="beam-pattern-surface" tabindex="0" role="img" aria-label="3D 수신 빔 패턴 표면" aria-describedby="beam-pattern-status beam-pattern-model-note beam-pattern-surface-caption"></canvas>
|
||
<figcaption id="beam-pattern-surface-caption">3D 표면을 준비하고 있습니다.</figcaption>
|
||
</figure>
|
||
|
||
<div class="beam-pattern-diagnostics" id="beam-pattern-peaks" aria-label="평면별 sampled peak 진단">
|
||
<p data-cut="xz"><strong>XZ</strong><span>계산 중…</span></p>
|
||
<p data-cut="yz"><strong>YZ</strong><span>계산 중…</span></p>
|
||
<p data-cut="xy"><strong>XY</strong><span>계산 중…</span></p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="shortcut-help">
|
||
<summary>빠른 편집 키</summary>
|
||
<div>
|
||
<p><kbd>V</kbd> 선택 · <kbd>A</kbd> 추가 · <kbd>Delete</kbd> 선택 삭제</p>
|
||
<p><kbd>← ↑ ↓ →</kbd> 위치 미세 조정 · <kbd>Shift</kbd>를 함께 누르면 10배 이동</p>
|
||
</div>
|
||
</details>
|
||
</section>
|
||
|
||
<aside
|
||
class="array-controls"
|
||
id="array-builder-controls"
|
||
data-split-panel
|
||
aria-label="마이크 어레이 설정"
|
||
>
|
||
<div class="runtime-line">
|
||
<span class="status-chip status-chip--neutral" data-runtime-status>런타임 확인 중…</span>
|
||
<span class="save-state" id="save-state" role="status">기본 배열</span>
|
||
</div>
|
||
|
||
<section class="validation-card" aria-live="polite" aria-atomic="true">
|
||
<span class="validation-dot" id="validation-dot" aria-hidden="true"></span>
|
||
<div>
|
||
<strong id="validation-title">검증 대기</strong>
|
||
<p id="validation-message">마이크 배열을 준비하고 있습니다.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="control-section array-metadata-section">
|
||
<div class="section-title"><span>01</span><h2>배열 정보</h2></div>
|
||
<label class="field field--wide">
|
||
<span>이름</span>
|
||
<input id="array-name" type="text" maxlength="120" autocomplete="off" />
|
||
</label>
|
||
<label class="field field--wide">
|
||
<span>Array ID</span>
|
||
<input
|
||
id="array-id"
|
||
type="text"
|
||
maxlength="120"
|
||
pattern="[a-z0-9][a-z0-9._-]*"
|
||
title="영문 소문자나 숫자로 시작하고 소문자, 숫자, 점, 밑줄, 하이픈만 사용할 수 있습니다."
|
||
autocomplete="off"
|
||
/>
|
||
</label>
|
||
<label class="field field--wide">
|
||
<span>설명 <small>선택 사항</small></span>
|
||
<textarea id="array-description" rows="2" maxlength="1000"></textarea>
|
||
</label>
|
||
</section>
|
||
|
||
<section class="control-section control-section--primary aperture-section">
|
||
<div class="section-title"><span>02</span><h2>크기와 형상</h2></div>
|
||
<div class="field-grid">
|
||
<label class="field">
|
||
<span>너비 (m)</span>
|
||
<input id="aperture-width" type="number" min="0.001" max="20" step="0.01" value="0.45" inputmode="decimal" />
|
||
</label>
|
||
<label class="field">
|
||
<span>높이 (m)</span>
|
||
<input id="aperture-height" type="number" min="0.001" max="20" step="0.01" value="0.45" inputmode="decimal" />
|
||
</label>
|
||
</div>
|
||
<label class="field field--wide">
|
||
<span>생성 경계</span>
|
||
<select id="aperture-shape">
|
||
<option value="rectangle">사각형</option>
|
||
<option value="ellipse">타원</option>
|
||
</select>
|
||
</label>
|
||
<p class="section-note">크기를 바꿔도 기존 좌표는 자동으로 늘어나거나 줄어들지 않습니다. 생성 경계 형상은 편집용이며 JSON에는 최종 좌표와 너비·높이만 저장됩니다.</p>
|
||
<div class="button-grid button-grid--two">
|
||
<button class="button button--ghost" id="center-microphones-button" type="button">좌표 중앙 정렬</button>
|
||
<button class="button button--ghost" id="fit-aperture-button" type="button">좌표에 크기 맞춤</button>
|
||
<button class="button button--ghost button-grid__wide" id="clamp-microphones-button" type="button">경계 밖 마이크를 안으로 이동</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="control-section preset-section">
|
||
<div class="section-title"><span>03</span><h2>프리셋 생성</h2></div>
|
||
<label class="field field--wide">
|
||
<span>배치 방식</span>
|
||
<select id="preset-type">
|
||
<option value="grid">격자</option>
|
||
<option value="linear">선형</option>
|
||
<option value="ring">원형 링</option>
|
||
<option value="archimedean">나선 · Archimedean</option>
|
||
<option value="spiral">Fermat 나선 · 해바라기</option>
|
||
<option value="random">균일 랜덤</option>
|
||
</select>
|
||
</label>
|
||
<div class="preset-grid">
|
||
<label class="field" id="preset-count-field">
|
||
<span>마이크 수</span>
|
||
<input id="preset-count" type="number" min="1" max="1024" step="1" value="16" inputmode="numeric" />
|
||
</label>
|
||
<label class="field" id="preset-axis-field">
|
||
<span>선형 축</span>
|
||
<select id="preset-axis">
|
||
<option value="x">가로 (x)</option>
|
||
<option value="y">세로 (y)</option>
|
||
</select>
|
||
</label>
|
||
<label class="field" id="preset-turns-field">
|
||
<span>나선 회전 수</span>
|
||
<input id="preset-turns" type="number" min="0.25" max="32" step="0.25" value="2.5" inputmode="decimal" />
|
||
</label>
|
||
<label class="field" id="preset-columns-field">
|
||
<span>열</span>
|
||
<input id="preset-columns" type="number" min="1" max="1024" step="1" value="4" inputmode="numeric" />
|
||
</label>
|
||
<label class="field" id="preset-rows-field">
|
||
<span>행</span>
|
||
<input id="preset-rows" type="number" min="1" max="1024" step="1" value="3" inputmode="numeric" />
|
||
</label>
|
||
<label class="field" id="preset-seed-field">
|
||
<span>Random seed</span>
|
||
<input id="preset-seed" type="number" min="0" max="4294967295" step="1" value="1" inputmode="numeric" />
|
||
</label>
|
||
<label class="field" id="preset-minimum-spacing-field">
|
||
<span>최소 간격 (m)</span>
|
||
<input id="preset-minimum-spacing" type="number" min="0" max="20" step="0.001" value="0.015" inputmode="decimal" />
|
||
</label>
|
||
<label class="field preset-grid__wide" id="preset-edge-inset-field">
|
||
<span>경계 여백 (m)</span>
|
||
<input id="preset-edge-inset" type="number" min="0" max="10" step="0.001" value="0.01" inputmode="decimal" />
|
||
</label>
|
||
</div>
|
||
<p class="section-note" id="preset-hint">격자는 아래에서 위로, 각 행은 왼쪽에서 오른쪽 순서로 채널을 만듭니다.</p>
|
||
<button class="button button--primary button--full" id="generate-preset-button" type="button">현재 배열을 프리셋으로 교체</button>
|
||
</section>
|
||
|
||
<section class="control-section microphone-section">
|
||
<div class="section-title section-title--split">
|
||
<div><span>04</span><h2>마이크 편집</h2></div>
|
||
<button class="mini-button" id="add-microphone-button" type="button">+ 마이크</button>
|
||
</div>
|
||
|
||
<label class="field field--wide search-field">
|
||
<span>목록 검색</span>
|
||
<input id="microphone-search" type="search" placeholder="ID로 찾기" autocomplete="off" />
|
||
</label>
|
||
<div class="microphone-list" id="microphone-list" role="listbox" aria-label="마이크 채널 목록">
|
||
<p class="list-empty">마이크 목록을 준비하고 있습니다.</p>
|
||
</div>
|
||
|
||
<div class="selected-microphone-editor">
|
||
<p class="selection-empty" id="microphone-selection-empty">canvas나 목록에서 마이크를 선택하세요.</p>
|
||
<div id="microphone-fields">
|
||
<label class="field field--wide">
|
||
<span>마이크 ID</span>
|
||
<input id="microphone-id" type="text" maxlength="64" autocomplete="off" disabled />
|
||
</label>
|
||
<div class="field-grid field-grid--three">
|
||
<label class="field"><span>x (m)</span><input id="microphone-x" type="number" step="0.001" inputmode="decimal" disabled /></label>
|
||
<label class="field"><span>y (m)</span><input id="microphone-y" type="number" step="0.001" inputmode="decimal" disabled /></label>
|
||
<label class="field"><span>z (m)</span><input id="microphone-z" type="number" step="0.001" inputmode="decimal" disabled /></label>
|
||
</div>
|
||
|
||
<details class="inline-details">
|
||
<summary>보정값</summary>
|
||
<div class="field-grid calibration-fields">
|
||
<label class="field"><span>Sensitivity (dB)</span><input id="microphone-sensitivity" type="number" min="-120" max="120" step="0.1" inputmode="decimal" disabled /></label>
|
||
<label class="field"><span>Phase (deg)</span><input id="microphone-phase" type="number" min="-180" max="180" step="0.1" inputmode="decimal" disabled /></label>
|
||
</div>
|
||
<p class="section-note" id="frequency-response-summary">주파수 응답 없음</p>
|
||
</details>
|
||
|
||
<div class="button-grid reorder-actions">
|
||
<button class="button button--ghost" id="move-microphone-up-button" type="button" disabled>채널 앞으로</button>
|
||
<button class="button button--ghost" id="move-microphone-down-button" type="button" disabled>채널 뒤로</button>
|
||
<button class="button button--danger button-grid__wide" id="remove-microphone-button" type="button" disabled>선택 마이크 삭제</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="mobile-save-actions">
|
||
<button class="button button--primary button--full" id="mobile-export-button" type="button">Array JSON 저장</button>
|
||
</div>
|
||
</aside>
|
||
|
||
<div
|
||
class="split-handle"
|
||
id="array-builder-split-handle"
|
||
role="separator"
|
||
tabindex="0"
|
||
aria-label="배열 설정 패널 너비 조절"
|
||
aria-controls="array-builder-controls"
|
||
aria-orientation="vertical"
|
||
data-split-resizer
|
||
data-storage-key="micarrsim.array-builder.control-panel-width"
|
||
title="드래그하거나 방향키로 너비 조절 · Enter/더블클릭으로 초기화"
|
||
></div>
|
||
|
||
</main>
|
||
</div>
|
||
|
||
<script type="module" src="/packages/ui/runtime-status.js"></script>
|
||
<script type="module" src="/apps/array-builder/main.js"></script>
|
||
</body>
|
||
</html>
|