poseref 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Termnix-IT
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,246 @@
1
+ # PoseRef
2
+
3
+ 画像生成AIへ渡す「構図・ポーズ参考画像」をブラウザ上で素早く作るための Web アプリです。
4
+ 顔・髪・衣装を持たない単色マネキンのポーズとカメラ構図を調整し、3D ビューを PNG として書き出し、
5
+ 参考画像用のプロンプト(日本語 / 英語)をコピーできます。UI は日本語です。
6
+ MCP に対応した AI エージェント(Claude Code / Codex)から、文章の指示でポーズとカメラを作らせることもできます。
7
+
8
+ ## 使い方
9
+
10
+ Node.js 22.18 以上が必要です。AI エージェントから使う場合は、Claude Code か Codex も入れておいてください。
11
+
12
+ ### 導入(最初の 1 回だけ)
13
+
14
+ ```bash
15
+ npx poseref setup
16
+ ```
17
+
18
+ 見つかった Claude Code と Codex に PoseRef を登録します。実行する前に、何を登録し、どのファイルを書き換えるかを表示して確認を求めます。
19
+ 確認を省く場合は `--yes`、表示だけして何も変えない場合は `--dry-run` を付けます。
20
+ 終わったら Claude Code / Codex を起動し直し、「PoseRef で、椅子に座って頬杖をつくポーズを斜め前から撮って」のように頼んでください。
21
+ エージェントが最初に PoseRef のツールを使ったときに、PoseRef が起動してブラウザで画面が開きます。
22
+ 3D の描画はブラウザで行うので、このタブは開いたままにしてください。ヘッダーに「AI連携: 接続中」と表示されていれば準備できています。
23
+
24
+ 登録するコマンドはバージョンを固定しています(`npx -y poseref@<バージョン> mcp`)。新しい版が公開されても、知らないうちに別のコードが動くことはありません。
25
+ 更新するときは `npx poseref@latest setup` を実行し直してください。
26
+
27
+ ### エージェントなしで使う
28
+
29
+ ```bash
30
+ npx poseref # PoseRef を起動してブラウザで開く
31
+ npx poseref stop # 起動中の PoseRef を止める
32
+ ```
33
+
34
+ 画面だけでもポーズ作り・PNG 書き出し・プロンプトのコピーができます。
35
+
36
+ ### 設定
37
+
38
+ | 環境変数 | 内容 |
39
+ | --- | --- |
40
+ | `POSEREF_PORT` | 使うポート(既定は 47173)。`npx poseref setup` のときに指定すると、エージェントの登録にも引き継がれます |
41
+ | `POSEREF_NO_OPEN` | 値を入れると、ブラウザを自動で開きません |
42
+
43
+ ### Claude Code と Codex の確認
44
+
45
+ Codex は、読み取り専用ではない MCP ツールを呼ぶたびに承認を求めます。`set_scene` と `reach` は PoseRef の画面だけを変える操作で、
46
+ 「元に戻す」で取り消せるので、`npx poseref setup` はこの 2 つを承認なしで実行する設定を `~/.codex/config.toml` に追記します。
47
+ 追記の前に同じ場所へバックアップを作り、Codex が設定を読めなくなった場合は元に戻します。
48
+ この設定がないと、ポーズを直すたびに承認することになり、`codex exec` のような非対話の実行では `set_scene` がキャンセルされます。
49
+
50
+ Claude Code もツールを呼ぶたびに許可を求めることがあります。毎回の確認を省く場合は、`~/.claude/settings.json` の
51
+ `permissions.allow` に `"mcp__poseref"` を加えると、PoseRef のツールすべてが許可されます(セットアップはここを変えません)。
52
+
53
+ ### エージェントが使うツール
54
+
55
+ | ツール | 内容 |
56
+ | --- | --- |
57
+ | `get_pose_spec` | ボーンの軸と符号の決まり、接地、カメラ、プリセットの実例をまとめた説明書を返す。内容はコードの定数から生成される |
58
+ | `get_scene` | 現在のポーズ・キャラクターの向きと位置・カメラ・アスペクト比を JSON で返す |
59
+ | `set_scene` | ポーズ・キャラクター・カメラ・アスペクト比を適用する。ポーズを指定したときは、体の最下点が床にちょうど着くよう腰の高さを自動で合わせる(ジャンプなどは `pose.ground: false` で無効にできる)。未知のボーン名や範囲外の値は、何も適用せずにエラーを返す |
60
+ | `render_views` | 現在のカメラ・正面・側面・真上などを 1 枚の PNG に並べて返す。同じ画像を `~/.poseref/renders` にも保存し、そのパスを返す |
61
+ | `reach` | 逆運動学(IK)。「右肘を右膝の上へ」「左手のひらを腰へ」のように、関節の目印を別の目印や座標へ動かすよう骨を回す。肘と膝は自然な方向にだけ曲がり、胸・首・頭は可動域の範囲内に収める。届かなかった目標は残りの距離とともに返す |
62
+ | `get_joint_positions` | 肘・手のひら・膝・足裏・顎などの位置(メートル)と、体の最下点の高さを返す。指定した 2 点の距離も計算し、手が顎に触れているか、足が床に着いているかを数値で確かめるのに使う |
63
+
64
+ エージェントは `set_scene` と `render_views` を繰り返し、描画結果を見ながらポーズを直します。
65
+ 仕上がりは「大まかに合っている」程度を想定しているので、細部は手で調整してください。
66
+ AI による変更は、ヘッダーの「元に戻す」(または Ctrl+Z)で 1 回ずつ取り消せます。
67
+ `render_views` が画像をファイルにも保存するのは、MCP の画像をモデルに渡せないクライアントでも、
68
+ エージェントがそのファイルを開いて確認できるようにするためです。
69
+ PoseRef のタブを複数開いている場合は、最後に操作したタブが対象になります。
70
+
71
+ ### 仕組みとセキュリティ
72
+
73
+ エージェントが起動する `npx poseref mcp` は、エージェントとは標準入出力(stdio)で MCP をやり取りします。
74
+ ブラウザへの指示だけを、バックグラウンドで動く PoseRef サーバー(`http://127.0.0.1:47173`)に送ります。
75
+ サーバーはエージェントのセッションが終わっても動き続けるので、別のセッションからも同じタブを使えます。
76
+
77
+ - サーバーは `127.0.0.1` でのみ待ち受け、同じ PC の外からは接続できません。`Host` と `Origin` を確かめ、ほかの Web サイトからの操作を拒否します。
78
+ - `~/.poseref/secret` に、利用者ごとの秘密鍵を本人だけが読める権限で作ります。`poseref mcp` は、ポートにいる相手がこの鍵を持つ PoseRef であることを確かめてから通信し、別のプログラムがポートを使っていた場合は何も送りません。サーバーの内部用の受け口(`/poseref/*`)は、この鍵がないと使えません。
79
+ - `render_views` の画像は `~/.poseref/renders` に最新 20 枚まで残します。
80
+ - npm パッケージには、ビルド済みの画面(`dist/`)とサーバー(`dist-server/`)だけが入っています。インストール時に実行されるスクリプトはありません。
81
+
82
+ ### アンインストール
83
+
84
+ ```bash
85
+ npx poseref stop
86
+ claude mcp remove poseref --scope user
87
+ codex mcp remove poseref
88
+ ```
89
+
90
+ 最後に、必要なら `~/.poseref` フォルダを削除してください。
91
+ `codex mcp remove` で承認設定が残った場合は、`~/.codex/config.toml` の `[mcp_servers.poseref.tools.*]` の行を消してください。
92
+
93
+ ## 開発
94
+
95
+ ```bash
96
+ git clone https://github.com/Termnix-IT/PoseRef.git
97
+ cd PoseRef
98
+ npm install
99
+ npm run dev
100
+ ```
101
+
102
+ | コマンド | 内容 |
103
+ | --- | --- |
104
+ | `npm run dev` | Vite 開発サーバー(`http://localhost:5173`)。MCP の HTTP エンドポイントも 47173 番で同時に起動する |
105
+ | `npm run build` | 型チェック、画面のビルド(`dist/`)、サーバーのビルド(`dist-server/cli.js`) |
106
+ | `npm start` | ビルドしてから、公開版と同じ `dist-server/cli.js` で PoseRef を起動する |
107
+ | `npm test` | IK・体の位置の計算・認証・内部用の受け口・セットアップのテスト(ブラウザ不要) |
108
+ | `npm run typecheck` | 型チェックのみ |
109
+ | `npm run preview` | ビルド結果のプレビュー(AI 連携なし) |
110
+
111
+ `npm run dev` と `npm start`、エージェントが起動した PoseRef は、同じ 47173 番を使います。開発を始める前に `npx poseref stop` で止めてください。
112
+
113
+ 開発中は、エージェントを MCP の HTTP エンドポイントで登録すると、ビルドせずに試せます。
114
+ リポジトリの `.mcp.json` に登録を入れてあるので、このリポジトリの中で Claude Code を起動すればそのまま使えます(初回に使用を許可するかどうかを聞かれます)。
115
+ ほかの場所から使う場合は次のように登録します。
116
+
117
+ ```bash
118
+ claude mcp add --transport http --scope user poseref-dev http://127.0.0.1:47173/mcp
119
+ ```
120
+
121
+ Codex の場合は `~/.codex/config.toml` に次を追記します。
122
+
123
+ ```toml
124
+ [mcp_servers.poseref-dev]
125
+ url = "http://127.0.0.1:47173/mcp"
126
+
127
+ [mcp_servers.poseref-dev.tools.set_scene]
128
+ approval_mode = "approve"
129
+
130
+ [mcp_servers.poseref-dev.tools.reach]
131
+ approval_mode = "approve"
132
+ ```
133
+
134
+ npm への公開は `npm publish` で行います。`prepublishOnly` でビルドとテストが走ります。
135
+ 公開する中身は、事前に `npm pack --dry-run` で確かめられます。
136
+
137
+ ## 技術スタック
138
+
139
+ - React 19 / TypeScript / Vite
140
+ - Three.js / React Three Fiber / @react-three/drei
141
+ - Zustand(状態管理)
142
+ - Node.js サーバー(`server/`): `poseref` コマンド、MCP サーバー、ブラウザへの中継。MCP SDK v2 / ws / zod
143
+
144
+ ## 画面構成
145
+
146
+ | 領域 | 内容 |
147
+ | --- | --- |
148
+ | 左 | 3D プレビュー(表示範囲 = 書き出し範囲。アスペクト比に合わせてレターボックス表示) |
149
+ | 右 | ポーズ / キャラクター / カメラ / アスペクト比 / 背景 / 書き出し |
150
+ | 下 | AIプロンプト生成(日本語 / English を切り替え、見出しクリックで開閉) |
151
+
152
+ ### 操作モード
153
+
154
+ ヘッダーの「カメラ操作 / ポーズ操作」で、3Dビュー上の左ドラッグが何をするかを切り替えます。
155
+
156
+ カメラ操作モードでは、どこをドラッグしてもカメラが動きます。ポーズが崩れる心配なくアングルを探せます。
157
+ ポーズ操作モードでは、マネキンの体や青いハンドルをドラッグすると、その部位のボーンが回転します。
158
+ 背景をドラッグしたときはこのモードでもカメラが回るので、アングルを変えながらポーズを直せます。
159
+
160
+ ハンドルはボーンの先端に置かれた青い球で、ポーズ操作モードのときだけ表示されます。
161
+ 肘のハンドルを引くと上腕が、手首のハンドルを引くと前腕が振られます。顔の前のハンドルは首の向き、
162
+ 胸のハンドルは上半身の傾きに対応します。腰にはハンドルがなく、体全体の向きはキャラクターパネルの
163
+ Yawか、骨盤そのもののドラッグで変えます。
164
+
165
+ 回転は常に画面に対して自然な向きに入ります。画面の奥行き方向へ動かしたいときは、
166
+ いったんカメラを回してからドラッグしてください。
167
+
168
+ ### 3D ビュー操作
169
+
170
+ - 左ドラッグ: カメラ回転(ポーズ操作モードではマネキン上のみ関節回転)/ 右ドラッグ: 移動 / ホイール: ズーム
171
+ - マネキンの部位をクリックするとそのボーンが選択され、右パネルに回転スライダーが表示されます
172
+ - 何もない場所をクリックすると選択解除
173
+ - ハンドルや選択中のハイライトは書き出したPNGには含まれません
174
+
175
+ ### ポーズプリセット(24種)
176
+
177
+ | グループ | プリセット |
178
+ | --- | --- |
179
+ | 立ち | Tポーズ / 立ち / 腰に手 / 腕組み / 後ろ手 / モデル立ち / 振り返り |
180
+ | 仕草 | ピース / 手を振る / 指差し / 考える / 敬礼 / バンザイ / 後頭部に手 |
181
+ | 動き | 歩く / 走る / ジャンプ / 構え |
182
+ | 座り・寝 | 椅子に座る / 体育座り / 片膝立ち / 正座 / しゃがむ / 仰向け |
183
+
184
+ プリセット適用後も各ボーンを個別に調整できます(調整するとプリセット表示は「カスタム」になります)。
185
+ 「振り返り」はポーズのみを変えるので、カメラプリセット「背面」と組み合わせると振り返り構図になります。
186
+
187
+ ### その他の機能
188
+
189
+ - 16 ボーンの X/Y/Z 回転(スライダー + 数値入力、またはビュー上のドラッグ)
190
+ - キャラクターの位置 (X/Y/Z) と向き (Yaw)、マネキンの色(背景とのコントラスト調整用)
191
+ - カメラプリセット: 正面 / 背面 / 左側面 / 右側面 / ハイアングル / ローアングル / 斜め前
192
+ - 画角 (FOV)、オービット(水平 / 上下 / 距離)、位置 / 注視点の数値指定
193
+ - アスペクト比: 1:1 / 16:9 / 9:16 / 4:3 / 3:4(1024px 基準、2048px も選択可)
194
+ - 背景: 白 / グレー / 黒 / 透過、床グリッドと接地影の ON/OFF
195
+ - PNG 書き出し(UI・選択ハイライトを含まない 3D ビューのみ)、画像のクリップボードコピー
196
+ - プロンプト生成(日本語 / English の切り替え、構図情報の自動追記 ON/OFF)
197
+ 見出しをクリックすると折りたためます。畳んでいる間も言語切り替えとコピーは使え、
198
+ その分3Dビューが縦に広がります。
199
+ - すべてリセット(ポーズ・キャラクター・カメラ・画角・アスペクト比を初期化)
200
+ - 元に戻す(Ctrl+Z): AI による変更・プリセット・リセットを 1 つずつ取り消します。
201
+ スライダーやドラッグでの調整は取り消しの対象になりません。
202
+
203
+ ## ディレクトリ構成
204
+
205
+ ```
206
+ server/ `poseref` コマンド(cli.ts)、PoseRef サーバー、MCP ツール、ブラウザとの中継、認証、セットアップ、Vite 用プラグイン
207
+ test/ node:test のテスト(npm test)
208
+ src/
209
+ agent/ サーバーとの WebSocket 接続と、やり取りするメッセージの型
210
+ components/
211
+ layout/ Header, Sidebar
212
+ panels/ Pose / Character / Camera / AspectRatio / Background / Export / Prompt
213
+ ui/ Button, SliderField, NumberInput, SegmentedControl, Section, Toggle, VectorFields
214
+ viewport/ Viewport, SceneCanvas, CameraRig, Mannequin, BoneHandle, Ground, Lights,
215
+ SceneBackground, ExportBridge
216
+ constants/ bones(ボーン階層・マネキン形状), posePresets, cameraPresets, aspectRatios, backgrounds,
217
+ prompts, uiText(UI 文言)
218
+ hooks/ useFitAspect, useExport, useCopyText, usePoseDrag(ビュー上のドラッグ操作), useUndoShortcut
219
+ store/ Zustand ストア(slices: character / pose / camera / view / export / agent)
220
+ types/ 共通型定義
221
+ utils/ math, rotation, exportPng, reviewRender(AI 確認用の複数視点描画), composition, promptBuilder,
222
+ clipboard, download
223
+ ```
224
+
225
+ ドラッグ用ハンドルの位置と大きさは `src/constants/bones.ts` の `BONE_HANDLES` で調整します。
226
+ 半径は必ずその関節まわりの体より大きくしてください。体に埋まったハンドルは見えず、クリックもできません。
227
+
228
+ `server/` と `test/` は、開発中は Node がビルドなしで直接実行し、公開用には `npm run build:server` で `dist-server/cli.js` にまとめます。
229
+ サーバーとテストから読み込む `src/` のファイル(`constants/` の一部、`utils/math.ts`・`ik.ts`・`jointPositions.ts`・`rotation.ts`、
230
+ `agent/protocol.ts`)では、実行時に必要な import に `.ts` 拡張子を付け、ブラウザ専用のモジュールを import しないでください。
231
+
232
+ UI の文言は `src/constants/uiText.ts` に集約しています。ポーズを追加する場合は
233
+ `src/constants/posePresets.ts` にプリセットを追記し、`src/types/index.ts` の `PosePresetId` に ID を加えます。
234
+
235
+ ## マネキンについて
236
+
237
+ 外部モデルには依存せず、カプセル・楕円体プリミティブを階層化してコードで生成しています。
238
+ 頭部には向きを判別するためのごく小さな鼻の膨らみのみを付けています(`src/constants/bones.ts` の
239
+ `MANNEQUIN_PARTS` で調整・削除できます)。
240
+
241
+ 座標系: キャラクターは +Z を向き、+X がキャラクターの左側です。
242
+ 四肢はローカル -Y 方向に伸びるため、腕・脚は X 回転がマイナスで前方へ振り上がります。
243
+
244
+ ## ライセンス
245
+
246
+ [MIT](LICENSE)
@@ -0,0 +1 @@
1
+ :root{--bg:#0e1014;--bg-deep:#0a0c10;--panel:#161920;--panel-2:#1c2029;--panel-3:#242a35;--border:#2a303b;--border-strong:#3a4250;--text:#e7e9ee;--text-muted:#8f97a5;--text-faint:#5f6774;--accent:#4f8cff;--accent-hover:#6a9dff;--accent-soft:#4f8cff2e;--accent-ring:#4f8cff73;--success:#3ccb7f;--danger:#ff6b6b;--radius-sm:6px;--radius:8px;--radius-lg:12px;--font:"Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Noto Sans JP", "Yu Gothic UI", sans-serif;--font-mono:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--shadow:0 10px 30px #0006;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{background:var(--bg);color:var(--text);font-family:var(--font);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:13px;line-height:1.45;overflow:hidden}button,input,textarea,select{font:inherit;color:inherit}button{cursor:pointer}button:disabled{cursor:not-allowed}h1,h2,h3,p{margin:0}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{border:2px solid var(--panel);background:#2f3541;border-radius:8px}::-webkit-scrollbar-thumb:hover{background:#3d4553}:focus-visible{outline:2px solid var(--accent-ring);outline-offset:1px}.app{grid-template-rows:48px minmax(0,1fr) auto;height:100dvh;display:grid}.header{border-bottom:1px solid var(--border);background:var(--panel);align-items:center;gap:14px;padding:0 16px;display:flex}.header__brand{align-items:center;gap:10px;display:flex}.header__logo{background:linear-gradient(135deg,#4f8cff,#7cc4ff);border-radius:7px;place-items:center;width:26px;height:26px;display:grid;box-shadow:0 2px 8px #4f8cff59}.header__logo svg{width:18px;height:18px}.header__title{letter-spacing:.02em;font-size:15px;font-weight:700}.header__tagline{color:var(--text-muted);border-left:1px solid var(--border);padding-left:14px;font-size:12px}.header__spacer{flex:1}.header__status{color:var(--text-muted);font-size:12px;font-family:var(--font-mono);align-items:center;gap:6px;display:flex}.header__agent{color:var(--text-muted);white-space:nowrap;align-items:center;gap:6px;font-size:12px;display:flex}.header__agent-dot{background:var(--text-faint);border-radius:50%;width:7px;height:7px}.header__agent.is-connected .header__agent-dot{background:var(--success)}.app__main{grid-template-columns:minmax(0,1fr) 340px;min-height:0;display:grid}.viewport{background-color:var(--bg-deep);background-image:radial-gradient(#ffffff0d 1px,#0000 1px);background-size:22px 22px;justify-content:center;align-items:center;min-width:0;min-height:0;display:flex;position:relative;overflow:hidden}.viewport__frame{box-shadow:0 0 0 1px var(--border-strong), var(--shadow);background:#7d7d7d;border-radius:4px;position:relative;overflow:hidden}.viewport__frame--transparent{background-color:#9a9a9a;background-image:linear-gradient(45deg,#707070 25%,#0000 25%),linear-gradient(-45deg,#707070 25%,#0000 25%),linear-gradient(45deg,#0000 75%,#707070 75%),linear-gradient(-45deg,#0000 75%,#707070 75%);background-position:0 0,0 10px,10px -10px,-10px 0;background-size:20px 20px}.viewport__frame canvas{display:block}.viewport__badge{-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:var(--font-mono);pointer-events:none;-webkit-user-select:none;user-select:none;background:#0a0c10bd;border-radius:6px;align-items:center;gap:8px;padding:4px 9px;font-size:11px;display:flex;position:absolute;top:10px;left:10px}.viewport__badge-label{color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em;font-size:10px}.viewport__badge-value{color:var(--text)}.viewport__hint{color:var(--text-faint);white-space:nowrap;pointer-events:none;-webkit-user-select:none;user-select:none;font-size:11px;position:absolute;bottom:8px;left:50%;transform:translate(-50%)}.sidebar{border-left:1px solid var(--border);background:var(--panel);min-height:0;overflow-y:auto}.prompt-panel{border-top:1px solid var(--border);background:var(--panel);padding:8px 16px}.prompt-panel.is-open{padding-bottom:14px}.prompt-panel__bar{align-items:center;gap:12px;min-height:34px;display:flex}.prompt-panel__toggle{border-radius:var(--radius-sm);min-width:0;color:inherit;text-align:left;background:0 0;border:0;align-items:center;gap:8px;margin-left:-6px;padding:6px;display:flex}.prompt-panel__toggle:hover{background:var(--panel-2)}.prompt-panel.is-open .section__chevron{transform:rotate(90deg)}.prompt-panel__title{letter-spacing:.02em;font-size:13px;font-weight:600}.prompt-panel__actions{flex-shrink:0;align-items:center;gap:10px;margin-left:auto;display:flex}.prompt-panel__actions .btn{min-width:108px}.prompt-panel__body{flex-direction:column;gap:7px;margin-top:6px;display:flex}.prompt-panel__meta{justify-content:space-between;align-items:center;gap:16px;display:flex}.prompt-panel__desc{color:var(--text-muted);min-width:0;font-size:12px}.prompt-panel__text{resize:vertical;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--bg);width:100%;min-height:72px;color:var(--text);padding:8px 10px;font-size:12px;line-height:1.55}.prompt-panel__text:focus{border-color:var(--accent);outline:none}@media (width<=960px){body{overflow:auto}.app{grid-template-rows:48px auto auto;height:auto;min-height:100vh}.app__main{grid-template-columns:1fr}.viewport{height:62vh}.sidebar{border-left:0;border-top:1px solid var(--border);overflow:visible}.prompt-panel__meta{flex-direction:column;align-items:flex-start;gap:8px}.header__tagline{display:none}}.section{border-bottom:1px solid var(--border)}.section__header{align-items:center;gap:8px;min-height:40px;padding:0 10px 0 6px;display:flex}.section__toggle{text-align:left;border-radius:var(--radius-sm);background:0 0;border:0;flex:1;align-items:center;gap:8px;min-width:0;padding:6px;display:flex}.section__toggle:hover{background:var(--panel-2)}.section__chevron{width:12px;color:var(--text-faint);font-size:11px;transition:transform .15s;display:inline-block}.section.is-open .section__chevron{transform:rotate(90deg)}.section__title{letter-spacing:.02em;font-size:13px;font-weight:600}.section__subtitle{color:var(--text-muted);font-size:11px;font-family:var(--font-mono);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.section__actions{flex-shrink:0;gap:4px;display:flex}.section__body{flex-direction:column;gap:12px;padding:2px 14px 14px;display:flex}.control-group{flex-direction:column;gap:8px;display:flex}.control-group__label{color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em;justify-content:space-between;align-items:center;font-size:11px;font-weight:600;display:flex}.hint{color:var(--text-muted);font-size:12px;line-height:1.5}.hint--mono{font-family:var(--font-mono);font-size:11px}.status{min-height:16px;font-size:12px}.status--success{color:var(--success)}.status--error{color:var(--danger)}.status--muted{color:var(--text-muted)}.btn{border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--panel-2);height:32px;color:var(--text);white-space:nowrap;justify-content:center;align-items:center;gap:6px;padding:0 12px;font-size:12.5px;font-weight:500;transition:background .12s,border-color .12s,color .12s;display:inline-flex}.btn:hover:not(:disabled){background:var(--panel-3);border-color:var(--border-strong)}.btn:disabled{opacity:.5}.btn--sm{height:26px;padding:0 9px;font-size:12px}.btn--block{width:100%}.btn--primary{background:var(--accent);border-color:var(--accent);color:#fff}.btn--primary:hover:not(:disabled){background:var(--accent-hover);border-color:var(--accent-hover)}.btn--ghost{color:var(--text-muted);background:0 0;border-color:#0000}.btn--ghost:hover:not(:disabled){color:var(--text);background:var(--panel-2);border-color:#0000}.btn.is-active{background:var(--accent-soft);border-color:var(--accent);color:#dbe8ff}.preset-grid{gap:6px;display:grid}.preset-grid--2{grid-template-columns:repeat(2,minmax(0,1fr))}.preset-grid--3{grid-template-columns:repeat(3,minmax(0,1fr))}.preset-grid--4{grid-template-columns:repeat(4,minmax(0,1fr))}.preset-group{flex-direction:column;gap:5px;display:flex}.preset-group__label{color:var(--text-faint);font-size:11px}.preset-grid .btn{text-overflow:ellipsis;padding:0 6px;overflow:hidden}.field{flex-direction:column;gap:4px;display:flex}.field__head{justify-content:space-between;align-items:center;display:flex}.field__label{color:var(--text);font-size:12px}.field__reset{color:var(--text-faint);background:0 0;border:0;border-radius:4px;padding:2px 4px;font-size:13px;line-height:1}.field__reset:hover{color:var(--text);background:var(--panel-2)}.field__controls{align-items:center;gap:10px;display:flex}.slider{appearance:none;cursor:pointer;background:0 0;flex:1;min-width:0;height:18px;margin:0}.slider::-webkit-slider-runnable-track{background:var(--panel-3);border-radius:2px;height:4px}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:var(--text);border:2px solid var(--accent);border-radius:50%;width:14px;height:14px;margin-top:-5px;box-shadow:0 1px 3px #00000073}.slider::-moz-range-track{background:var(--panel-3);border-radius:2px;height:4px}.slider::-moz-range-thumb{background:var(--text);border:2px solid var(--accent);border-radius:50%;width:10px;height:10px}.number-input{flex-shrink:0;align-items:center;gap:3px;display:flex}.number-input__field{border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--bg);width:64px;height:26px;color:var(--text);font-family:var(--font-mono);text-align:right;appearance:textfield;padding:0 6px;font-size:12px}.number-input__field::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.number-input__field::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.number-input__field:focus{border-color:var(--accent);outline:none}.number-input__unit{color:var(--text-faint);width:12px;font-size:11px}.vector-row{grid-template-columns:58px repeat(3,minmax(0,1fr));align-items:center;gap:6px;display:grid}.vector-row__label{font-size:12px}.vector-row__axis{align-items:center;gap:4px;min-width:0;display:flex}.vector-row__axis-name{color:var(--text-faint);font-family:var(--font-mono);font-size:11px}.vector-row .number-input{flex:1;min-width:0}.vector-row .number-input__field{width:100%}.segmented{border-radius:var(--radius);background:var(--bg);border:1px solid var(--border);gap:2px;padding:3px;display:flex}.segmented__item{height:28px;color:var(--text-muted);background:0 0;border:0;border-radius:6px;flex:1;font-size:12px;font-weight:500;transition:background .12s,color .12s}.segmented__item:hover{color:var(--text)}.segmented--compact{padding:2px}.segmented--compact .segmented__item{flex:none;height:24px;padding:0 10px}.segmented__item.is-active{background:var(--panel-3);color:var(--text);box-shadow:0 1px 2px #00000059}.toggle{cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:8px;display:inline-flex}.toggle__input{opacity:0;width:0;height:0;position:absolute}.toggle__track{background:var(--panel-3);border:1px solid var(--border);border-radius:9px;width:32px;height:18px;transition:background .15s;position:relative}.toggle__thumb{background:var(--text-muted);border-radius:50%;width:12px;height:12px;transition:transform .15s,background .15s;position:absolute;top:2px;left:2px}.toggle__input:checked+.toggle__track{background:var(--accent);border-color:var(--accent)}.toggle__input:checked+.toggle__track .toggle__thumb{background:#fff;transform:translate(14px)}.toggle__input:focus-visible+.toggle__track{outline:2px solid var(--accent-ring);outline-offset:1px}.toggle__label{font-size:12px}.toggle-row{flex-wrap:wrap;gap:16px;display:flex}.bone-picker{flex-direction:column;gap:4px;display:flex}.bone-picker__center{grid-template-columns:repeat(4,minmax(0,1fr));gap:4px;margin-bottom:4px;display:grid}.bone-picker__pair{grid-template-columns:1fr 76px 76px;align-items:center;gap:4px;display:grid}.bone-picker__pair-label{color:var(--text-muted);padding-left:2px;font-size:12px}.bone-picker__side{text-align:center;color:var(--text-faint);font-size:10px;font-family:var(--font-mono);letter-spacing:.1em}.swatches{gap:8px;display:flex}.swatch{border:2px solid var(--border);border-radius:50%;width:28px;height:28px;padding:0;transition:transform .12s,border-color .12s}.swatch:hover{transform:scale(1.08)}.swatch.is-active{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}.disclosure{border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg)}.disclosure summary{cursor:pointer;color:var(--text-muted);-webkit-user-select:none;user-select:none;padding:7px 10px;font-size:12px;list-style:none}.disclosure summary::-webkit-details-marker{display:none}.disclosure summary:before{content:"▸";width:12px;color:var(--text-faint);transition:transform .15s;display:inline-block}.disclosure[open] summary:before{transform:rotate(90deg)}.disclosure__body{flex-direction:column;gap:8px;padding:4px 10px 10px;display:flex}