docs: add quick start and user guides

This commit is contained in:
Codex
2026-08-28 12:47:57 +09:00
parent 3317a03d1a
commit 513de2b51f
16 changed files with 1148 additions and 65 deletions
+4 -3
View File
@@ -18,6 +18,7 @@ distance 생성, CPU STFT가 포함된다. 200×200은 64 MiB steering buffer
쓰지 않는다.
같은 환경의 자동 검증에서 192채널·50×50 GPU raw map은 CPU raw map 대비 peak-normalized RMS
오차 `3.008e-5`를 기록했고, 모든 37개 테스트를 통과했다. 상세 원자료는
`webgpu-intel-gen9-2026-08-28.json`에 있다. 사용자는 `/benchmarks/webgpu-scaling.html`에서 자신의
장치 결과를 다시 측정해 JSON으로 저장할 수 있다.
오차 `3.008e-5`를 기록했고, 측정 당시 제공되던 37개 테스트를 모두 통과했다. 이후 end-to-end와
서버 보안 테스트가 추가되었으므로 현재 전체 suite 상태는 `/tests/`에서 별도로 확인한다. 상세
원자료는 `webgpu-intel-gen9-2026-08-28.json`에 있다. 사용자는
`/benchmarks/webgpu-scaling.html`에서 자신의 장치 결과를 다시 측정해 JSON으로 저장할 수 있다.
+36 -3
View File
@@ -11,14 +11,27 @@ micarrsim의 저장 형식은 JSON Schema Draft 2020-12와 추가 관계 검증
- `micarrsim.experiment`: scan plane/grid, 주파수 대역, STFT와 DAS 설정
- `micarrsim.result`: raw/display acoustic map, 복수 peak와 source 매칭, 평가·성능 정보
scene JSON에는 192채널 원시 sample을 넣지 않는다. 합성 tone/noise는 seed가 포함된 recipe로
재생성하고, 녹음 파일과 이미지는 manifest 기준의 안전한 상대 경로로 참조한다. 사용자 컴퓨터의
절대 경로, URL, `..` 경로는 저장하지 않는다.
scene JSON에는 192채널 원시 sample이나 원본 asset byte를 넣지 않는다. 합성 tone/noise는 seed가
포함된 recipe로 재생성하고, 녹음 파일과 이미지는 `id`, `kind`, `fileName`, `mimeType`,
`sha256` manifest로 참조한다. 사용자 컴퓨터의 절대 경로, URL, `..` 경로는 저장하지 않는다.
array의 `sensitivityDb`와 `phaseOffsetDeg`는 각 마이크 수신 channel에 적용되는 편차다. 합성기는
이를 신호에 주입하고, beamformer는 알려진 calibration 값의 역 gain/phase를 적용한다. 따라서
두 값이 0이 아닌 배열도 오류 없이 조용히 무시되지 않는다.
## 로컬 asset 재연결
브라우저 보안상 Scene JSON을 새 페이지에서 열었을 때 이전 로컬 파일 권한은 복원되지 않는다.
Camera는 사용자가 다시 선택한 파일을 SHA-256으로 계산해 같은 descriptor에만 연결한다. 이름이
같아도 byte가 다르면 거절하고, 원본은 브라우저 메모리에서만 decode하며 서버에 업로드하지 않는다.
- 파일 하나: 최대 128 MiB
- 한 Scene에 연결된 원본 파일 합계: 최대 512 MiB
- 동시에 유지하는 decode 오디오 channel 합계: 최대 192 MiB
Builder가 내보낸 Scene JSON과 사용한 원본 이미지·오디오는 함께 보관해야 한다. Preview WAV는
scene recipe를 mono로 합친 청취 확인용 파일이며 마이크 배열의 192채널 수신 신호가 아니다.
## 오디오와 map 정규화
`scene.mix.normalization`은 마이크 신호 합성 전에 적용할 오디오 mix 정책이다. `none`은 원래
@@ -32,6 +45,25 @@ raw DAS map을 덮어쓰지 않는다.
map은 row-major 배열이다. 첫 행은 `yMax`, 마지막 행은 `yMin`, 각 행은 `xMin`에서 `xMax`
방향이다. `values.length`는 반드시 `rows × columns`와 같아야 한다.
## Result 평가 정보
각 band의 `peaks`는 위치와 power 값, 선택적 `sourceId`, `localizationErrorM`,
`localizationErrorPixels`를 저장한다. `metrics`에는 다음 유한값만 기록한다.
- `peakToSidelobeDb`
- `energyInsideTruthRatio`
- `precision`, `recall`
- `mainLobeWidthM`
`evaluation`은 지표를 다시 해석할 수 있도록 truth 반경, source 매칭 tolerance, peak 검출
threshold와 최소 거리, sidelobe 제외 반경, half-power 비율, dynamic range, 최대 검출 수를
버전과 함께 저장한다. `environment`에는 실제 backend, microphone/grid 수, scan bounds와 z,
가능한 경우 GPU adapter 및 fallback 정보를 기록한다.
대역별 ground truth는 tone의 주파수가 band 안에 있을 때 그 tone source를 포함한다. noise와
asset source는 현재 모든 band의 truth로 간주한다. 즉 녹음 asset의 실제 spectrum energy를
대역별로 판정하는 계약은 아직 없으며, 이 가정은 실제 데이터 결과를 해석할 때 고려해야 한다.
## 재현성 hash
`result.inputHash`는 실행에 사용한 `{ array, scene, experiment }`를 객체 key 사전순으로 재귀
@@ -48,6 +80,7 @@ SHA-256을 계산해 scene의 해당 asset `sha256` 필드를 채운 상태로 c
- asset 종류와 안전한 상대 경로
- Nyquist 및 시간·scan 범위
- map shape와 source peak 매칭
- band별 evaluation 파라미터와 허용 metric
- 현재 비스트리밍 CPU backend의 512 MiB channel-buffer 예산
지원하지 않는 schemaVersion은 묵시적으로 변환하지 않고 오류로 알린다.
+32 -22
View File
@@ -1,5 +1,10 @@
# micarrsim 실행 계획
> 구현 상태 · 2026-08-28
>
> 단계 0–4는 완료되어 설치 없는 브라우저 앱, CPU/WebGPU DAS, Builder→Camera asset 재연결,
> 배열 비교, 평가·Result 내보내기와 53개 자동 테스트로 검증했다. 단계 5는 향후 고도화 범위다.
## 1. 목표와 성공 기준
이 프로젝트는 정방형 평면에 배치할 192개 MEMS 마이크의 배열을 비교하기 위한 재현 가능한
@@ -8,7 +13,7 @@
1. 기계음 데이터와 ambient audio를 조합해 장면 및 ground truth를 생성한다.
2. 배열 JSON과 장면 정보를 사용해 각 마이크에 도달하는 신호를 합성한다.
3. WGSL로 conventional delay-and-sum(DAS/DAC) 빔포밍을 수행한다.
3. WGSL로 conventional delay-and-sum(DAS) 빔포밍을 수행한다.
4. 주파수 구간별 acoustic image를 ground truth 위에 표시하고 정량 평가한다.
5. 배열, 거리, 음원, 주파수, grid 해상도를 바꾼 실험을 저장하고 비교한다.
@@ -18,10 +23,12 @@ CPU 기준 구현과 GPU 결과의 수치 오차가 합의된 허용 범위 안
## 2. 기술 접근 결정
TypeScript 기반의 브라우저 앱 2개와 WebGPU/WGSL을 권장한다. 브라우저는 배열·장면 편집,
오디오 미리 듣기, 이미지 overlay에 적합하고 WebGPU는 192 × scan point × frequency bin 형태의
병렬 연산에 잘 맞는다. WGSL 커널을 독립 패키지로 두면 이후 GLSL 또는 네이티브 GPU 구현으로
옮길 때 데이터 배치와 수식을 재사용하기 쉽다.
최종 구현은 빌드 단계와 패키지 설치가 없는 브라우저 네이티브 ES module 2개와 WebGPU/WGSL을
선택했다. 초기 계획의 TypeScript 권고보다 Windows 사용자가 `start.cmd` 하나로 바로 실행하는
목표를 우선한 결정이다. JSON Schema와 브라우저 자동 테스트가 런타임 계약을 보완한다.
브라우저는 배열·장면 편집, 오디오 미리 듣기, 이미지 overlay에 적합하고 WebGPU는
192 × scan point × frequency bin 형태의 병렬 연산에 잘 맞는다. WGSL 커널은 독립 package로
두어 데이터 배치와 수식을 CPU 기준 구현과 대조할 수 있게 했다.
데이터셋 전체를 브라우저에 적재하지 않는다. 사용자가 로컬 파일을 선택해 장면을 만들고,
결과를 manifest와 오디오 파일로 내보내는 방식을 우선 구현한다. 대규모 batch 전처리가 필요해질
@@ -105,7 +112,7 @@ flattening이 모델 타당성을 훼손하는지 청취 및 spectrum 통계로
## 8. 단계별 구현
### 단계 0 — 기반과 계약
### 단계 0 — 기반과 계약 · 완료
- workspace, formatter, linter, unit test, WebGPU feature detection 구성
- 네 가지 JSON Schema와 최소 예제 작성
@@ -114,7 +121,7 @@ flattening이 모델 타당성을 훼손하는지 청취 및 spectrum 통계로
완료 조건: 예제 JSON이 schema validation을 통과하고 두 앱이 동일 manifest를 읽는다.
### 단계 1 — CPU 기준 모델
### 단계 1 — CPU 기준 모델 · 완료
- 자유음장 구면 전파, 분수 지연, STFT, steering, DAS의 CPU reference 구현
- 단일 음원의 지연·감쇠·peak 위치를 analytic result와 비교
@@ -122,15 +129,15 @@ flattening이 모델 타당성을 훼손하는지 청취 및 spectrum 통계로
완료 조건: 자동화된 golden test가 통과하고 sign/좌표 convention이 문서화된다.
### 단계 2 — Dataset Builder
### 단계 2 — Dataset Builder · 완료
- 이미지와 음원 파일 불러오기, 위치·시간·gain 편집, ambient mix와 미리 듣기
- deterministic seed, clipping 경고, manifest/audio 내보내기
- deterministic seed, clipping 경고, Scene JSON과 mono Preview WAV 내보내기
- 데이터셋 adapter 인터페이스와 synthetic adapter 구현
완료 조건: 만든 scene을 다시 열었을 때 음원, 위치, 파형, 정답이 동일하다.
### 단계 3 — WebGPU DAS
### 단계 3 — WebGPU DAS · 완료
- CPU와 동일한 buffer layout 및 WGSL kernel 구현
- frequency band별 map 생성, device limit 대응, CPU fallback
@@ -138,7 +145,7 @@ flattening이 모델 타당성을 훼손하는지 청취 및 spectrum 통계로
완료 조건: CPU/GPU map 오차 기준을 통과하고 192채널 50×50 실행 timing이 기록된다.
### 단계 4 — Acoustic Camera UI와 평가
### 단계 4 — Acoustic Camera UI와 평가 · 완료
- scene/ground truth/acoustic overlay, 배열 geometry, parameter panel 구현
- 결과 metric, 실행 환경, 입력 hash와 map 내보내기
@@ -146,7 +153,7 @@ flattening이 모델 타당성을 훼손하는지 청취 및 spectrum 통계로
완료 조건: 사용자가 코드 수정 없이 배열 JSON 두 개를 같은 장면에서 비교할 수 있다.
### 단계 5 — 실제 데이터와 고도화
### 단계 5 — 실제 데이터와 고도화 · 향후
- 라이선스 검토를 통과한 DCASE/MIMII adapter와 batch workflow 추가
- microphone calibration·noise 모델 검토
@@ -164,15 +171,18 @@ flattening이 모델 타당성을 훼손하는지 청취 및 spectrum 통계로
- 실제 AC와 simulation gap: 감도, 위상, 위치 오차를 이후 calibration/noise model로 주입 가능하게 한다.
- post-processing이 기본 결과를 가림: raw DAS map과 후처리 map을 항상 별도로 보존한다.
## 10. 첫 구현 백로그
## 10. 구현 결과와 다음 백로그
1. TypeScript workspace와 두 앱 shell을 생성한다.
2. array/scene/experiment/result schema와 fixture를 만든다.
3. 좌표 변환, 거리, 구면 감쇠, 주파수 phase delay 단위 테스트를 작성한다.
4. CPU DAS golden implementation과 synthetic scene을 만든다.
5. WGSL kernel 및 CPU/GPU 비교 harness를 만든다.
6. scene canvas, array view, parameter controls를 연결한다.
7. benchmark와 평가 지표를 결과 manifest에 기록한다.
- [x] 설치 없는 ES module workspace와 두 앱 shell
- [x] array/scene/experiment/result schema와 synthetic fixture
- [x] 좌표·거리·구면 감쇠·분수 지연·STFT 단위 테스트
- [x] CPU DAS golden implementation
- [x] WGSL kernel, CPU/GPU 비교와 50²/100²/200² benchmark
- [x] scene canvas, array view, parameter controls와 Result export
- [x] Builder→Camera SHA-256 asset 재연결 왕복 테스트
- [ ] DCASE/MIMII 라이선스 검토와 adapter
- [ ] 잔향·calibration·noise model
- [ ] DAMAS/CLEAN-SC 후처리
각 단계는 정확성 test를 먼저 통과시킨 뒤 UI 또는 최적화를 진행한다. 초기 milestone에서는 잔향,
DAMAS/CLEAN-SC, 대규모 dataset batch 변환을 완료 조건으로 삼지 않는다.
정확성 test를 먼저 통과시킨 뒤 UI 또는 최적화를 진행한다는 원칙은 유지한다. 단계 5 기능은
자유음장 DAS 결과를 바꾸지 않는 선택형 확장으로 추가한다.
+192
View File
@@ -0,0 +1,192 @@
<!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 빠른 시작, 전체 데이터 흐름, 결과 해석과 문제 해결"
/>
<title>micarrsim 사용 설명서</title>
<link rel="stylesheet" href="/packages/ui/app-shell.css" />
<link rel="stylesheet" href="/docs/styles.css" />
</head>
<body>
<header class="guide-header">
<a class="guide-brand" href="/">← micarrsim 홈</a>
<nav aria-label="문서 목차">
<a href="#quick-start">빠른 시작</a>
<a href="#full-workflow">내 데이터</a>
<a href="#results">결과 해석</a>
<a href="#troubleshooting">문제 해결</a>
</nav>
</header>
<main class="guide-shell">
<section class="guide-hero">
<p class="eyebrow">Local user guide</p>
<h1>설치 없이 시작하는<br />음향 카메라 실험</h1>
<p>
Windows 10/11과 최신 Chrome 또는 Edge만 있으면 됩니다. 모든 입력과 계산은 이
컴퓨터의 브라우저 안에서 처리되며 외부 서버로 업로드되지 않습니다.
</p>
</section>
<section class="guide-section" id="quick-start">
<div class="section-number">01</div>
<div>
<p class="eyebrow">30초 확인</p>
<h2>기본 결과 하나 저장하기</h2>
<ol class="guide-steps">
<li><code>start.cmd</code>를 더블클릭합니다.</li>
<li>홈에서 <a href="/apps/acoustic-camera/">Acoustic Camera</a>를 엽니다.</li>
<li><strong>▶ 기본 예제 실행</strong>을 누릅니다.</li>
<li>완료되면 <strong>Result JSON</strong>을 눌러 다운로드 폴더에 저장합니다.</li>
</ol>
<aside class="callout">
브라우저가 자동으로 열리지 않으면
<a href="http://127.0.0.1:4173/">http://127.0.0.1:4173/</a>을 직접 여세요.
종료할 때는 서버 콘솔에서 <kbd>Ctrl</kbd>+<kbd>C</kbd>를 누릅니다.
</aside>
</div>
</section>
<section class="guide-section" id="full-workflow">
<div class="section-number">02</div>
<div>
<p class="eyebrow">5분 전체 흐름</p>
<h2>내 장면을 만들고 Camera에서 실행하기</h2>
<ol class="guide-steps">
<li>
<a href="/apps/dataset-builder/">Dataset Builder</a>에서 이미지와 오디오를
선택하고 음원 위치, gain, 시간, ambient를 설정합니다.
</li>
<li><strong>Scene JSON 저장</strong>을 누르고 사용한 원본 파일과 함께 보관합니다.</li>
<li><strong>Scene 저장 후 Camera 열기</strong>를 누릅니다.</li>
<li>
Camera에서 파일을 <strong>Scene → Array → Experiment → Assets</strong> 순서로
가져옵니다. 내 Array/Experiment가 없다면 내장 기본값을 그대로 둡니다.
</li>
<li>
Assets에서 Builder에 사용한 원본 이미지와 오디오를 한 번에 다시 선택합니다.
준비 상태가 모두 초록색인지 확인합니다.
</li>
<li><strong>Auto (GPU→CPU)</strong>로 실행한 뒤 Result JSON을 저장합니다.</li>
</ol>
<aside class="callout callout--important" id="asset-reconnect">
<strong>Scene JSON에는 원본 파일이 들어 있지 않습니다.</strong>
파일명·종류·SHA-256 manifest만 저장됩니다. 브라우저는 새 페이지에서 로컬 파일
권한을 유지할 수 없으므로 같은 원본을 다시 선택해야 합니다. 이름이 같아도 내용의
SHA-256이 다르면 연결되지 않습니다.
</aside>
<div class="limit-grid" aria-label="asset 한도">
<div><strong>128 MiB</strong><span>원본 파일 하나</span></div>
<div><strong>512 MiB</strong><span>연결 파일 합계</span></div>
<div><strong>192 MiB</strong><span>decode된 오디오 합계</span></div>
</div>
</div>
</section>
<section class="guide-section">
<div class="section-number">03</div>
<div>
<p class="eyebrow">Compute backend</p>
<h2>속도와 격자 선택</h2>
<div class="guide-table-wrap">
<table>
<thead><tr><th>선택</th><th>동작</th><th>권장 상황</th></tr></thead>
<tbody>
<tr><td>Auto (GPU→CPU)</td><td>WebGPU를 먼저 시도하고 불가능하면 CPU 사용</td><td>기본 권장</td></tr>
<tr><td>WebGPU 강제</td><td>GPU 장치가 없거나 실패하면 fallback 없이 오류</td><td>GPU 비교·측정</td></tr>
<tr><td>CPU 강제</td><td>동일한 자유음장 DAS를 CPU에서 계산</td><td>호환성 확인·기준 비교</td></tr>
</tbody>
</table>
</div>
<p class="guide-copy">
먼저 50×50을 권장합니다. 100×100은 4배, 200×200은 16배의 scan point를 계산합니다.
실행 중에는 취소할 수 있으며, 취소하거나 실패해도 이전 완료 결과는 유지됩니다.
</p>
</div>
</section>
<section class="guide-section" id="results">
<div class="section-number">04</div>
<div>
<p class="eyebrow">Result JSON</p>
<h2>무엇이 저장되나요?</h2>
<dl class="result-list">
<div><dt>rawMap / displayMap</dt><dd>선형 power 원본과 화면 표시용 acoustic map</dd></div>
<div><dt>peaks</dt><dd>검출 위치, source 매칭, m·pixel 위치 오차</dd></div>
<div><dt>metrics</dt><dd>energy 비율, precision/recall, sidelobe, main-lobe 폭</dd></div>
<div><dt>evaluation</dt><dd>검출 threshold, 반경, tolerance 등 지표 계산 조건</dd></div>
<div><dt>timing / environment</dt><dd>backend, grid·scan 범위, 장치 정보와 구간별 시간</dd></div>
<div><dt>inputHash</dt><dd>Array·Scene·Experiment 입력을 묶은 SHA-256 재현성 식별자</dd></div>
</dl>
<aside class="callout">
평가는 tone 음원을 해당 주파수 대역이 tone 주파수를 포함할 때만 정답으로 사용합니다.
noise와 asset 음원은 현재 모든 대역의 정답으로 간주합니다. 녹음 파일의 실제 대역별
에너지를 자동 판정하는 기능은 아직 없습니다.
</aside>
</div>
</section>
<section class="guide-section" id="troubleshooting">
<div class="section-number">05</div>
<div>
<p class="eyebrow">Troubleshooting</p>
<h2>막혔을 때 확인할 것</h2>
<details open>
<summary>화면이 비거나 module/Worker 오류가 납니다.</summary>
<p><code>index.html</code>을 직접 열지 말고 반드시 <code>start.cmd</code>로 실행하세요.</p>
</details>
<details>
<summary>4173 포트를 사용할 수 없다고 나옵니다.</summary>
<p><code>start.cmd -Port 4174</code>처럼 다른 포트를 지정하고 <code>http://127.0.0.1:4174/</code>을 여세요.</p>
</details>
<details>
<summary>Experiment를 가져올 수 없습니다.</summary>
<p>Scene과 Array를 먼저 가져오세요. Experiment의 <code>sceneId</code>와 <code>arrayId</code>가 현재 문서와 같아야 합니다.</p>
</details>
<details>
<summary>Assets가 준비되지 않습니다.</summary>
<p>Builder에서 사용한 것과 바이트가 같은 원본 파일을 다시 선택하세요. 파일명뿐 아니라 SHA-256도 일치해야 합니다.</p>
</details>
<details>
<summary>WebGPU가 없거나 실행이 너무 느립니다.</summary>
<p>Backend를 Auto 또는 CPU로 두고 50×50 grid부터 실행하세요. 최신 Chrome/Edge와 그래픽 드라이버도 확인하세요.</p>
</details>
</div>
</section>
<section class="guide-section">
<div class="section-number">06</div>
<div>
<p class="eyebrow">Scope & privacy</p>
<h2>현재 모델의 범위</h2>
<p class="guide-copy">
micarrsim 1.0은 점 음원, 구면 감쇠·분수 지연을 쓰는 자유음장 모델과 conventional
DAS를 제공합니다. 실내 반사·잔향, 실제 마이크 위치 오차와 비선형성, DAMAS,
CLEAN-SC, DCASE/MIMII 자동 adapter는 현재 범위에 포함되지 않습니다. 결과는 실제
장비의 절대 성능 보증이 아니라 같은 조건에서 배열을 비교하는 재현 가능한 기준입니다.
</p>
<p class="guide-copy">
원본 asset은 브라우저 메모리에서만 읽고 서버나 외부 네트워크로 보내지 않습니다.
다만 Scene JSON에는 원본 파일명이 남을 수 있으므로 외부 공유 전 확인하세요.
</p>
</div>
</section>
<section class="reference-grid" aria-label="상세 문서">
<a href="/README.md"><strong>전체 README</strong><span>설치·실행·구조·제약</span></a>
<a href="/apps/dataset-builder/README.md"><strong>Builder 설명서</strong><span>source·ambient·Preview WAV</span></a>
<a href="/apps/acoustic-camera/README.md"><strong>Camera 설명서</strong><span>backend·결과·비교</span></a>
<a href="/docs/data-contracts.md"><strong>데이터 계약</strong><span>JSON Schema와 재현성</span></a>
<a href="/docs/execution-plan.md"><strong>실행 계획</strong><span>구현 상태와 향후 단계</span></a>
<a href="/tests/"><strong>브라우저 테스트</strong><span>단위·통합 테스트 실행</span></a>
<a href="/benchmarks/webgpu-scaling.html"><strong>WebGPU 측정</strong><span>현재 장치 benchmark</span></a>
</section>
</main>
</body>
</html>
+291
View File
@@ -0,0 +1,291 @@
.guide-header {
position: sticky;
z-index: 10;
top: 0;
display: flex;
align-items: center;
justify-content: space-between;
min-height: 64px;
padding: 12px clamp(18px, 4vw, 48px);
border-bottom: 1px solid var(--line);
background: rgb(8 17 15 / 92%);
backdrop-filter: blur(18px);
}
.guide-brand,
.guide-header nav a {
color: inherit;
font-size: 0.82rem;
font-weight: 700;
text-decoration: none;
}
.guide-header nav {
display: flex;
flex-wrap: wrap;
gap: 18px;
color: var(--muted);
}
.guide-shell {
width: min(980px, calc(100% - 40px));
margin: 0 auto;
padding: clamp(48px, 8vw, 96px) 0 72px;
}
.guide-hero {
max-width: 820px;
margin-bottom: clamp(64px, 10vw, 112px);
}
.guide-hero h1 {
margin-bottom: 24px;
font-size: clamp(2.8rem, 8vw, 6.4rem);
letter-spacing: -0.07em;
line-height: 0.98;
}
.guide-hero > p:last-child,
.guide-copy {
color: #c8d9d3;
line-height: 1.8;
}
.guide-hero > p:last-child {
max-width: 720px;
font-size: clamp(1rem, 2vw, 1.2rem);
}
.guide-section {
display: grid;
grid-template-columns: 70px minmax(0, 1fr);
gap: clamp(18px, 4vw, 42px);
padding: clamp(36px, 7vw, 68px) 0;
border-top: 1px solid var(--line);
scroll-margin-top: 72px;
}
.section-number {
color: var(--mint);
font-size: 0.75rem;
font-weight: 760;
letter-spacing: 0.12em;
}
.guide-section h2 {
margin-bottom: 28px;
font-size: clamp(1.8rem, 4vw, 3rem);
letter-spacing: -0.05em;
}
.guide-steps {
display: grid;
gap: 14px;
margin: 0;
padding-left: 1.35rem;
color: #c8d9d3;
line-height: 1.75;
}
.guide-steps li {
padding-left: 8px;
}
.guide-steps a,
.callout a {
color: var(--mint);
}
code,
kbd {
border: 1px solid var(--line);
border-radius: 6px;
color: var(--text);
background: rgb(255 255 255 / 5%);
font-family: "Cascadia Code", Consolas, monospace;
}
code {
padding: 0.12em 0.4em;
}
kbd {
padding: 2px 6px;
box-shadow: 0 2px 0 var(--line);
font-size: 0.78rem;
}
.callout {
margin-top: 26px;
padding: 18px 20px;
border-left: 3px solid var(--cyan);
border-radius: 0 12px 12px 0;
color: var(--muted);
background: rgb(88 199 232 / 7%);
line-height: 1.7;
}
.callout--important {
border-color: var(--amber);
background: rgb(255 197 110 / 7%);
}
.callout strong {
color: var(--text);
}
.limit-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
margin-top: 14px;
}
.limit-grid div {
padding: 18px;
border: 1px solid var(--line);
border-radius: 12px;
background: rgb(255 255 255 / 2%);
}
.limit-grid strong,
.limit-grid span {
display: block;
}
.limit-grid strong {
color: var(--mint);
font-size: 1.2rem;
}
.limit-grid span {
margin-top: 5px;
color: var(--muted);
font-size: 0.78rem;
}
.guide-table-wrap {
overflow-x: auto;
border: 1px solid var(--line);
border-radius: 14px;
}
.guide-table-wrap table {
width: 100%;
border-collapse: collapse;
color: #c8d9d3;
font-size: 0.88rem;
}
.guide-table-wrap th,
.guide-table-wrap td {
padding: 14px 16px;
border-bottom: 1px solid var(--line);
text-align: left;
vertical-align: top;
}
.guide-table-wrap th {
color: var(--text);
background: rgb(255 255 255 / 4%);
}
.guide-table-wrap tr:last-child td {
border-bottom: 0;
}
.result-list {
display: grid;
gap: 10px;
margin: 0;
}
.result-list div {
display: grid;
grid-template-columns: minmax(150px, 0.4fr) minmax(0, 1fr);
gap: 18px;
padding: 14px 0;
border-bottom: 1px solid var(--line);
}
.result-list dt {
color: var(--mint);
font-family: "Cascadia Code", Consolas, monospace;
font-size: 0.86rem;
}
.result-list dd {
margin: 0;
color: var(--muted);
line-height: 1.6;
}
details {
padding: 16px 0;
border-bottom: 1px solid var(--line);
}
summary {
cursor: pointer;
font-weight: 700;
}
details p {
margin: 12px 0 0;
color: var(--muted);
line-height: 1.7;
}
.reference-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
padding-top: 48px;
border-top: 1px solid var(--line);
}
.reference-grid a {
padding: 20px;
border: 1px solid var(--line);
border-radius: 14px;
background: var(--surface);
text-decoration: none;
}
.reference-grid a:hover,
.reference-grid a:focus-visible {
border-color: var(--mint);
outline: none;
}
.reference-grid strong,
.reference-grid span {
display: block;
}
.reference-grid span {
margin-top: 8px;
color: var(--muted);
font-size: 0.78rem;
line-height: 1.5;
}
@media (max-width: 720px) {
.guide-header nav {
display: none;
}
.guide-section {
grid-template-columns: 1fr;
}
.limit-grid,
.reference-grid {
grid-template-columns: 1fr;
}
.result-list div {
grid-template-columns: 1fr;
gap: 6px;
}
}