@nickname4th/pura-cli 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 pura contributors
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,224 @@
1
+ # pura
2
+
3
+ ![pura social preview](assets/pura-social-preview.png)
4
+
5
+ pura is a LAN Android device mirror for product and design teams. A central Hub shows all online Android devices, while each developer runs a local Agent that talks to their own USB-connected phone through ADB.
6
+
7
+ No login, no cloud, no public tunnel. It is meant for trusted office networks.
8
+
9
+ ## Quickstart
10
+
11
+ Start the Hub with Docker Compose:
12
+
13
+ ```bash
14
+ docker compose up -d
15
+ ```
16
+
17
+ Open the Hub:
18
+
19
+ ```text
20
+ http://<hub-lan-ip>:8787
21
+ ```
22
+
23
+ On each developer machine, connect an Agent:
24
+
25
+ ```bash
26
+ npx @nickname4th/pura-cli connect <hub-lan-ip>:8787 --name "Zhang San"
27
+ ```
28
+
29
+ On macOS, keep the Agent connected after login or terminal close. Install the CLI globally first so the background service has a stable executable path, then connect with `--background`:
30
+
31
+ ```bash
32
+ npm install -g @nickname4th/pura-cli
33
+ pura-cli connect <hub-lan-ip>:8787 --name "Zhang San" --background
34
+ ```
35
+
36
+ Publish the local Android device:
37
+
38
+ ```bash
39
+ npx @nickname4th/pura-cli connect device --name "Zhang San Pixel 8" --owner "Zhang San" --note "login branch"
40
+ ```
41
+
42
+ Designers can now pick the published machine on the Hub homepage, open the live screen, and click on it with a mouse.
43
+
44
+ ## Project Site
45
+
46
+ The GitHub Pages site lives in `site/` and is deployed by `.github/workflows/pages.yml`.
47
+
48
+ After publishing the repository, enable GitHub Pages with GitHub Actions as the source. The public URL will be:
49
+
50
+ ```text
51
+ https://liutianjie.github.io/pura/
52
+ ```
53
+
54
+ ## Requirements
55
+
56
+ - Node.js 20+ for developer Agents
57
+ - Android platform-tools: `adb`
58
+ - Android USB debugging enabled and authorized on each developer machine
59
+ - Docker and Docker Compose for Hub deployment
60
+ - Hub can reach every Agent over the LAN
61
+ - A modern browser
62
+
63
+ ## Installation
64
+
65
+ Developers can use pura without installing it permanently:
66
+
67
+ ```bash
68
+ npx @nickname4th/pura-cli --help
69
+ ```
70
+
71
+ Or install globally:
72
+
73
+ ```bash
74
+ npm install -g @nickname4th/pura-cli
75
+ pura-cli --help
76
+ ```
77
+
78
+ For repository development:
79
+
80
+ ```bash
81
+ npm install
82
+ npm run build
83
+ npm link
84
+ ```
85
+
86
+ ## Hub Deployment
87
+
88
+ Recommended Docker Compose deployment:
89
+
90
+ ```bash
91
+ docker compose up -d
92
+ ```
93
+
94
+ The included compose file builds the local image by default. To use a published GHCR image:
95
+
96
+ ```bash
97
+ PURA_IMAGE=ghcr.io/liutianjie/pura:main docker compose up -d
98
+ ```
99
+
100
+ Equivalent Node.js deployment:
101
+
102
+ ```bash
103
+ pura-cli hub --host 0.0.0.0 --port 8787
104
+ ```
105
+
106
+ ## Developer Agent
107
+
108
+ Each developer connects their local Agent to the Hub:
109
+
110
+ ```bash
111
+ pura-cli connect 192.168.100.128:8787 --name "Zhang San"
112
+ ```
113
+
114
+ The Agent listens on `8788` by default and continuously reports local ADB devices to the Hub.
115
+
116
+ If the Hub cannot reach the auto-detected Agent URL, specify it:
117
+
118
+ ```bash
119
+ pura-cli connect 192.168.100.128:8787 --name "Zhang San" --public-url http://192.168.100.45:8788
120
+ ```
121
+
122
+ The Agent heartbeat automatically recovers after Wi-Fi or Hub restarts as long as the Agent process is still running. On macOS, install the saved Agent connection as a LaunchAgent so it starts at login and restarts if the terminal is closed:
123
+
124
+ ```bash
125
+ pura-cli connect 192.168.100.128:8787 --name "Zhang San" --background
126
+ ```
127
+
128
+ Check or remove the background service:
129
+
130
+ ```bash
131
+ pura-cli auto-connect --status
132
+ pura-cli auto-connect --uninstall
133
+ ```
134
+
135
+ ## Publish Device
136
+
137
+ Connect a phone over USB and confirm it is authorized:
138
+
139
+ ```bash
140
+ adb devices -l
141
+ ```
142
+
143
+ Then publish it:
144
+
145
+ ```bash
146
+ pura-cli connect device --name "Zhang San Pixel 8" --owner "Zhang San" --note "login branch"
147
+ ```
148
+
149
+ If multiple Android devices are connected:
150
+
151
+ ```bash
152
+ pura-cli connect device --serial RFCY10DHQ3P --name "Samsung S25" --owner "Li Si"
153
+ ```
154
+
155
+ ## Runtime Model
156
+
157
+ - Hub maintains online Agents and devices, serves the web UI, and proxies video WebSocket/tap requests.
158
+ - Agent runs on each developer machine and owns ADB, screen capture, tap execution, and device metadata.
159
+ - CLI commands:
160
+ - `pura-cli hub`
161
+ - `pura-cli connect <hub>`
162
+ - `pura-cli auto-connect`
163
+ - `pura-cli connect device`
164
+ - `pura-cli devices`
165
+
166
+ ## API
167
+
168
+ Hub:
169
+
170
+ - `POST /api/agents/heartbeat`
171
+ - `GET /api/devices`
172
+ - `POST /api/devices/:deviceId/session`
173
+ - `POST /api/devices/:deviceId/tap`
174
+ - `PUT /api/devices/:deviceId/publication`
175
+ - `DELETE /api/devices/:deviceId/publication`
176
+ - `DELETE /api/sessions/:id`
177
+ - `WS /ws/sessions/:id/video`
178
+
179
+ Agent:
180
+
181
+ - `GET /api/devices`
182
+ - `POST /api/devices/:serial/session`
183
+ - `POST /api/devices/:serial/tap`
184
+ - `PUT /api/devices/:serial/publication`
185
+ - `DELETE /api/devices/:serial/publication`
186
+ - `DELETE /api/sessions/:id`
187
+ - `WS /ws/sessions/:id/video`
188
+
189
+ ## Environment
190
+
191
+ - `ROLE=hub|agent|standalone`
192
+ - `HOST=0.0.0.0`
193
+ - `PORT=8787`
194
+ - `HUB_URL=http://<hub-ip>:8787`
195
+ - `AGENT_ID`
196
+ - `AGENT_NAME`
197
+ - `PUBLIC_URL=http://<agent-ip>:8788`
198
+ - `ADB_PATH=adb`
199
+ - `STREAM_SIZE` optional; unset uses native device resolution
200
+ - `STREAM_BITRATE=8000000`
201
+ - `STREAM_TIME_LIMIT_SECONDS=180`
202
+ - `INCLUDE_TCP_DEVICES=true`
203
+ - `DATA_DIR=data-agent`
204
+
205
+ ## Publishing
206
+
207
+ The npm package is `@nickname4th/pura-cli` and installs the `pura-cli` binary.
208
+
209
+ Release flow:
210
+
211
+ 1. Update `version` in `package.json`.
212
+ 2. Run `npm run check`, `npm run build`, and `npm pack --dry-run`.
213
+ 3. Push a tag like `v0.1.0`.
214
+ 4. GitHub Actions publishes `pura-cli` to npm and `ghcr.io/liutianjie/pura` to GHCR.
215
+
216
+ The release workflow requires an `NPM_TOKEN` repository secret.
217
+
218
+ ## Notes
219
+
220
+ - The current video path uses Android `screenrecord` H.264 output. No Android app or root is required.
221
+ - Mouse control currently supports tap only.
222
+ - Do not expose Hub or Agent ports directly to the public internet.
223
+ - Agent Docker is intentionally not the default because local USB/ADB access is much smoother with native `pura-cli`.
224
+ - Some Android builds enforce `screenrecord` time limits; the Agent restarts the stream automatically when it exits.
@@ -0,0 +1 @@
1
+ :root{font-family:Avenir Next,DIN Alternate,Helvetica Neue,sans-serif;color:#e8ece7;background:#101211;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;--panel: #171a18;--panel-2: #20231f;--line: #343b34;--muted: #98a49a;--text: #e8ece7;--green: #9dff74;--amber: #ffbf47;--red: #ff6b62;--steel: #7bb4d6;--button: #d6ff59;--button-text: #15190f;--ease-out: cubic-bezier(.2, 0, 0, 1);--ease-emphasized: cubic-bezier(.05, .7, .1, 1)}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh;background:linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px) 0 0 / 28px 28px,linear-gradient(0deg,rgba(255,255,255,.02) 1px,transparent 1px) 0 0 / 28px 28px,#101211}button{font:inherit}.shell{display:grid;grid-template-columns:320px minmax(0,1fr);min-height:100vh}.rail{display:flex;flex-direction:column;gap:18px;min-height:100vh;padding:22px;background:#121512f0;border-right:1px solid var(--line);animation:panelIn .52s var(--ease-emphasized) both}.brand{display:flex;align-items:center;gap:12px;min-width:0;animation:riseIn .48s var(--ease-emphasized) both}.brandMark{display:grid;place-items:center;width:42px;height:42px;color:var(--button-text);background:var(--button);border-radius:7px}.brand h1,.brand p,.topbar h2,.topbar p{margin:0}.brand h1{font-size:19px;line-height:1.1;letter-spacing:0}.brand p{margin-top:4px;color:var(--muted);font-size:12px;text-transform:uppercase}.refreshButton,.languageButton,.deviceItem,.primary,.secondary{display:inline-flex;align-items:center;justify-content:center;gap:9px;min-height:40px;border:1px solid var(--line);border-radius:7px;cursor:pointer;transition:transform .16s var(--ease-out),border-color .18s var(--ease-out),background-color .18s var(--ease-out),box-shadow .18s var(--ease-out),opacity .18s var(--ease-out)}.railControls{display:grid;grid-template-columns:1fr auto;gap:8px;animation:riseIn .52s var(--ease-emphasized) 80ms both}.refreshButton,.languageButton{color:var(--text);background:#1d211d}.languageButton{min-width:74px;padding:0 12px}.refreshButton:hover,.languageButton:hover,.secondary:hover:not(:disabled){border-color:#617057;background:#242a24;transform:translateY(-1px)}.refreshButton:hover svg{animation:spinOnce .62s var(--ease-emphasized)}.primary:hover:not(:disabled){box-shadow:0 10px 28px #d6ff5933;transform:translateY(-1px)}.refreshButton:active,.languageButton:active,.primary:active:not(:disabled),.secondary:active:not(:disabled),.deviceItem:active{transform:translateY(0) scale(.985)}.deviceList{display:flex;flex-direction:column;gap:9px;min-height:0;overflow:auto;animation:riseIn .52s var(--ease-emphasized) .13s both}.deviceList.compact{max-height:260px}.sectionLabel{display:inline-flex;align-items:center;gap:7px;color:#b5c0b4;font-size:12px;font-weight:700;text-transform:uppercase}.deviceItem{width:100%;display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center;justify-content:stretch;padding:12px;color:var(--text);background:#171b17;text-align:left;animation:listItemIn .36s var(--ease-emphasized) both}.deviceItem:hover,.deviceItem.selected{border-color:#5f7258;background:#202720;transform:translate(3px)}.deviceItem.selected{box-shadow:inset 3px 0 0 var(--button),0 10px 24px #0000002e}.deviceItem.published{border-color:#9dff7442}.deviceSelectButton{display:flex;align-items:center;gap:9px;min-width:0;padding:0;color:inherit;background:transparent;border:0;cursor:pointer;text-align:left}.deviceManageButton,.iconButton{display:inline-grid;place-items:center;width:32px;height:32px;color:#cbd7c7;background:#ffffff09;border:1px solid rgba(255,255,255,.08);border-radius:7px;cursor:pointer;transition:color .16s var(--ease-out),background-color .16s var(--ease-out),border-color .16s var(--ease-out),transform .16s var(--ease-out)}.deviceManageButton{opacity:0;transform:translate(4px)}.deviceItem:hover .deviceManageButton,.deviceItem.selected .deviceManageButton,.deviceManageButton:focus-visible{opacity:1;transform:translate(0)}.deviceManageButton:hover,.iconButton:hover{color:var(--button);background:#d6ff591a;border-color:#d6ff5957}.deviceCopy{display:grid;min-width:0}.deviceCopy strong,.deviceCopy small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.deviceCopy strong{font-size:14px}.deviceCopy small{margin-top:3px;color:var(--muted);font-family:SFMono-Regular,Consolas,monospace;font-size:11px}.dot{width:9px;height:9px;margin-left:auto;border-radius:50%;background:var(--red);transition:transform .18s var(--ease-out),box-shadow .18s var(--ease-out)}.dot.device{background:var(--green);box-shadow:0 0 #9dff7400;animation:onlinePulse 2.2s ease-in-out infinite}.dot.unauthorized{background:var(--amber)}.emptyState{display:grid;place-items:center;gap:10px;min-width:min(260px,100%);min-height:140px;padding:24px 28px;color:var(--muted);border:1px dashed var(--line);border-radius:7px;animation:fadeIn .42s var(--ease-out) both}.emptyState.small{min-width:0;min-height:76px;padding:18px;font-size:12px}.workspace{display:flex;flex-direction:column;min-width:0;padding:22px;animation:workspaceIn .56s var(--ease-emphasized) 70ms both}.topbar{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:14px;animation:riseIn .52s var(--ease-emphasized) .12s both}.eyebrow{color:var(--steel);font-size:12px;text-transform:uppercase}.topbar h2{margin-top:5px;font-size:clamp(22px,3vw,34px);line-height:1.05;letter-spacing:0}.actions{display:flex;gap:10px}.viewTabs{display:inline-flex;gap:4px;padding:4px;background:#111411eb;border:1px solid var(--line);border-radius:8px;animation:riseIn .52s var(--ease-emphasized) .15s both}.viewTabs button{min-height:32px;padding:0 12px;color:var(--muted);background:transparent;border:0;border-radius:6px;cursor:pointer;transition:color .16s var(--ease-out),background-color .16s var(--ease-out),transform .16s var(--ease-out)}.viewTabs button:hover{color:var(--text);transform:translateY(-1px)}.viewTabs button.active{color:var(--button-text);background:var(--button)}.primary,.secondary{padding:0 15px;white-space:nowrap}.primary{color:var(--button-text);background:var(--button);border-color:var(--button);font-weight:700}.secondary{color:var(--text);background:#1d211d}a.secondary,a.iconButton{text-decoration:none}.secondary.danger{color:#ffd8d4;border-color:#ff6b625c}.secondary.danger:hover:not(:disabled){background:#ff6b621f;border-color:#ff6b628c}.primary:disabled,.secondary:disabled{cursor:not-allowed;opacity:.45}.metaStrip{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px}.infoPill{display:inline-flex;align-items:center;gap:7px;min-height:30px;padding:0 10px;color:#c6cec4;background:#20231fdb;border:1px solid var(--line);border-radius:999px;font-size:12px;animation:chipIn .36s var(--ease-emphasized) both;transition:border-color .18s var(--ease-out),transform .18s var(--ease-out)}.infoPill:hover{border-color:#586355;transform:translateY(-1px)}.infoPill.tone-link{color:#cfe8f8;background:#7bb4d621;border-color:#7bb4d657}.infoPill.tone-screen{color:#f8e4bd;background:#ffbf471f;border-color:#ffbf4752}.infoPill.tone-ready{color:#dbffd0;background:#9dff741f;border-color:#9dff7457}.infoPill.tone-warning{color:#ffe3bd;background:#ffbf4721;border-color:#ffbf476b}.infoPill.tone-gesture{color:#ddd6ff;background:#9b7eff21;border-color:#9b7eff57}.infoPill.tone-agent{color:#cffff2;background:#5bd6b81f;border-color:#5bd6b857}.infoPill.tone-owner{color:#ffd5e7;background:#ff70a81f;border-color:#ff70a857}.cursorToggle{display:inline-flex;align-items:center;gap:7px;min-height:30px;padding:0 10px;color:#aeb9ad;background:#20231fbd;border:1px solid var(--line);border-radius:999px;cursor:pointer;font-size:12px;transition:color .16s var(--ease-out),background-color .16s var(--ease-out),border-color .16s var(--ease-out),transform .16s var(--ease-out)}.cursorToggle:hover{color:var(--text);border-color:#586355;transform:translateY(-1px)}.cursorToggle.active{color:#eaffc5;background:#d6ff591f;border-color:#d6ff5957}.cursorNameControl{display:inline-flex;align-items:center;gap:7px;min-height:30px;padding:0 9px;color:#c6cec4;background:#121512c7;border:1px solid var(--line);border-radius:999px;transition:border-color .16s var(--ease-out),box-shadow .16s var(--ease-out),background-color .16s var(--ease-out)}.cursorNameControl:focus-within{background:#181d17e6;border-color:#d6ff596b;box-shadow:0 0 0 2px #d6ff591a}.cursorNameControl input{width:96px;padding:0;color:var(--text);background:transparent;border:0;outline:none;font-size:12px}.annotationTools{display:inline-flex;gap:4px;padding:4px;background:#111411bd;border:1px solid var(--line);border-radius:999px}.annotationTools button{display:inline-flex;align-items:center;gap:6px;min-height:24px;padding:0 8px;color:#aeb9ad;background:transparent;border:0;border-radius:999px;cursor:pointer;font-size:12px;transition:color .16s var(--ease-out),background-color .16s var(--ease-out),transform .16s var(--ease-out)}.annotationTools button:hover{color:var(--text);background:#ffffff0b;transform:translateY(-1px)}.annotationTools button.active{color:var(--button-text);background:var(--button)}.errorBanner{margin-bottom:14px;padding:12px 14px;color:#ffe5e2;background:#ff6b6229;border:1px solid rgba(255,107,98,.45);border-radius:7px;animation:errorIn .34s var(--ease-out) both}.screenshotResult{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin:-4px 0 14px;padding:8px;width:fit-content;color:#dfe9dc;background:#121512d1;border:1px solid var(--line);border-radius:8px}.screenshotResult span,.screenshotResult a,.screenshotResult button{display:inline-flex;align-items:center;gap:7px;min-height:30px;font-size:12px}.phoneGrid{display:grid;gap:16px;min-height:0;animation:riseIn .56s var(--ease-emphasized) .18s both}.phoneGrid.empty{place-items:center;min-height:420px}.gridSummary{display:inline-flex;align-items:baseline;gap:8px;width:fit-content;color:var(--muted);font-size:13px}.gridSummary strong{color:var(--text);font-size:28px;line-height:1}.phoneCards{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:14px}.phoneCard{display:grid;gap:12px;padding:14px;color:var(--text);background:#171b17e0;border:1px solid var(--line);border-radius:8px;text-align:left;animation:listItemIn .42s var(--ease-emphasized) both;transition:transform .18s var(--ease-out),border-color .18s var(--ease-out),background-color .18s var(--ease-out),box-shadow .18s var(--ease-out)}.phoneOpenArea{display:grid;gap:12px;width:100%;padding:0;color:inherit;background:transparent;border:0;text-align:left;cursor:pointer}.phoneCard:hover,.phoneCard.selected{border-color:#d6ff5994;background:#1f261ef0;box-shadow:0 18px 40px #0000003d;transform:translateY(-3px)}.phonePreview{position:relative;display:grid;place-items:center;min-height:230px;padding:12px;background:radial-gradient(circle at 50% 18%,rgba(214,255,89,.1),transparent 34%),#0b0d0b;border:1px solid rgba(255,255,255,.07);border-radius:8px;overflow:hidden}.phoneChrome{position:relative;display:grid;width:min(58%,150px);min-width:92px;max-height:250px;padding:10px 7px 9px;background:linear-gradient(180deg,#222822,#0b0d0b);border:1px solid #3d463a;border-radius:18px;box-shadow:0 18px 45px #00000061;transition:transform .24s var(--ease-out),box-shadow .24s var(--ease-out)}.phoneCard:hover .phoneChrome{box-shadow:0 22px 54px #0000007a;transform:translateY(-2px) scale(1.015)}.speaker{position:absolute;top:5px;left:50%;width:30%;height:3px;background:#4b5447;border-radius:99px;transform:translate(-50%)}.previewScreen{position:relative;display:grid;place-items:center;align-content:center;gap:8px;min-height:0;color:#b9c6b5;background:linear-gradient(160deg,rgba(123,180,214,.16),transparent 42%),linear-gradient(20deg,rgba(214,255,89,.1),transparent 48%),#111511;border:1px solid #30382e;border-radius:12px;overflow:hidden}.previewImage{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;background:#060706;opacity:0;transition:opacity .22s var(--ease-out)}.previewImage.loaded{opacity:1}.previewLoading{position:absolute;inset:0;background:linear-gradient(115deg,transparent 0%,rgba(255,255,255,.07) 38%,transparent 68%) -140% 0 / 240% 100%,radial-gradient(circle at 60% 18%,rgba(214,255,89,.1),transparent 34%),#070907;animation:previewSweep 1.4s var(--ease-out) infinite}.previewScreen small{position:relative;z-index:1;color:var(--muted);font-family:SFMono-Regular,Consolas,monospace;font-size:11px}.previewScreen>svg{position:relative;z-index:1}.previewSignal{position:absolute;right:9px;top:9px;z-index:2;width:8px;height:8px;background:var(--red);border-radius:50%}.previewSignal.device{background:var(--green);animation:onlinePulse 2.2s ease-in-out infinite}.previewSignal.unauthorized{background:var(--amber)}.liveTag{position:absolute;right:10px;top:10px;min-height:24px;padding:4px 8px;color:var(--button-text);background:var(--button);border-radius:999px;font-size:11px;font-weight:800}.phoneMeta{display:grid;gap:4px;min-width:0}.phoneMeta strong,.phoneMeta span,.phoneMeta small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.phoneMeta strong{font-size:15px}.phoneMeta span,.phoneMeta small{color:var(--muted);font-size:12px}.phoneFooter{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px 12px;color:#cbd4c8;font-size:12px}.phoneFooter span{display:inline-flex;align-items:center;gap:7px;flex:1 1 72px;min-width:0}.phoneFooter .dot{flex:0 0 9px;margin-left:0}.phoneActions{display:inline-flex;flex:0 0 auto;flex-wrap:nowrap;gap:6px;margin-left:auto}.phoneAction{display:inline-flex;align-items:center;justify-content:center;min-width:max-content;min-height:32px;padding:0 11px;color:#dbe5d8;background:#1d211d;border:1px solid var(--line);border-radius:6px;cursor:pointer;font-size:12px;line-height:1;white-space:nowrap;transition:background-color .16s var(--ease-out),border-color .16s var(--ease-out),transform .16s var(--ease-out),opacity .16s var(--ease-out)}.phoneAction:hover:not(:disabled){background:#242a24;border-color:#617057;transform:translateY(-1px)}.phoneAction.danger{color:#ffd8d4;border-color:#ff6b6257}.phoneAction:disabled{cursor:not-allowed;opacity:.42}.managerScrim{position:fixed;inset:0;z-index:50;display:grid;place-items:center end;padding:22px;background:#04060480;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);animation:fadeIn .18s var(--ease-out) both}.managerPanel{display:grid;gap:16px;width:min(390px,calc(100vw - 44px));padding:16px;color:var(--text);background:#161916f5;border:1px solid rgba(214,255,89,.22);border-radius:8px;box-shadow:0 24px 80px #00000075;animation:managerIn .26s var(--ease-emphasized) both}.managerHeader{display:flex;align-items:center;justify-content:space-between;gap:12px}.managerHeader div{display:grid;gap:4px;min-width:0}.managerHeader span{color:var(--steel);font-size:12px;font-weight:800;text-transform:uppercase}.managerHeader strong{overflow:hidden;font-size:20px;line-height:1.1;text-overflow:ellipsis;white-space:nowrap}.managerForm{display:grid;gap:12px}.managerForm label{display:grid;gap:6px}.managerForm label span{display:inline-flex;align-items:center;gap:6px;color:#b8c4b6;font-size:12px;font-weight:800}.managerForm input{width:100%;min-height:40px;padding:0 11px;color:var(--text);background:#111411;border:1px solid #394037;border-radius:7px;outline:none;transition:border-color .16s var(--ease-out),box-shadow .16s var(--ease-out),background-color .16s var(--ease-out)}.managerForm input:focus{border-color:var(--button);box-shadow:0 0 0 2px #d6ff5924;background:#151a14}.managerActions{display:flex;justify-content:flex-end;gap:8px;padding-top:4px}.managerScreenshots{display:grid;gap:10px}.managerSectionHeader{display:flex;align-items:center;justify-content:space-between;gap:10px}.managerSectionHeader span{color:#b8c4b6;font-size:12px;font-weight:800}.managerSectionHeader .secondary{min-height:32px;padding:0 10px}.screenshotEmpty{padding:14px;color:var(--muted);background:#ffffff06;border:1px dashed var(--line);border-radius:7px;font-size:12px;text-align:center}.screenshotList{display:grid;gap:8px;max-height:260px;overflow:auto}.screenshotItem{display:grid;grid-template-columns:42px minmax(0,1fr) auto auto;gap:9px;align-items:center;padding:8px;background:#ffffff06;border:1px solid var(--line);border-radius:7px}.screenshotItem img{width:42px;height:58px;object-fit:cover;background:#060706;border:1px solid rgba(255,255,255,.08);border-radius:5px}.screenshotItem div{display:grid;gap:3px;min-width:0}.screenshotItem strong,.screenshotItem span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.screenshotItem strong{color:var(--text);font-size:12px}.screenshotItem span{color:var(--muted);font-size:11px}.focusSurface{display:grid;grid-template-columns:minmax(220px,280px) minmax(0,1fr);gap:14px;align-items:stretch;min-height:0}.controlPanel{display:grid;gap:10px;align-content:start;margin-bottom:0;padding:12px;background:#171a18d1;border:1px solid var(--line);border-radius:8px;animation:riseIn .52s var(--ease-emphasized) .21s both}.controlGroup{display:grid;grid-template-columns:1fr 1fr;gap:8px}.controlLabel{grid-column:1 / -1;color:#b5c0b4;font-size:12px;font-weight:800;text-transform:uppercase}.controlButton{display:inline-flex;align-items:center;justify-content:center;gap:7px;min-height:34px;padding:0 10px;color:var(--text);background:#1d211d;border:1px solid var(--line);border-radius:7px;cursor:pointer;font-size:12px;white-space:nowrap;transition:transform .15s var(--ease-out),border-color .16s var(--ease-out),background-color .16s var(--ease-out),opacity .16s var(--ease-out)}.controlButton:hover:not(:disabled){border-color:#617057;background:#242a24;transform:translateY(-1px)}.controlButton:active:not(:disabled){transform:scale(.98)}.controlButton:disabled{cursor:not-allowed;opacity:.42}.textControl{display:grid;grid-template-columns:1fr;gap:8px;align-items:center;padding-top:2px}.textControl>svg{display:none}.textControl input{width:100%;min-height:36px;padding:0 10px;color:var(--text);background:#111411;border:1px solid #394037;border-radius:7px;outline:none;transition:border-color .16s var(--ease-out),box-shadow .16s var(--ease-out)}.textControl input:focus{border-color:var(--button);box-shadow:0 0 0 2px #d6ff5924}.publishPanel{display:grid;grid-template-columns:minmax(160px,1fr) minmax(140px,.7fr) minmax(220px,1.2fr) auto;gap:10px;align-items:end;margin-bottom:14px;padding:12px;background:#171a18d1;border:1px solid var(--line);border-radius:8px;animation:riseIn .52s var(--ease-emphasized) .18s both}.publishPanel label{display:grid;gap:6px;min-width:0}.publishPanel label span{display:inline-flex;align-items:center;gap:6px;color:#b5c0b4;font-size:12px;font-weight:700}.publishPanel input{width:100%;min-height:38px;padding:0 10px;color:var(--text);background:#111411;border:1px solid #394037;border-radius:7px;outline:none;transition:border-color .16s var(--ease-out),box-shadow .16s var(--ease-out),background-color .16s var(--ease-out)}.publishPanel input:focus{border-color:#d6ff59;box-shadow:0 0 0 2px #d6ff5924;background:#151a14}.publishActions{display:flex;gap:8px}.stage{min-height:0;display:grid;place-items:center;animation:riseIn .56s var(--ease-emphasized) .24s both}.screenFrame{position:relative;width:min(100%,1100px);aspect-ratio:16 / 10;min-height:360px;max-height:calc(100vh - 180px);background:#060706;border:1px solid #2f352f;border-radius:8px;overflow:hidden;box-shadow:0 24px 90px #0000006b;transition:border-color .26s var(--ease-out),box-shadow .26s var(--ease-out),transform .26s var(--ease-out)}.screenFrame:hover{border-color:#46513f;box-shadow:0 28px 100px #0000007a;transform:translateY(-1px)}.screen{display:block;width:100%;height:100%;object-fit:contain;background:#050605;cursor:crosshair;touch-action:none;-webkit-user-select:none;user-select:none}.statusBadge{position:absolute;right:13px;top:13px;display:inline-flex;align-items:center;gap:8px;min-height:30px;padding:0 10px;color:#dce5da;background:#0a0c0ac2;border:1px solid rgba(255,255,255,.12);border-radius:999px;font-size:12px;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);animation:badgeIn .26s var(--ease-emphasized) both}.statusBadge span{width:8px;height:8px;border-radius:50%;background:var(--muted)}.statusBadge.live span{background:var(--green);box-shadow:0 0 14px var(--green)}.statusBadge.connecting span{background:var(--amber);animation:connectingPulse .88s ease-in-out infinite}.statusBadge.error span{background:var(--red)}.standby{position:absolute;inset:0;display:grid;place-items:center;align-content:center;gap:12px;color:#aab4a9;background:linear-gradient(135deg,rgba(214,255,89,.06),transparent 40%),#050605eb;animation:fadeIn .42s var(--ease-out) both}.tapPulse{position:absolute;width:26px;height:26px;margin:-13px 0 0 -13px;pointer-events:none;border:2px solid var(--button);border-radius:50%;animation:pulse .52s var(--ease-emphasized) forwards}.annotationLayer{position:absolute;inset:0;z-index:3;width:100%;height:100%;pointer-events:none}.annotationRect,.annotationDraw{fill:none;stroke:var(--annotation-color);stroke-linecap:round;stroke-linejoin:round;vector-effect:non-scaling-stroke;filter:drop-shadow(0 1px 2px rgba(0,0,0,.34))}.annotationRect{fill:color-mix(in srgb,var(--annotation-color) 14%,transparent);stroke-width:2.5px;stroke-dasharray:7 5}.annotationDraw{stroke-width:4px}.remoteCursor{position:absolute;z-index:4;display:inline-flex;align-items:center;gap:5px;color:var(--cursor-color);pointer-events:none;filter:drop-shadow(0 1px 3px rgba(0,0,0,.38));transform:translate(2px,2px);animation:cursorPop .18s var(--ease-emphasized) both}.remoteCursor svg{fill:#00000057;stroke-width:2.5}.remoteCursor small{max-width:120px;overflow:hidden;padding:4px 8px;color:#0d110d;background:var(--cursor-color);border-radius:999px;font-size:13px;font-weight:800;line-height:1;text-overflow:ellipsis;white-space:nowrap}.deviceItem:nth-of-type(2){animation-delay:45ms}.deviceItem:nth-of-type(3){animation-delay:90ms}.deviceItem:nth-of-type(4){animation-delay:135ms}.deviceItem:nth-of-type(5){animation-delay:.18s}@keyframes panelIn{0%{opacity:0;transform:translate(-18px)}to{opacity:1;transform:translate(0)}}@keyframes workspaceIn{0%{opacity:0;transform:translateY(18px) scale(.992)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes riseIn{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}@keyframes listItemIn{0%{opacity:0;transform:translate(-10px)}to{opacity:1;transform:translate(0)}}@keyframes chipIn{0%{opacity:0;transform:translateY(6px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes badgeIn{0%{opacity:0;transform:translateY(-6px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes managerIn{0%{opacity:0;transform:translate(14px) scale(.985)}to{opacity:1;transform:translate(0) scale(1)}}@keyframes cursorPop{0%{opacity:0;transform:translate(2px,2px) scale(.92)}to{opacity:1;transform:translate(2px,2px) scale(1)}}@keyframes previewSweep{to{background-position:140% 0,0 0,0 0}}@keyframes errorIn{0%{opacity:0;transform:translate(-8px)}65%{transform:translate(2px)}to{opacity:1;transform:translate(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes spinOnce{to{transform:rotate(360deg)}}@keyframes onlinePulse{0%,to{box-shadow:0 0 #9dff7400}45%{box-shadow:0 0 16px #9dff7485}}@keyframes connectingPulse{0%,to{transform:scale(.82);opacity:.75}50%{transform:scale(1.18);opacity:1}}@keyframes pulse{0%{opacity:.95;transform:scale(.45)}to{opacity:0;transform:scale(1.8)}}@media(max-width:820px){.shell{grid-template-columns:1fr}.rail{min-height:auto;border-right:0;border-bottom:1px solid var(--line);animation-name:riseIn}.workspace{padding:16px}.topbar{align-items:stretch;flex-direction:column}.actions{width:100%}.actions button{flex:1}.screenFrame{min-height:260px;max-height:none}.focusSurface,.publishPanel{grid-template-columns:1fr}.controlGroup{grid-template-columns:repeat(2,minmax(0,1fr))}.publishActions{width:100%}.publishActions button{flex:1}}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:1ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:1ms!important}}