dsh-mobile 0.4.0 → 0.4.2

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/lib/client.js CHANGED
@@ -20,7 +20,6 @@ window.__ModuleLoader__.load({
20
20
  pluginUpdatedRestart: "Updated to v{version}. Restart DSH to apply it.",
21
21
  pluginUpdateFailed: "Update failed: {error}",
22
22
  updateTo: "Update to v{version}",
23
- updateNotesHeading: "What’s new",
24
23
  updateNotice: "Restart DSH after installing. Existing apps and paired devices need no re-pairing; unsure about the desktop version? Check the README compatibility table.",
25
24
  updateNotesEmpty: "No release notes fetched — updating is still fine; full details on the GitHub Release page.",
26
25
  updateNow: "Update now",
@@ -39,6 +38,7 @@ window.__ModuleLoader__.load({
39
38
  clearAllDevices: "Clear all devices",
40
39
  pairingQr: "Pairing QR code",
41
40
  remoteIntro: "Choose the remote channel that suits you. Switching or disabling remote access does not affect the local network.",
41
+ httpFrameWarning: "HTTP pages may be embedded for compatibility, but their contents are unencrypted and can be altered. Use HTTPS for sensitive work.",
42
42
  chooseProvider: "Choose connection method",
43
43
  currentProvider: "Current connection",
44
44
  remoteStateOff: "Off",
@@ -188,7 +188,6 @@ window.__ModuleLoader__.load({
188
188
  pluginUpdatedRestart: "Aggiornato alla v{version}. Riavvia DSH per applicarlo.",
189
189
  pluginUpdateFailed: "Aggiornamento non riuscito: {error}",
190
190
  updateTo: "Aggiorna alla v{version}",
191
- updateNotesHeading: "Novità",
192
191
  updateNotice: "Riavvia DSH dopo l’installazione. Le app esistenti e i dispositivi già associati non richiedono ri-associazione; in caso di dubbi sulla versione desktop, consulta la tabella di compatibilità nel README.",
193
192
  updateNotesEmpty: "Note di rilascio non recuperate — l’aggiornamento resta possibile; dettagli nella pagina Release su GitHub.",
194
193
  updateNow: "Aggiorna ora",
@@ -207,6 +206,7 @@ window.__ModuleLoader__.load({
207
206
  clearAllDevices: "Rimuovi tutti i dispositivi",
208
207
  pairingQr: "Codice QR di abbinamento",
209
208
  remoteIntro: "Scegli il canale remoto più adatto. Cambiare o disattivare l’accesso remoto non influisce sulla rete locale.",
209
+ httpFrameWarning: "Le pagine HTTP possono essere incorporate per compatibilità, ma i contenuti non sono cifrati e possono essere alterati. Usa HTTPS per le attività sensibili.",
210
210
  chooseProvider: "Scegli metodo di connessione",
211
211
  currentProvider: "Connessione attuale",
212
212
  remoteStateOff: "Disattivata",
@@ -358,7 +358,6 @@ window.__ModuleLoader__.load({
358
358
  pluginUpdatedRestart: "插件已更新至 v{version},重启 DSH 后生效。",
359
359
  pluginUpdateFailed: "更新失败:{error}",
360
360
  updateTo: "更新到 v{version}",
361
- updateNotesHeading: "更新内容",
362
361
  updateNotice: "安装后需重启 DSH。现有 App 与已配对设备无需重新配对;电脑端版本不确定时,先对照 README 兼容表。",
363
362
  updateNotesEmpty: "未取到最新版更新说明,仍可继续更新;详情见 GitHub Release 页。",
364
363
  updateNow: "立即更新",
@@ -377,6 +376,7 @@ window.__ModuleLoader__.load({
377
376
  clearAllDevices: "清除所有设备",
378
377
  pairingQr: "配对二维码",
379
378
  remoteIntro: "选择更适合你的远程通道。切换或关闭远程访问不会影响局域网。",
379
+ httpFrameWarning: "为兼容性允许嵌入 HTTP 页面,但内容未加密,可能被篡改;处理敏感内容请使用 HTTPS。",
380
380
  chooseProvider: "选择连接方式",
381
381
  currentProvider: "当前连接",
382
382
  remoteStateOff: "未启用",
@@ -560,7 +560,6 @@ window.__ModuleLoader__.load({
560
560
  });
561
561
  Object.assign(MOBILE_CONTROL_MESSAGES.en, {
562
562
  selfHostedConnections: "Self-hosted connection",
563
- selfHostedDescription: "For users with a VPS and domain. It does not depend on public tunnel bandwidth.",
564
563
  advanced: "Advanced",
565
564
  frpName: "Self-hosted FRP",
566
565
  frpDescription: "A restricted FRP channel for this DSH gateway only. You manage the VPS and public domain.",
@@ -648,7 +647,6 @@ window.__ModuleLoader__.load({
648
647
  });
649
648
  Object.assign(MOBILE_CONTROL_MESSAGES.it, {
650
649
  selfHostedConnections: "Connessione autogestita",
651
- selfHostedDescription: "Per chi dispone di VPS e dominio. Non dipende dalla banda dei tunnel pubblici.",
652
650
  advanced: "Avanzato",
653
651
  frpName: "FRP autogestito",
654
652
  frpDescription: "Canale FRP limitato al solo gateway DSH. VPS e dominio pubblico restano sotto il tuo controllo.",
@@ -736,7 +734,6 @@ window.__ModuleLoader__.load({
736
734
  });
737
735
  Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
738
736
  selfHostedConnections: "自建连接",
739
- selfHostedDescription: "适合已有 VPS 的用户,可使用域名或公网 IPv4,不受公共服务带宽限制。",
740
737
  advanced: "高级",
741
738
  frpName: "自建 FRP",
742
739
  frpDescription: "只为当前 DSH 网关建立单用途 FRP 通道,VPS 和公开入口由你掌控。",
@@ -1037,6 +1034,336 @@ window.__ModuleLoader__.load({
1037
1034
  generated: "生成时间"
1038
1035
  }
1039
1036
  };
1037
+ Object.assign(MOBILE_CONTROL_MESSAGES.en, {
1038
+ selfHostedDescription: "Use your own VPS or HTTPS reverse proxy without a public tunnel service.",
1039
+ originName: "Own reverse proxy",
1040
+ originDescription: "You manage HTTPS. DSH Mobile provides a private, authenticated HTTP backend; no tunnel component is installed.",
1041
+ prepareOrigin: "Connect your HTTPS reverse proxy",
1042
+ originPublicOrigin: "Public HTTPS address",
1043
+ originListenHost: "Private listen IPv4",
1044
+ originListenPort: "HTTP backend port",
1045
+ originAllowedCidrs: "Allowed proxy source CIDRs",
1046
+ originHelp: "Same computer: use 127.0.0.1. For a proxy on another LAN device, use this computer’s private IPv4 and allow only the proxy’s direct source IPv4/32. Separate CIDRs with spaces or commas.",
1047
+ originWarning: "Keep this HTTP backend private. Never expose or port-forward it to the Internet. Your proxy must terminate HTTPS and preserve the external Host, including its port, and WebSocket upgrades.",
1048
+ originSaveStart: "Save and start backend",
1049
+ originBackendSaved: "Saved HTTP backend (not running)",
1050
+ originBackendListening: "Listening HTTP backend",
1051
+ originCopyBackend: "Copy backend address",
1052
+ originBackendCopied: "HTTP backend address copied.",
1053
+ originPurge: "Clear proxy settings",
1054
+ originPurgeConfirm: "Stop this private HTTP backend and delete only its reverse-proxy settings? Paired remote devices, LAN access, and other providers are kept.",
1055
+ originPurged: "The backend is stopped and its proxy settings are cleared. Paired devices are kept.",
1056
+ originSaving: "Saving settings and starting the private backend…",
1057
+ originPurging: "Stopping the backend and clearing its settings…",
1058
+ originBackendReady: "Backend listening",
1059
+ originReady: "The private HTTP backend is listening. Public HTTPS, its certificate, and WebSocket connectivity have NOT been verified. Test the public address from your phone before relying on it.",
1060
+ originUnavailable: "Configure the public HTTPS address and private backend first.",
1061
+ originStarting: "Starting the private HTTP backend…",
1062
+ switchingOrigin: "Switching to your own reverse proxy…",
1063
+ reconnectingOrigin: "Restarting the private HTTP backend…",
1064
+ resetOriginConfirm: "Stop this backend and reset remote pairing? All paired remote devices must pair again. Reverse-proxy settings and LAN devices are kept.",
1065
+ originPublicOriginInvalid: "Enter a public HTTPS origin only, such as https://phone.example.com:8815; no path, query, credentials, or private address.",
1066
+ originListenHostInvalid: "Use an explicit loopback or private IPv4 address on this computer, not 0.0.0.0 or a public address.",
1067
+ originListenPortInvalid: "Use a whole-number port from 1024 to 65535; lower ports need privileges the plugin does not have.",
1068
+ originListenPortReserved: "Port 3443 belongs to the LAN gateway and port 3080 to DSH itself, so both are always taken. Choose another port, such as 3444 or 3445.",
1069
+ originAllowedCidrsInvalid: "Allow 1–16 valid loopback/private IPv4 CIDRs with no host bits. For a LAN listener, include the actual proxy source IPv4/32; public ranges are not accepted.",
1070
+ originSettingsInvalid: "The reverse-proxy settings are invalid. Check the four fields and save again.",
1071
+ originConfigMissing: "Save the reverse-proxy settings before starting the backend.",
1072
+ originConfigInvalid: "The saved settings are invalid. Correct and save them again, or clear only these settings.",
1073
+ originPortInUse: "The backend port is already in use. Stop the conflicting listener or choose another port. Note that 3443, 3080, and a port already used by a cloudflared tunnel are never free.",
1074
+ originAddressUnavailable: "The listen address is not available on this computer. Choose its current private IPv4.",
1075
+ originStartFailed: "The private backend could not start. Check its address, port, permissions, and local logs.",
1076
+ originConfigTargetInvalid: "The saved proxy settings file is not a regular file. Remove it and save the settings again.",
1077
+ originControllerUnavailable: "The reverse-proxy provider is not available in this session. Restart DSH and try again."
1078
+ });
1079
+ Object.assign(MOBILE_CONTROL_MESSAGES.it, {
1080
+ selfHostedDescription: "Usa il tuo VPS o reverse proxy HTTPS senza un servizio di tunnel pubblico.",
1081
+ originName: "Reverse proxy proprio",
1082
+ originDescription: "Gestisci tu HTTPS. DSH Mobile offre un backend HTTP privato con autenticazione; non installa componenti tunnel.",
1083
+ prepareOrigin: "Collega il tuo reverse proxy HTTPS",
1084
+ originPublicOrigin: "Indirizzo HTTPS pubblico",
1085
+ originListenHost: "IPv4 privato di ascolto",
1086
+ originListenPort: "Porta backend HTTP",
1087
+ originAllowedCidrs: "CIDR sorgente del proxy consentiti",
1088
+ originHelp: "Stesso computer: usa 127.0.0.1. Per un proxy su un altro dispositivo LAN, usa l’IPv4 privato di questo computer e consenti solo l’IPv4 sorgente diretta del proxy con /32. Separa i CIDR con spazi o virgole.",
1089
+ originWarning: "Mantieni privato il backend HTTP. Non esporlo né inoltrarne la porta su Internet. Il proxy deve terminare HTTPS e conservare l’Host esterno, inclusa la porta, e gli upgrade WebSocket.",
1090
+ originSaveStart: "Salva e avvia backend",
1091
+ originBackendSaved: "Backend HTTP salvato (non attivo)",
1092
+ originBackendListening: "Backend HTTP in ascolto",
1093
+ originCopyBackend: "Copia indirizzo backend",
1094
+ originBackendCopied: "Indirizzo backend HTTP copiato.",
1095
+ originPurge: "Cancella impostazioni proxy",
1096
+ originPurgeConfirm: "Fermare questo backend HTTP privato ed eliminare solo le sue impostazioni reverse proxy? Dispositivi remoti abbinati, accesso LAN e altri provider vengono conservati.",
1097
+ originPurged: "Backend fermato e impostazioni proxy cancellate. I dispositivi abbinati sono conservati.",
1098
+ originSaving: "Salvataggio impostazioni e avvio del backend privato…",
1099
+ originPurging: "Arresto del backend e cancellazione impostazioni…",
1100
+ originBackendReady: "Backend in ascolto",
1101
+ originReady: "Il backend HTTP privato è in ascolto. HTTPS pubblico, certificato e connettività WebSocket NON sono stati verificati. Prova l’indirizzo pubblico dal telefono prima di usarlo.",
1102
+ originUnavailable: "Configura prima l’indirizzo HTTPS pubblico e il backend privato.",
1103
+ originStarting: "Avvio del backend HTTP privato…",
1104
+ switchingOrigin: "Passaggio al reverse proxy proprio…",
1105
+ reconnectingOrigin: "Riavvio del backend HTTP privato…",
1106
+ resetOriginConfirm: "Fermare il backend e reimpostare gli abbinamenti remoti? Tutti i dispositivi remoti dovranno essere abbinati di nuovo. Le impostazioni proxy e i dispositivi LAN vengono conservati.",
1107
+ originPublicOriginInvalid: "Inserisci solo un’origine HTTPS pubblica, ad esempio https://phone.example.com:8815, senza percorso, query, credenziali o indirizzo privato.",
1108
+ originListenHostInvalid: "Usa un IPv4 loopback o privato esplicito di questo computer, non 0.0.0.0 né un indirizzo pubblico.",
1109
+ originListenPortInvalid: "Usa una porta intera da 1024 a 65535; le porte più basse richiedono privilegi che il plugin non ha.",
1110
+ originListenPortReserved: "La porta 3443 appartiene al gateway LAN e la 3080 a DSH stesso, quindi sono sempre occupate. Scegli un’altra porta, ad esempio 3444 o 3445.",
1111
+ originAllowedCidrsInvalid: "Consenti 1–16 CIDR IPv4 loopback/privati validi senza bit host. Per l’ascolto LAN includi l’IPv4 sorgente reale del proxy con /32; le reti pubbliche non sono ammesse.",
1112
+ originSettingsInvalid: "Impostazioni reverse proxy non valide. Controlla i quattro campi e salva di nuovo.",
1113
+ originConfigMissing: "Salva le impostazioni reverse proxy prima di avviare il backend.",
1114
+ originConfigInvalid: "Impostazioni salvate non valide. Correggile e salvale di nuovo, oppure cancella solo queste impostazioni.",
1115
+ originPortInUse: "La porta backend è già occupata. Ferma il processo in conflitto o scegli un’altra porta. 3443, 3080 e una porta già usata da un tunnel cloudflared non sono mai libere.",
1116
+ originAddressUnavailable: "L’indirizzo di ascolto non è disponibile su questo computer. Scegli il suo IPv4 privato attuale.",
1117
+ originStartFailed: "Impossibile avviare il backend privato. Controlla indirizzo, porta, permessi e log locali.",
1118
+ originConfigTargetInvalid: "Il file delle impostazioni proxy salvato non è un file regolare. Rimuovilo e salva di nuovo le impostazioni.",
1119
+ originControllerUnavailable: "Il provider reverse proxy non è disponibile in questa sessione. Riavvia DSH e riprova."
1120
+ });
1121
+ Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
1122
+ selfHostedDescription: "使用自己的 VPS 或 HTTPS 反向代理,不依赖公共隧道服务。",
1123
+ originName: "自有反向代理",
1124
+ originDescription: "HTTPS 由你管理,插件提供带认证的私有 HTTP 后端,无需安装隧道组件。",
1125
+ prepareOrigin: "接入自有 HTTPS 反向代理",
1126
+ originPublicOrigin: "公网 HTTPS 地址",
1127
+ originListenHost: "私有监听 IPv4",
1128
+ originListenPort: "HTTP 后端端口",
1129
+ originAllowedCidrs: "允许的代理来源 CIDR",
1130
+ originHelp: "代理与 DSH 同机时用 127.0.0.1;代理在另一台局域网设备上时,填写本机私有 IPv4,并只允许代理实际直连来源 IPv4/32。多个 CIDR 用空格或逗号分隔。",
1131
+ originWarning: "HTTP 后端必须留在私网,绝不能直接暴露公网或做公网端口映射。反向代理负责 HTTPS,并须保留外部 Host(含端口)及 WebSocket 升级。",
1132
+ originSaveStart: "保存并启动后端",
1133
+ originBackendSaved: "已保存的 HTTP 后端(未运行)",
1134
+ originBackendListening: "正在监听的 HTTP 后端",
1135
+ originCopyBackend: "复制后端地址",
1136
+ originBackendCopied: "已复制 HTTP 后端地址。",
1137
+ originPurge: "清除代理配置",
1138
+ originPurgeConfirm: "停止此私有 HTTP 后端,并且只删除它的反向代理配置?已配对的远程设备、局域网访问和其他提供方均保留。",
1139
+ originPurged: "已停止后端并清除代理配置,已配对设备仍保留。",
1140
+ originSaving: "正在保存配置并启动私有后端…",
1141
+ originPurging: "正在停止后端并清除配置…",
1142
+ originBackendReady: "后端已监听",
1143
+ originReady: "私有 HTTP 后端已监听,但尚未验证公网 HTTPS、证书及 WebSocket 连通性。请先用手机测试公网地址,不代表公网连接已成功。",
1144
+ originUnavailable: "请先配置公网 HTTPS 地址和私有后端。",
1145
+ originStarting: "正在启动私有 HTTP 后端…",
1146
+ switchingOrigin: "正在切换到自有反向代理…",
1147
+ reconnectingOrigin: "正在重启私有 HTTP 后端…",
1148
+ resetOriginConfirm: "停止此后端并重置远程配对?所有已配对的远程设备都需要重新配对;反向代理配置和局域网设备保留。",
1149
+ originPublicOriginInvalid: "只填写公网 HTTPS 源地址,例如 https://phone.example.com:8815;不能含路径、查询参数、账号密码或私有地址。",
1150
+ originListenHostInvalid: "填写本机明确的回环或私有 IPv4,不能使用 0.0.0.0 或公网地址。",
1151
+ originListenPortInvalid: "端口必须是 1024–65535 的整数;更低的端口需要插件没有的特权。",
1152
+ originListenPortReserved: "3443 属于局域网网关、3080 属于 DSH 自身,两者始终被占用。请换一个端口,例如 3444 或 3445。",
1153
+ originAllowedCidrsInvalid: "填写 1–16 个规范的回环/私有 IPv4 CIDR,不能有主机位;局域网监听时须包含代理实际来源 IPv4/32,不接受公网网段。",
1154
+ originSettingsInvalid: "反向代理配置无效,请检查四个字段后重新保存。",
1155
+ originConfigMissing: "请先保存反向代理配置,再启动后端。",
1156
+ originConfigInvalid: "已保存的配置无效,请修正并重新保存,或仅清除这项配置。",
1157
+ originPortInUse: "后端端口已被占用,请停止冲突进程或改用其他端口。3443、3080 以及已被 cloudflared 隧道占用的端口永远不会空闲。",
1158
+ originAddressUnavailable: "本机没有这个监听地址,请选择当前本机的私有 IPv4。",
1159
+ originStartFailed: "私有后端启动失败,请检查地址、端口、权限和本地日志。",
1160
+ originConfigTargetInvalid: "已保存的代理配置文件不是普通文件。请移除后重新保存设置。",
1161
+ originControllerUnavailable: "当前会话里反向代理提供方不可用。请重启 DSH 后重试。"
1162
+ });
1163
+ Object.assign(MOBILE_CONTROL_MESSAGES.en, {
1164
+ cloudflaredBadge: "Account optional",
1165
+ cloudflaredDescription: "Installs the official cloudflared client on demand. A quick tunnel needs no account and gives a temporary public address; a named tunnel uses a Cloudflare account token and keeps one hostname that survives restarts.",
1166
+ prepareCloudflared: "Prepare cloudflared",
1167
+ cloudflaredUnsupported: "Only Windows x64 is currently supported. You can still use the built-in Tailscale Funnel.",
1168
+ cloudflaredNotInstalled: "Not installed. A fixed version is downloaded from the official cloudflared release page only after you click the button below.",
1169
+ cloudflaredReady: "Official component {version} is ready.",
1170
+ cloudflaredQuickNote: "Quick tunnel: no Cloudflare account is needed, but Cloudflare positions quick tunnels for testing — the address is random, rate-limited, and carries no uptime guarantee. Do not rely on one for access that must stay reachable.",
1171
+ cloudflaredNamedNote: "Named tunnel: the hostname above stays the same across restarts and Cloudflare routes it to the local forward port. The connector token is stored only in the DSH Mobile private directory, is handed to cloudflared through its environment rather than the command line, and is never shown again once saved.",
1172
+ cloudflaredComponentNote: "Downloaded from the official cloudflared release page and verified at a fixed version only after you choose Install. The client is started with automatic updates disabled. It does not add a system service, startup item, registry entry, or PATH entry.",
1173
+ installConfirmCloudflared: "Download and verify cloudflared {size} from the official release page and store it only in the DSH Mobile private directory? No system service, PATH/registry entry, or startup item is added.",
1174
+ installingCloudflared: "Installing the official cloudflared component. Keep DSH running until it completes.",
1175
+ purgeCloudflared: "Completely remove the cloudflared component",
1176
+ purgeCloudflaredConfirm: "Completely remove the cloudflared component and its runtime logs from the DSH Mobile private directory? Local network access, DSH data, and other system programs are unaffected.",
1177
+ purgingCloudflared: "Stopping the channel and removing cloudflared files managed by DSH Mobile…",
1178
+ switchingCloudflared: "Switching to cloudflared…",
1179
+ reconnectingCloudflared: "Reconnecting cloudflared…",
1180
+ remoteUnavailableCloudflared: "cloudflared is not installed.",
1181
+ remoteStartingCloudflared: "Starting the cloudflared connector…",
1182
+ remoteConnectingCloudflared: "Starting the DSH authentication gateway…",
1183
+ resetCloudflaredConfirm: "Disable the cloudflared remote channel and remove all remote paired devices? The installed component and the local network configuration are unchanged.",
1184
+ cloudflaredMissing: "The official cloudflared component is not installed. Complete the preparation steps above.",
1185
+ cloudflaredInvalid: "cloudflared component verification failed. Remove it completely and reinstall.",
1186
+ cloudflaredPortUnavailable: "Could not allocate a local remote-gateway port. Try again.",
1187
+ cloudflaredLaunchFailed: "The cloudflared client failed to start.",
1188
+ cloudflaredTimeout: "Timed out waiting for the tunnel to become reachable. Reconnect.",
1189
+ cloudflaredStopped: "The cloudflared connection stopped.",
1190
+ cloudflaredExited: "The cloudflared connection exited unexpectedly. Reconnect.",
1191
+ cloudflaredOutputInvalid: "cloudflared returned an unrecognized status.",
1192
+ cloudflaredOriginInvalid: "The public address returned by cloudflared failed validation.",
1193
+ cloudflaredComponentUnsupported: "This build cannot run the cloudflared component.",
1194
+ cloudflaredDownloadHashMismatch: "The downloaded cloudflared failed its SHA-256 verification. Retry the install.",
1195
+ cloudflaredDownloadSizeMismatch: "The downloaded cloudflared is not the pinned size. Retry the install.",
1196
+ cloudflaredDownloadRedirectMissing: "The download page stopped redirecting to the release asset. Retry later, or install from the official page manually.",
1197
+ cloudflaredDownloadRedirectInvalid: "The download redirect did not point at a GitHub release asset, so it was refused.",
1198
+ cloudflaredDownloadRedirectRejected: "The download redirect was refused by policy. Retry later, or install from the official page manually.",
1199
+ cloudflaredExecutableHashMismatch: "The installed cloudflared no longer matches the verified build. Remove it completely and install again.",
1200
+ cloudflaredPortReservationFailed: "Could not reserve a local port for the tunnel. Try again.",
1201
+ cloudflaredTunnelTargetInvalid: "The saved tunnel configuration is not a regular file. Remove it and save the settings again.",
1202
+ cloudflaredTunnelSaving: "Saving the tunnel settings and reconnecting cloudflared…",
1203
+ cloudflaredTunnelForgetting: "Removing the saved tunnel token…",
1204
+ cloudflaredTunnelTitle: "Tunnel type",
1205
+ cloudflaredTunnelQuick: "Quick",
1206
+ cloudflaredTunnelNamed: "Named",
1207
+ cloudflaredTunnelQuickHint: "No account: cloudflared allocates a random address on every start.",
1208
+ cloudflaredTunnelNamedHint: "Uses a Cloudflare account token and keeps one address that survives restarts.",
1209
+ cloudflaredTunnelHostname: "Public hostname",
1210
+ cloudflaredTunnelHostnamePlaceholder: "dsh.example.com",
1211
+ cloudflaredTunnelPort: "Local forward port",
1212
+ cloudflaredTunnelToken: "Connector token",
1213
+ cloudflaredTunnelTokenPlaceholder: "Paste the tunnel token from the Cloudflare dashboard",
1214
+ cloudflaredTunnelTokenSaved: "A token is saved. Leave blank to keep it.",
1215
+ cloudflaredTunnelSave: "Save and connect",
1216
+ cloudflaredTunnelSaveQuick: "Switch to a quick tunnel",
1217
+ cloudflaredTunnelConfigured: "Named tunnel configured: {hostname} reaches this computer on 127.0.0.1:{port}.",
1218
+ cloudflaredTunnelMissing: "No named tunnel configured yet.",
1219
+ cloudflaredTunnelHint: "In the Cloudflare dashboard create a tunnel, then set its public hostname to http://127.0.0.1:{port}. The token is stored only in the DSH Mobile private directory and is never shown again after saving.",
1220
+ cloudflaredTunnelForget: "Remove the saved token",
1221
+ cloudflaredTunnelForgetConfirm: "Remove the saved tunnel token, stop the cloudflared channel, and go back to a quick tunnel? Nothing reconnects on its own: switch the channel back on afterwards. The tunnel still exists in your Cloudflare account, and its public hostname is unchanged.",
1222
+ cloudflaredTunnelInvalid: "The saved named-tunnel configuration is unreadable; the provider is using a quick tunnel. Save the settings again.",
1223
+ cloudflaredTunnelPortUnavailable: "The configured local forward port is unavailable. Cloudflare routes the public hostname to that exact port, so free it or choose another one.",
1224
+ cloudflaredTunnelHostnameInvalid: "Enter a public hostname under a domain on this Cloudflare account, for example dsh.example.com.",
1225
+ cloudflaredTunnelPortInvalid: "Enter a forward port between 1024 and 65535.",
1226
+ cloudflaredTunnelPortReserved: "Port 3443 is reserved for the DSH Mobile LAN gateway and is always occupied. Choose another port, such as 3444 or 3445.",
1227
+ cloudflaredTunnelTokenInvalid: "That does not look like a tunnel token. Copy the whole token from the Cloudflare dashboard.",
1228
+ cloudflaredTunnelSettingsInvalid: "The tunnel settings were rejected.",
1229
+ cloudflaredTunnelConfigMissing: "Enter the token, hostname, and port to use a named tunnel."
1230
+ });
1231
+ Object.assign(MOBILE_CONTROL_MESSAGES.it, {
1232
+ cloudflaredBadge: "Account facoltativo",
1233
+ cloudflaredDescription: "Installa su richiesta il client cloudflared ufficiale. Il tunnel rapido non richiede account e fornisce un indirizzo pubblico temporaneo; il tunnel con nome usa il token di un account Cloudflare e mantiene un hostname stabile tra i riavvii.",
1234
+ prepareCloudflared: "Prepara cloudflared",
1235
+ cloudflaredUnsupported: "Attualmente è supportato solo Windows x64. Puoi comunque usare Tailscale Funnel integrato.",
1236
+ cloudflaredNotInstalled: "Non installato. Una versione fissa viene scaricata dalla pagina ufficiale di cloudflared solo dopo aver premuto il pulsante.",
1237
+ cloudflaredReady: "Componente ufficiale {version} pronto.",
1238
+ cloudflaredQuickNote: "Tunnel rapido: non serve un account Cloudflare, ma Cloudflare li considera pensati per i test — l’indirizzo è casuale, soggetto a limiti di frequenza e senza garanzia di disponibilità. Non usarlo per un accesso che deve restare raggiungibile.",
1239
+ cloudflaredNamedNote: "Tunnel con nome: l’hostname qui sopra resta lo stesso tra i riavvii e Cloudflare lo instrada verso la porta di inoltro locale. Il token del connettore è salvato solo nella directory privata DSH Mobile, viene passato a cloudflared tramite l’ambiente e non tramite la riga di comando, e dopo il salvataggio non viene più mostrato.",
1240
+ cloudflaredComponentNote: "Viene scaricato dalla pagina ufficiale di cloudflared e verificato a una versione fissa solo dopo aver scelto Installa. Il client viene avviato con gli aggiornamenti automatici disattivati. Non aggiunge servizi di sistema, avvio automatico, voci di registro o PATH.",
1241
+ installConfirmCloudflared: "Scaricare e verificare cloudflared {size} dalla pagina ufficiale e salvarlo solo nella directory privata DSH Mobile? Non vengono aggiunti servizi, PATH/registro o avvio automatico.",
1242
+ installingCloudflared: "Installazione del componente cloudflared ufficiale. Mantieni DSH in esecuzione fino al termine.",
1243
+ purgeCloudflared: "Rimuovi completamente il componente cloudflared",
1244
+ purgeCloudflaredConfirm: "Rimuovere completamente il componente cloudflared e i log runtime dalla directory privata DSH Mobile? Rete locale, dati DSH e altri programmi non sono interessati.",
1245
+ purgingCloudflared: "Arresto del canale e rimozione dei file cloudflared gestiti da DSH Mobile…",
1246
+ switchingCloudflared: "Passaggio a cloudflared…",
1247
+ reconnectingCloudflared: "Riconnessione di cloudflared…",
1248
+ remoteUnavailableCloudflared: "cloudflared non è installato.",
1249
+ remoteStartingCloudflared: "Avvio del connettore cloudflared…",
1250
+ remoteConnectingCloudflared: "Avvio del gateway di autenticazione DSH…",
1251
+ resetCloudflaredConfirm: "Disattivare il canale remoto cloudflared e rimuovere tutti i dispositivi remoti? Il componente installato e la configurazione della rete locale restano invariati.",
1252
+ cloudflaredMissing: "Il componente cloudflared ufficiale non è installato. Completa i passaggi sopra.",
1253
+ cloudflaredInvalid: "Verifica del componente cloudflared non riuscita. Rimuovilo completamente e reinstallalo.",
1254
+ cloudflaredPortUnavailable: "Impossibile assegnare una porta locale al gateway remoto. Riprova.",
1255
+ cloudflaredLaunchFailed: "Il client cloudflared non è stato avviato.",
1256
+ cloudflaredTimeout: "Timeout in attesa che il tunnel diventi raggiungibile. Riconnettiti.",
1257
+ cloudflaredStopped: "La connessione cloudflared è stata arrestata.",
1258
+ cloudflaredExited: "La connessione cloudflared è terminata in modo imprevisto. Riconnettiti.",
1259
+ cloudflaredOutputInvalid: "cloudflared ha restituito uno stato non riconosciuto.",
1260
+ cloudflaredOriginInvalid: "L’indirizzo pubblico restituito da cloudflared non ha superato la verifica.",
1261
+ cloudflaredComponentUnsupported: "Questa build non può eseguire il componente cloudflared.",
1262
+ cloudflaredDownloadHashMismatch: "Il cloudflared scaricato non ha superato la verifica SHA-256. Riprova l’installazione.",
1263
+ cloudflaredDownloadSizeMismatch: "Il cloudflared scaricato non ha la dimensione prevista. Riprova l’installazione.",
1264
+ cloudflaredDownloadRedirectMissing: "La pagina di download non reindirizza più all’asset della release. Riprova più tardi o installa manualmente dalla pagina ufficiale.",
1265
+ cloudflaredDownloadRedirectInvalid: "Il reindirizzamento di download non puntava a un asset di release GitHub ed è stato rifiutato.",
1266
+ cloudflaredDownloadRedirectRejected: "Il reindirizzamento di download è stato rifiutato dalle regole. Riprova più tardi o installa manualmente dalla pagina ufficiale.",
1267
+ cloudflaredExecutableHashMismatch: "Il cloudflared installato non corrisponde più alla build verificata. Rimuovilo completamente e reinstalla.",
1268
+ cloudflaredPortReservationFailed: "Impossibile riservare una porta locale per il tunnel. Riprova.",
1269
+ cloudflaredTunnelTargetInvalid: "La configurazione salvata del tunnel non è un file regolare. Rimuovila e salva di nuovo le impostazioni.",
1270
+ cloudflaredTunnelSaving: "Salvataggio delle impostazioni e riconnessione di cloudflared…",
1271
+ cloudflaredTunnelForgetting: "Rimozione del token del tunnel salvato…",
1272
+ cloudflaredTunnelTitle: "Tipo di tunnel",
1273
+ cloudflaredTunnelQuick: "Rapido",
1274
+ cloudflaredTunnelNamed: "Con nome",
1275
+ cloudflaredTunnelQuickHint: "Senza account: cloudflared assegna un indirizzo casuale a ogni avvio.",
1276
+ cloudflaredTunnelNamedHint: "Usa il token di un account Cloudflare e mantiene un indirizzo stabile tra i riavvii.",
1277
+ cloudflaredTunnelHostname: "Hostname pubblico",
1278
+ cloudflaredTunnelHostnamePlaceholder: "dsh.example.com",
1279
+ cloudflaredTunnelPort: "Porta di inoltro locale",
1280
+ cloudflaredTunnelToken: "Token del connettore",
1281
+ cloudflaredTunnelTokenPlaceholder: "Incolla il token del tunnel dalla dashboard Cloudflare",
1282
+ cloudflaredTunnelTokenSaved: "Un token è salvato. Lascia vuoto per mantenerlo.",
1283
+ cloudflaredTunnelSave: "Salva e connetti",
1284
+ cloudflaredTunnelSaveQuick: "Passa a un tunnel rapido",
1285
+ cloudflaredTunnelConfigured: "Tunnel con nome configurato: {hostname} raggiunge questo computer su 127.0.0.1:{port}.",
1286
+ cloudflaredTunnelMissing: "Nessun tunnel con nome configurato.",
1287
+ cloudflaredTunnelHint: "Nella dashboard Cloudflare crea un tunnel e imposta il suo hostname pubblico su http://127.0.0.1:{port}. Il token è salvato solo nella directory privata DSH Mobile e non viene più mostrato dopo il salvataggio.",
1288
+ cloudflaredTunnelForget: "Rimuovi il token salvato",
1289
+ cloudflaredTunnelForgetConfirm: "Rimuovere il token salvato, arrestare il canale cloudflared e tornare a un tunnel rapido? Nulla si riconnette da solo: riattiva il canale in seguito. Il tunnel resta nel tuo account Cloudflare e il suo hostname pubblico non cambia.",
1290
+ cloudflaredTunnelInvalid: "La configurazione salvata del tunnel con nome non è leggibile; il provider usa un tunnel rapido. Salva di nuovo le impostazioni.",
1291
+ cloudflaredTunnelPortUnavailable: "La porta di inoltro locale configurata non è disponibile. Cloudflare instrada l’hostname pubblico esattamente su quella porta: liberala o scegline un’altra.",
1292
+ cloudflaredTunnelHostnameInvalid: "Inserisci un hostname pubblico sotto un dominio di questo account Cloudflare, ad esempio dsh.example.com.",
1293
+ cloudflaredTunnelPortInvalid: "Inserisci una porta di inoltro tra 1024 e 65535.",
1294
+ cloudflaredTunnelPortReserved: "La porta 3443 è riservata al gateway LAN di DSH Mobile ed è sempre occupata. Scegli un’altra porta, ad esempio 3444 o 3445.",
1295
+ cloudflaredTunnelTokenInvalid: "Non sembra un token di tunnel. Copia il token completo dalla dashboard Cloudflare.",
1296
+ cloudflaredTunnelSettingsInvalid: "Le impostazioni del tunnel sono state rifiutate.",
1297
+ cloudflaredTunnelConfigMissing: "Inserisci token, hostname e porta per usare un tunnel con nome."
1298
+ });
1299
+ Object.assign(MOBILE_CONTROL_MESSAGES.zh, {
1300
+ cloudflaredBadge: "账号可选",
1301
+ cloudflaredDescription: "按需安装官方 cloudflared 客户端。快速隧道无需账号,提供临时公网地址;命名隧道使用 Cloudflare 账号令牌,地址固定、重启不变。",
1302
+ prepareCloudflared: "准备 cloudflared",
1303
+ cloudflaredUnsupported: "当前仅支持 Windows x64。你仍可选择内置的 Tailscale Funnel。",
1304
+ cloudflaredNotInstalled: "尚未安装。只有点击下方按钮后,才会从 cloudflared 官方发布页下载固定版本。",
1305
+ cloudflaredReady: "官方组件 {version} 已就绪。",
1306
+ cloudflaredQuickNote: "快速隧道:无需 Cloudflare 账号,但官方定位是测试用途 —— 地址随机、有限流、无可用性保证。请勿用于必须长期可达的访问。",
1307
+ cloudflaredNamedNote: "命名隧道:上面的公网域名重启后不变,由 Cloudflare 转发到本机转发端口。连接器令牌只保存在 DSH Mobile 私有目录,经环境变量而非命令行传给 cloudflared,保存后不再显示。",
1308
+ cloudflaredComponentNote: "仅在你点击安装后从 cloudflared 官方发布页下载并校验固定版本,且启动时关闭自动更新。不会写入系统服务、开机启动、注册表或 PATH。",
1309
+ installConfirmCloudflared: "将从 cloudflared 官方发布页下载并校验固定版本({size}),仅保存到 DSH Mobile 私有目录。不会安装系统服务、写入 PATH/注册表或设置开机启动。是否继续?",
1310
+ installingCloudflared: "正在安装 cloudflared 官方组件。完成前请保持 DSH 运行。",
1311
+ purgeCloudflared: "彻底移除 cloudflared 组件",
1312
+ purgeCloudflaredConfirm: "彻底移除 DSH Mobile 私有目录中的 cloudflared 组件和运行日志?不会影响局域网、DSH 数据或系统中的其他程序。",
1313
+ purgingCloudflared: "正在关闭通道并清理 DSH Mobile 管理的 cloudflared 文件…",
1314
+ switchingCloudflared: "正在切换到 cloudflared…",
1315
+ reconnectingCloudflared: "正在重连 cloudflared…",
1316
+ remoteUnavailableCloudflared: "cloudflared 尚未安装。",
1317
+ remoteStartingCloudflared: "正在启动 cloudflared 连接器…",
1318
+ remoteConnectingCloudflared: "正在启动 DSH 认证网关…",
1319
+ resetCloudflaredConfirm: "关闭 cloudflared 远程通道并移除所有远程配对设备?已安装组件与局域网配置保持不变。",
1320
+ cloudflaredMissing: "cloudflared 官方组件尚未安装。请先完成上方准备步骤。",
1321
+ cloudflaredInvalid: "cloudflared 组件校验失败。请彻底移除后重新安装。",
1322
+ cloudflaredPortUnavailable: "无法分配本机远程网关端口,请重试。",
1323
+ cloudflaredLaunchFailed: "cloudflared 客户端未能启动。",
1324
+ cloudflaredTimeout: "等待隧道可用超时,请重新连接。",
1325
+ cloudflaredStopped: "cloudflared 连接已停止。",
1326
+ cloudflaredExited: "cloudflared 连接意外退出,请重新连接。",
1327
+ cloudflaredOutputInvalid: "cloudflared 返回了无法识别的状态。",
1328
+ cloudflaredOriginInvalid: "cloudflared 返回的公网地址未通过校验。",
1329
+ cloudflaredComponentUnsupported: "当前构建无法运行 cloudflared 组件。",
1330
+ cloudflaredDownloadHashMismatch: "下载的 cloudflared 未通过 SHA-256 校验。请重新安装。",
1331
+ cloudflaredDownloadSizeMismatch: "下载的 cloudflared 体积与固定版本不符。请重新安装。",
1332
+ cloudflaredDownloadRedirectMissing: "下载页不再跳转到发布资源。请稍后重试,或按官方页面手动安装。",
1333
+ cloudflaredDownloadRedirectInvalid: "下载跳转的目标不是 GitHub 发布资源,已被拒绝。",
1334
+ cloudflaredDownloadRedirectRejected: "下载跳转未通过校验规则。请稍后重试,或按官方页面手动安装。",
1335
+ cloudflaredExecutableHashMismatch: "已安装的 cloudflared 与校验过的版本不一致。请彻底移除后重新安装。",
1336
+ cloudflaredPortReservationFailed: "无法为隧道预留本机端口,请重试。",
1337
+ cloudflaredTunnelTargetInvalid: "已保存的隧道配置不是普通文件。请移除后重新保存设置。",
1338
+ cloudflaredTunnelSaving: "正在保存隧道设置并重新连接 cloudflared…",
1339
+ cloudflaredTunnelForgetting: "正在移除已保存的隧道令牌…",
1340
+ cloudflaredTunnelTitle: "隧道类型",
1341
+ cloudflaredTunnelQuick: "快速隧道",
1342
+ cloudflaredTunnelNamed: "命名隧道",
1343
+ cloudflaredTunnelQuickHint: "无需账号:每次启动由 cloudflared 随机分配地址。",
1344
+ cloudflaredTunnelNamedHint: "使用 Cloudflare 账号令牌,地址固定,重启后仍然有效。",
1345
+ cloudflaredTunnelHostname: "公网域名",
1346
+ cloudflaredTunnelHostnamePlaceholder: "dsh.example.com",
1347
+ cloudflaredTunnelPort: "本机转发端口",
1348
+ cloudflaredTunnelToken: "连接器令牌",
1349
+ cloudflaredTunnelTokenPlaceholder: "粘贴 Cloudflare 控制台里的隧道令牌",
1350
+ cloudflaredTunnelTokenSaved: "已保存令牌,留空表示不更改。",
1351
+ cloudflaredTunnelSave: "保存并连接",
1352
+ cloudflaredTunnelSaveQuick: "切换到快速隧道",
1353
+ cloudflaredTunnelConfigured: "命名隧道已配置:{hostname} 将转发到本机 127.0.0.1:{port}。",
1354
+ cloudflaredTunnelMissing: "尚未配置命名隧道。",
1355
+ cloudflaredTunnelHint: "先在 Cloudflare 控制台创建隧道,并把它的公开主机名指向 http://127.0.0.1:{port}。令牌只保存在 DSH Mobile 私有目录,保存后不会再显示。",
1356
+ cloudflaredTunnelForget: "移除已保存的令牌",
1357
+ cloudflaredTunnelForgetConfirm: "移除已保存的隧道令牌、停止 cloudflared 通道并改回快速隧道?不会自动重连,之后需要手动重新打开开关。Cloudflare 账号里的隧道本身仍在,公开主机名也不会改变。",
1358
+ cloudflaredTunnelInvalid: "已保存的命名隧道配置无法读取,当前按快速隧道运行。请重新保存设置。",
1359
+ cloudflaredTunnelPortUnavailable: "配置的本机转发端口不可用。Cloudflare 把公开主机名固定转发到该端口,请释放它或换一个端口。",
1360
+ cloudflaredTunnelHostnameInvalid: "请填写本 Cloudflare 账号下某个域名的公开主机名,例如 dsh.example.com。",
1361
+ cloudflaredTunnelPortInvalid: "请填写 1024 到 65535 之间的转发端口。",
1362
+ cloudflaredTunnelPortReserved: "3443 已留给 DSH Mobile 局域网网关,且始终被占用。请换一个端口,例如 3444 或 3445。",
1363
+ cloudflaredTunnelTokenInvalid: "这不像是一个隧道令牌。请从 Cloudflare 控制台完整复制令牌。",
1364
+ cloudflaredTunnelSettingsInvalid: "隧道设置未通过校验。",
1365
+ cloudflaredTunnelConfigMissing: "使用命名隧道需要同时填写令牌、公网域名和端口。"
1366
+ });
1040
1367
  const DIAGNOSTIC_REASON_MESSAGES = {
1041
1368
  en: {
1042
1369
  "lan-setup-required": ["Local network setup has not been completed.", "Return to Local network and choose a network to finish setup."],
@@ -1050,6 +1377,7 @@ window.__ModuleLoader__.load({
1050
1377
  "firewall-missing": ["The complete local network firewall rules were not found.", "Run dsh-mobile setup as administrator and check the firewall rules."],
1051
1378
  "firewall-unknown": ["The system did not allow the plugin to read firewall status.", "If the phone cannot find this computer, run setup as administrator."],
1052
1379
  "remote-off": ["Remote access through {provider} is currently off.", ""],
1380
+ "remote-origin-ready": ["The private HTTP backend is listening; public HTTPS is not verified.", "Verify your reverse proxy, public certificate, external Host including port, and WebSocket access from your phone. Keep the HTTP backend private."],
1053
1381
  "remote-ready": ["{provider} endpoint {endpointSuffix} is reachable in about {latencyMs} ms.", ""],
1054
1382
  "remote-rate-limited": ["{provider} endpoint {endpointSuffix} is reachable, but this check observed rate limiting.", "Try again later; older sessions load on demand to reduce traffic."],
1055
1383
  "remote-fake-ip": ["The Tailscale address is intercepted by the current VPN or DNS proxy, so TLS was not established.", "Switch VPN node or proxy mode; if it still fails, use cpolar."],
@@ -1071,6 +1399,7 @@ window.__ModuleLoader__.load({
1071
1399
  "firewall-missing": ["Le regole firewall complete per la rete locale non sono state trovate.", "Esegui dsh-mobile setup come amministratore e controlla le regole firewall."],
1072
1400
  "firewall-unknown": ["Il sistema non ha consentito al plugin di leggere lo stato del firewall.", "Se il telefono non trova il computer, esegui setup come amministratore."],
1073
1401
  "remote-off": ["L’accesso remoto tramite {provider} è disattivato.", ""],
1402
+ "remote-origin-ready": ["Il backend HTTP privato è in ascolto; HTTPS pubblico non è verificato.", "Verifica dal telefono reverse proxy, certificato pubblico, Host esterno inclusa la porta e accesso WebSocket. Mantieni privato il backend HTTP."],
1074
1403
  "remote-ready": ["L’endpoint {provider} {endpointSuffix} è raggiungibile in circa {latencyMs} ms.", ""],
1075
1404
  "remote-rate-limited": ["L’endpoint {provider} {endpointSuffix} è raggiungibile, ma il controllo ha rilevato una limitazione temporanea.", "Riprova più tardi; le sessioni precedenti vengono caricate su richiesta per ridurre il traffico."],
1076
1405
  "remote-fake-ip": ["L’indirizzo Tailscale è intercettato dalla VPN o dal proxy DNS corrente e TLS non è stato stabilito.", "Cambia nodo VPN o modalità proxy; se il problema continua, usa cpolar."],
@@ -1092,6 +1421,7 @@ window.__ModuleLoader__.load({
1092
1421
  "firewall-missing": ["未找到完整的局域网防火墙规则。", "以管理员身份运行 dsh-mobile setup 并检查防火墙规则。"],
1093
1422
  "firewall-unknown": ["系统未允许插件读取防火墙状态。", "若手机找不到电脑,请以管理员身份运行 setup。"],
1094
1423
  "remote-off": ["{provider} 远程访问当前未启用。", ""],
1424
+ "remote-origin-ready": ["私有 HTTP 后端已监听;尚未验证公网 HTTPS。", "请用手机核对反向代理、公网证书、含端口的外部 Host 及 WebSocket 访问。HTTP 后端必须留在私网。"],
1095
1425
  "remote-ready": ["{provider} 端点 {endpointSuffix} 可达,往返约 {latencyMs} ms。", ""],
1096
1426
  "remote-rate-limited": ["{provider} 端点 {endpointSuffix} 可达,但本次检查观察到服务限流。", "稍后重试;旧会话会按需加载以减少流量。"],
1097
1427
  "remote-fake-ip": ["Tailscale 地址被当前 VPN 或 DNS 代理接管,TLS 链路未建立。", "切换 VPN 节点或代理模式;仍失败时改用 cpolar。"],
@@ -1576,11 +1906,11 @@ window.__ModuleLoader__.load({
1576
1906
  .dsh-native-mobile-backdrop { display:block; position:fixed; z-index:235; inset:env(safe-area-inset-top) 0 0; border:0; background:rgb(15 23 42 / 32%); }
1577
1907
  .dsh-native-mobile-backdrop:not([hidden]) { animation:dsh-mobile-fade-in var(--dsh-mobile-motion-duration) ease-out; }
1578
1908
  .dsh-native-mobile-backdrop[hidden] { display:none; }
1579
- [data-dsh-mobile-details] { position:fixed !important; z-index:250 !important; inset:0 0 0 auto !important; width:min(94vw,460px) !important; max-width:none !important; transform:translateX(100%); transition:transform var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); background:var(--dsw-bg, #fff); box-shadow:-18px 0 46px rgb(15 23 42 / 18%); }
1909
+ [data-dsh-mobile-details] { position:fixed !important; z-index:250 !important; inset:0 0 0 auto !important; width:min(94vw,460px) !important; max-width:none !important; transform:translateX(100%); transition:transform var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); background:var(--dsw-alias-bg-layer-1, #fff); box-shadow:-18px 0 46px rgb(15 23 42 / 18%); }
1580
1910
  [data-dsh-mobile-details][data-open="true"] { transform:translateX(0); }
1581
1911
  [data-dsh-mobile-handle] { display:none !important; }
1582
1912
  [data-dsh-mobile-settings] { flex-direction:column !important; width:100vw !important; height:100dvh !important; max-width:none !important; border-radius:0 !important; animation:dsh-mobile-panel-in var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); }
1583
- [data-dsh-mobile-settings-nav] { flex:none !important; width:100% !important; padding:max(14px,env(safe-area-inset-top)) 12px 8px !important; gap:10px !important; border-bottom:1px solid var(--dsw-alias-border-subtle,#e8ebef); }
1913
+ [data-dsh-mobile-settings-nav] { flex:none !important; width:100% !important; padding:max(14px,env(safe-area-inset-top)) 12px 8px !important; gap:10px !important; border-bottom:1px solid var(--dsw-alias-border-l2,#e8ebef); }
1584
1914
  [data-dsh-mobile-settings-nav] [class*="_navTitle"] { padding:0 8px !important; font-size:18px !important; line-height:28px !important; }
1585
1915
  [data-dsh-mobile-settings-list] { flex-direction:row !important; gap:4px !important; overflow-x:auto !important; scrollbar-width:none; }
1586
1916
  [data-dsh-mobile-settings-list]::-webkit-scrollbar { display:none; }
@@ -1664,7 +1994,7 @@ window.__ModuleLoader__.load({
1664
1994
  camera action on the same row metrics and tokens as the native menu. */
1665
1995
  .dsh-mobile-media-action { box-sizing:border-box; display:flex; align-items:center; justify-content:flex-start; gap:8px; width:100%; min-width:0; min-height:40px; padding:8px 10px; border:0; border-radius:10px; background:transparent; color:var(--dsw-alias-label-primary,inherit); cursor:pointer; font:inherit; font-size:14px; line-height:22px; text-align:left; touch-action:manipulation; }
1666
1996
  .dsh-mobile-media-action:active { opacity:.72; }
1667
- .dsh-mobile-media-action:focus-visible { outline:2px solid var(--dsw-alias-interactive-border-focus,#4c82f7); outline-offset:1px; }
1997
+ .dsh-mobile-media-action:focus-visible { outline:2px solid var(--dsw-alias-state-business-primary,#4c82f7); outline-offset:1px; }
1668
1998
  .dsh-mobile-media-action:disabled { cursor:default; opacity:.38; }
1669
1999
  .dsh-mobile-media-action svg { flex:none; width:16px; height:16px; color:var(--dsw-alias-label-tertiary,currentColor); }
1670
2000
  .dsh-mobile-media-action span { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
@@ -2327,7 +2657,60 @@ window.__ModuleLoader__.load({
2327
2657
  };
2328
2658
  }
2329
2659
  //#endregion
2660
+ //#region src/local-admin-host.ts
2661
+ /** Browser-safe desktop-admin Host checks. Do not import Node APIs here. */
2662
+ function unwrapBrackets(hostname) {
2663
+ return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
2664
+ }
2665
+ function parseIpv4(hostname) {
2666
+ const parts = hostname.split(".");
2667
+ if (parts.length !== 4) return void 0;
2668
+ const octets = [];
2669
+ for (const part of parts) {
2670
+ if (!/^(?:0|[1-9]\d{0,2})$/u.test(part)) return void 0;
2671
+ const value = Number(part);
2672
+ if (value > 255) return void 0;
2673
+ octets.push(value);
2674
+ }
2675
+ return octets;
2676
+ }
2677
+ function isLoopbackIpv4(octets) {
2678
+ return octets[0] === 127;
2679
+ }
2680
+ function isPrivateIpv4(octets) {
2681
+ return octets[0] === 10 || octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31 || octets[0] === 192 && octets[1] === 168;
2682
+ }
2683
+ function isLinkLocalIpv4(octets) {
2684
+ return octets[0] === 169 && octets[1] === 254;
2685
+ }
2686
+ /**
2687
+ * Hostnames that may appear on the DSH desktop admin surface.
2688
+ * Loopback, RFC1918, and IPv4 link-local are accepted. Public IPs,
2689
+ * CGNAT, and arbitrary DNS names stay rejected so DNS rebinding
2690
+ * cannot reach `/api/mobile-access`.
2691
+ */
2692
+ function isLocalAdminHostname(hostname) {
2693
+ const host = unwrapBrackets(hostname).trim().toLowerCase();
2694
+ if (host === "localhost" || host === "::1") return true;
2695
+ const octets = parseIpv4(host);
2696
+ if (octets === void 0) return false;
2697
+ return isLoopbackIpv4(octets) || isPrivateIpv4(octets) || isLinkLocalIpv4(octets);
2698
+ }
2699
+ /**
2700
+ * Whether the current browser document should mount the desktop Mobile access
2701
+ * control. Dedicated Mobile HTTPS (the phone surface) stays native even when
2702
+ * the Host is a private LAN address.
2703
+ */
2704
+ function isDesktopAdminSurface(hostname, search = "", frontend) {
2705
+ const query = search.startsWith("?") ? search.slice(1) : search;
2706
+ return isLocalAdminHostname(hostname) && frontend !== "dedicated" && !new URLSearchParams(query).has("dsh-mobile-preview");
2707
+ }
2708
+ //#endregion
2330
2709
  //#region src/client.ts
2710
+ /** Map a host-reported provider onto the UI union, defaulting to the built-in one. */
2711
+ function parseMobileRemoteProvider(value) {
2712
+ return value === "cpolar" || value === "cloudflared" || value === "frp" || value === "origin" ? value : "tailscale";
2713
+ }
2331
2714
  const queuedDefinitions = [];
2332
2715
  let queuedLegacyMount;
2333
2716
  if (typeof window !== "undefined" && window.dshMobile === void 0) window.dshMobile = {
@@ -2338,9 +2721,6 @@ window.__ModuleLoader__.load({
2338
2721
  queuedDefinitions.push(definition);
2339
2722
  }
2340
2723
  };
2341
- function isLoopbackHost(hostname) {
2342
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
2343
- }
2344
2724
  function selectMobileControlLocale(documentLanguage = "", navigatorLanguages = []) {
2345
2725
  for (const value of [documentLanguage, ...navigatorLanguages]) {
2346
2726
  const language = value.trim().toLowerCase().split(/[-_]/u)[0];
@@ -2593,7 +2973,103 @@ window.__ModuleLoader__.load({
2593
2973
  function createFrpServerTemplateForClipboard(serverPort, token, publicOrigin) {
2594
2974
  return createRestrictedFrpServerTemplate(serverPort, token, publicOrigin);
2595
2975
  }
2976
+ const ORIGIN_ERROR_MESSAGE_KEYS = {
2977
+ origin_public_origin_invalid: "originPublicOriginInvalid",
2978
+ origin_listen_host_invalid: "originListenHostInvalid",
2979
+ origin_listen_port_invalid: "originListenPortInvalid",
2980
+ origin_listen_port_reserved: "originListenPortReserved",
2981
+ origin_allowed_cidrs_invalid: "originAllowedCidrsInvalid",
2982
+ origin_settings_invalid: "originSettingsInvalid",
2983
+ origin_config_missing: "originConfigMissing",
2984
+ origin_config_invalid: "originConfigInvalid",
2985
+ origin_config_target_invalid: "originConfigTargetInvalid",
2986
+ origin_controller_unavailable: "originControllerUnavailable",
2987
+ origin_listen_port_in_use: "originPortInUse",
2988
+ origin_listen_address_unavailable: "originAddressUnavailable",
2989
+ origin_gateway_start_failed: "originStartFailed"
2990
+ };
2596
2991
  function installControl() {
2992
+ /**
2993
+ * Provider failure codes to message keys.
2994
+ *
2995
+ * Kept as keys rather than translated text so the same table can serve both the
2996
+ * status repaint and a failed request: a server rejection carries its code in
2997
+ * `error.message`, and showing that raw would print `Error: cloudflared_...` in
2998
+ * every language while the translated sentence sat unreachable.
2999
+ */
3000
+ const REMOTE_ERROR_MESSAGE_KEYS = {
3001
+ funnel_permission_required: "funnelPermission",
3002
+ funnel_https_required: "funnelHttps",
3003
+ funnel_start_failed: "funnelStart",
3004
+ funnel_start_timeout: "funnelTimeout",
3005
+ tailscale_dns_missing: "tailscaleDnsMissing",
3006
+ gateway_start_failed: "gatewayStartFailed",
3007
+ control_channel_failed: "controlChannelFailed",
3008
+ cpolar_component_missing: "cpolarMissing",
3009
+ cpolar_component_invalid: "cpolarInvalid",
3010
+ cpolar_config_missing: "cpolarConfigMissing",
3011
+ cpolar_config_invalid: "cpolarConfigInvalid",
3012
+ cpolar_port_unavailable: "cpolarPortUnavailable",
3013
+ cpolar_launch_failed: "cpolarLaunchFailed",
3014
+ cpolar_start_timeout: "cpolarTimeout",
3015
+ cpolar_stopped: "cpolarStopped",
3016
+ cpolar_exited: "cpolarExited",
3017
+ cpolar_invalid_output: "cpolarOutputInvalid",
3018
+ cpolar_invalid_origin: "cpolarOriginInvalid",
3019
+ cloudflared_component_missing: "cloudflaredMissing",
3020
+ cloudflared_component_invalid: "cloudflaredInvalid",
3021
+ cloudflared_port_unavailable: "cloudflaredPortUnavailable",
3022
+ cloudflared_launch_failed: "cloudflaredLaunchFailed",
3023
+ cloudflared_start_timeout: "cloudflaredTimeout",
3024
+ cloudflared_stopped: "cloudflaredStopped",
3025
+ cloudflared_exited: "cloudflaredExited",
3026
+ cloudflared_invalid_output: "cloudflaredOutputInvalid",
3027
+ cloudflared_invalid_origin: "cloudflaredOriginInvalid",
3028
+ cloudflared_tunnel_port_unavailable: "cloudflaredTunnelPortUnavailable",
3029
+ cloudflared_tunnel_hostname_invalid: "cloudflaredTunnelHostnameInvalid",
3030
+ cloudflared_tunnel_port_invalid: "cloudflaredTunnelPortInvalid",
3031
+ cloudflared_tunnel_port_reserved: "cloudflaredTunnelPortReserved",
3032
+ cloudflared_tunnel_token_invalid: "cloudflaredTunnelTokenInvalid",
3033
+ cloudflared_tunnel_settings_invalid: "cloudflaredTunnelSettingsInvalid",
3034
+ cloudflared_tunnel_config_missing: "cloudflaredTunnelConfigMissing",
3035
+ cloudflared_tunnel_config_invalid: "cloudflaredTunnelInvalid",
3036
+ cloudflared_tunnel_target_invalid: "cloudflaredTunnelTargetInvalid",
3037
+ cloudflared_component_unsupported: "cloudflaredComponentUnsupported",
3038
+ cloudflared_download_hash_mismatch: "cloudflaredDownloadHashMismatch",
3039
+ cloudflared_download_size_mismatch: "cloudflaredDownloadSizeMismatch",
3040
+ cloudflared_executable_hash_mismatch: "cloudflaredExecutableHashMismatch",
3041
+ cloudflared_download_redirect_missing: "cloudflaredDownloadRedirectMissing",
3042
+ cloudflared_download_redirect_invalid: "cloudflaredDownloadRedirectInvalid",
3043
+ cloudflared_download_redirect_rejected: "cloudflaredDownloadRedirectRejected",
3044
+ cloudflared_port_reservation_failed: "cloudflaredPortReservationFailed",
3045
+ frp_component_missing: "frpMissing",
3046
+ frp_component_invalid: "frpInvalid",
3047
+ frp_config_missing: "frpConfigMissing",
3048
+ frp_config_verify_failed: "frpConfigVerifyFailed",
3049
+ frp_vhost_publicly_reachable: "frpVhostPublic",
3050
+ frp_vhost_probe_failed: "frpVhostProbeFailed",
3051
+ frp_launch_failed: "frpLaunchFailed",
3052
+ frp_start_timeout: "frpTimeout",
3053
+ frp_discovery_mismatch: "frpDiscoveryMismatch",
3054
+ frp_discovery_invalid: "frpDiscoveryInvalid",
3055
+ frp_stopped: "frpStopped",
3056
+ frp_exited: "frpExited",
3057
+ ...ORIGIN_ERROR_MESSAGE_KEYS
3058
+ };
3059
+ /** The table above, translated, for the status repaint. */
3060
+ const translatedRemoteErrors = () => Object.fromEntries(Object.entries(REMOTE_ERROR_MESSAGE_KEYS).map(([code, key]) => [code, t(key)]));
3061
+ /**
3062
+ * Text for a failed control request.
3063
+ *
3064
+ * A rejection carries its code in `error.message`, so a known code renders its
3065
+ * translated sentence; only an unrecognized one falls back to the generic message,
3066
+ * and it shows the bare message rather than a stringified Error.
3067
+ */
3068
+ const remoteFailureTextFor = (error, fallbackKey) => {
3069
+ const message = error instanceof Error ? error.message : String(error);
3070
+ const key = REMOTE_ERROR_MESSAGE_KEYS[message];
3071
+ return key === void 0 ? t(fallbackKey, { error: message }) : t(key);
3072
+ };
2597
3073
  const locale = selectedMobileControlLocale();
2598
3074
  const localeTag = locale === "it" ? "it-IT" : locale === "zh" ? "zh-CN" : "en-US";
2599
3075
  const t = controlTranslator(locale);
@@ -2728,6 +3204,10 @@ window.__ModuleLoader__.load({
2728
3204
  remoteView.hidden = true;
2729
3205
  const remoteIntro = element("p", "dsh-mobile-control__intro");
2730
3206
  remoteIntro.textContent = t("remoteIntro");
3207
+ const httpFrameWarning = element("p", "dsh-mobile-control__http-frame-warning");
3208
+ httpFrameWarning.textContent = t("httpFrameWarning");
3209
+ httpFrameWarning.hidden = location.protocol !== "https:";
3210
+ httpFrameWarning.setAttribute("role", "note");
2731
3211
  const providerSection = element("section", "dsh-mobile-control__provider-section");
2732
3212
  const providerHeading = element("h3", "dsh-mobile-control__section-title");
2733
3213
  providerHeading.textContent = t("chooseProvider");
@@ -2781,7 +3261,35 @@ window.__ModuleLoader__.load({
2781
3261
  cpolarChoiceDescription.textContent = t("cpolarDescription");
2782
3262
  cpolarChoiceTop.append(cpolarChoiceName, cpolarChoiceBadge);
2783
3263
  cpolarChoice.append(cpolarChoiceTop, cpolarChoiceDescription);
2784
- providerChoices.append(cpolarChoice, tailscaleChoice);
3264
+ const cloudflaredChoice = element("button", "dsh-mobile-control__provider");
3265
+ cloudflaredChoice.type = "button";
3266
+ cloudflaredChoice.setAttribute("role", "radio");
3267
+ cloudflaredChoice.setAttribute("aria-checked", "false");
3268
+ const cloudflaredChoiceTop = element("span", "dsh-mobile-control__provider-top");
3269
+ const cloudflaredChoiceName = element("strong");
3270
+ cloudflaredChoiceName.textContent = "cloudflared";
3271
+ const cloudflaredChoiceBadge = element("span", "dsh-mobile-control__provider-badge");
3272
+ cloudflaredChoiceBadge.textContent = t("cloudflaredBadge");
3273
+ const cloudflaredChoiceDescription = element("span", "dsh-mobile-control__provider-description");
3274
+ cloudflaredChoiceDescription.textContent = t("cloudflaredDescription");
3275
+ cloudflaredChoiceTop.append(cloudflaredChoiceName, cloudflaredChoiceBadge);
3276
+ cloudflaredChoice.append(cloudflaredChoiceTop, cloudflaredChoiceDescription);
3277
+ providerChoices.append(cpolarChoice, cloudflaredChoice, tailscaleChoice);
3278
+ const providerCardOrder = [
3279
+ cpolarChoice,
3280
+ cloudflaredChoice,
3281
+ tailscaleChoice
3282
+ ];
3283
+ providerChoices.addEventListener("keydown", (event) => {
3284
+ const step = event.key === "ArrowRight" || event.key === "ArrowDown" ? 1 : event.key === "ArrowLeft" || event.key === "ArrowUp" ? -1 : 0;
3285
+ const jump = event.key === "Home" ? 0 : event.key === "End" ? providerCardOrder.length - 1 : void 0;
3286
+ if (step === 0 && jump === void 0) return;
3287
+ const current = providerCardOrder.indexOf(document.activeElement);
3288
+ if (current < 0) return;
3289
+ const next = jump ?? (current + step + providerCardOrder.length) % providerCardOrder.length;
3290
+ event.preventDefault();
3291
+ providerCardOrder[next]?.focus();
3292
+ });
2785
3293
  const cpolarSetup = element("section", "dsh-mobile-control__cpolar-setup");
2786
3294
  cpolarSetup.hidden = true;
2787
3295
  const cpolarSetupTitle = element("h3", "dsh-mobile-control__section-title");
@@ -2843,6 +3351,96 @@ window.__ModuleLoader__.load({
2843
3351
  cpolarDetailsBody.append(cpolarDetailsText, cpolarStorage, cpolarOfficial, cpolarTerms, cpolarPurge);
2844
3352
  cpolarDetails.append(cpolarDetailsSummary, cpolarDetailsBody);
2845
3353
  cpolarSetup.append(cpolarSetupTitle, cpolarComponentStatus, cpolarInstall, cpolarAccount, cpolarDetails);
3354
+ const cloudflaredSetup = element("section", "dsh-mobile-control__cpolar-setup dsh-mobile-control__cloudflared-setup");
3355
+ cloudflaredSetup.hidden = true;
3356
+ const cloudflaredSetupTitle = element("h3", "dsh-mobile-control__section-title");
3357
+ cloudflaredSetupTitle.textContent = t("prepareCloudflared");
3358
+ const cloudflaredTunnelTitle = element("h3", "dsh-mobile-control__section-title");
3359
+ cloudflaredTunnelTitle.textContent = t("cloudflaredTunnelTitle");
3360
+ const cloudflaredMode = element("div", "dsh-mobile-control__switcher");
3361
+ const cloudflaredModeQuick = element("button", "dsh-mobile-control__tab is-active");
3362
+ cloudflaredModeQuick.type = "button";
3363
+ cloudflaredModeQuick.textContent = t("cloudflaredTunnelQuick");
3364
+ const cloudflaredModeNamed = element("button", "dsh-mobile-control__tab");
3365
+ cloudflaredModeNamed.type = "button";
3366
+ cloudflaredModeNamed.textContent = t("cloudflaredTunnelNamed");
3367
+ cloudflaredMode.append(cloudflaredModeQuick, cloudflaredModeNamed);
3368
+ const cloudflaredModeHint = element("p", "dsh-mobile-control__component-status");
3369
+ cloudflaredModeHint.textContent = t("cloudflaredTunnelQuickHint");
3370
+ const cloudflaredTunnel = element("div", "dsh-mobile-control__tunnel");
3371
+ cloudflaredTunnel.hidden = true;
3372
+ const cloudflaredTunnelFields = element("div", "dsh-mobile-control__frp-fields");
3373
+ const cloudflaredHostnameLabel = element("label", "dsh-mobile-control__field");
3374
+ cloudflaredHostnameLabel.textContent = t("cloudflaredTunnelHostname");
3375
+ const cloudflaredHostname = element("input");
3376
+ cloudflaredHostname.type = "text";
3377
+ cloudflaredHostname.autocomplete = "off";
3378
+ cloudflaredHostname.spellcheck = false;
3379
+ cloudflaredHostname.placeholder = t("cloudflaredTunnelHostnamePlaceholder");
3380
+ const cloudflaredPortLabel = element("label", "dsh-mobile-control__field");
3381
+ cloudflaredPortLabel.textContent = t("cloudflaredTunnelPort");
3382
+ const cloudflaredPort = element("input");
3383
+ cloudflaredPort.type = "number";
3384
+ cloudflaredPort.inputMode = "numeric";
3385
+ cloudflaredPort.min = "1024";
3386
+ cloudflaredPort.max = "65535";
3387
+ cloudflaredPort.value = "3444";
3388
+ const cloudflaredTokenLabel = element("label", "dsh-mobile-control__field");
3389
+ cloudflaredTokenLabel.textContent = t("cloudflaredTunnelToken");
3390
+ const cloudflaredToken = element("input");
3391
+ cloudflaredToken.type = "password";
3392
+ cloudflaredToken.autocomplete = "off";
3393
+ cloudflaredToken.spellcheck = false;
3394
+ cloudflaredToken.placeholder = t("cloudflaredTunnelTokenPlaceholder");
3395
+ cloudflaredHostnameLabel.append(cloudflaredHostname);
3396
+ cloudflaredPortLabel.append(cloudflaredPort);
3397
+ cloudflaredTokenLabel.append(cloudflaredToken);
3398
+ cloudflaredTunnelFields.append(cloudflaredHostnameLabel, cloudflaredPortLabel, cloudflaredTokenLabel);
3399
+ const cloudflaredTunnelStatus = element("p", "dsh-mobile-control__component-status");
3400
+ cloudflaredTunnelStatus.textContent = t("cloudflaredTunnelMissing");
3401
+ const cloudflaredTunnelHint = element("p", "dsh-mobile-control__component-status");
3402
+ cloudflaredTunnelHint.textContent = t("cloudflaredTunnelHint", { port: "3444" });
3403
+ const cloudflaredTunnelSave = element("button", "dsh-mobile-control__primary dsh-mobile-control__frp-action");
3404
+ cloudflaredTunnelSave.type = "button";
3405
+ cloudflaredTunnelSave.textContent = t("cloudflaredTunnelSave");
3406
+ /** The button commits the shown mode, so quick mode must not promise to save settings. */
3407
+ const cloudflaredSaveLabel = () => (cloudflaredModeDraft ?? cloudflaredTunnelMode) === "named" ? t("cloudflaredTunnelSave") : t("cloudflaredTunnelSaveQuick");
3408
+ const cloudflaredTunnelForget = element("button", "dsh-mobile-control__danger dsh-mobile-control__frp-action");
3409
+ cloudflaredTunnelForget.type = "button";
3410
+ cloudflaredTunnelForget.textContent = t("cloudflaredTunnelForget");
3411
+ cloudflaredTunnelForget.hidden = true;
3412
+ cloudflaredTunnel.append(cloudflaredTunnelFields, cloudflaredTunnelStatus, cloudflaredTunnelHint);
3413
+ const cloudflaredComponentStatus = element("p", "dsh-mobile-control__component-status");
3414
+ cloudflaredComponentStatus.textContent = t("checkingComponent");
3415
+ const cloudflaredInstall = element("button", "dsh-mobile-control__primary");
3416
+ cloudflaredInstall.type = "button";
3417
+ cloudflaredInstall.textContent = t("installOfficial");
3418
+ const cloudflaredNote = element("p", "dsh-mobile-control__origin-warning");
3419
+ cloudflaredNote.textContent = t("cloudflaredQuickNote");
3420
+ const cloudflaredDetails = element("details", "dsh-mobile-control__details");
3421
+ const cloudflaredDetailsSummary = element("summary");
3422
+ cloudflaredDetailsSummary.textContent = t("componentDetails");
3423
+ const cloudflaredDetailsBody = element("div", "dsh-mobile-control__details-body");
3424
+ const cloudflaredDetailsText = element("p");
3425
+ cloudflaredDetailsText.textContent = t("cloudflaredComponentNote");
3426
+ const cloudflaredStorage = element("code", "dsh-mobile-control__storage");
3427
+ cloudflaredStorage.textContent = t("pluginPrivateDirectory");
3428
+ const cloudflaredOfficial = element("a", "dsh-mobile-control__text-link");
3429
+ cloudflaredOfficial.href = "https://github.com/cloudflare/cloudflared/releases";
3430
+ cloudflaredOfficial.target = "_blank";
3431
+ cloudflaredOfficial.rel = "noopener noreferrer";
3432
+ cloudflaredOfficial.textContent = t("officialDownload");
3433
+ const cloudflaredTerms = element("a", "dsh-mobile-control__text-link");
3434
+ cloudflaredTerms.href = "https://www.cloudflare.com/website-terms/";
3435
+ cloudflaredTerms.target = "_blank";
3436
+ cloudflaredTerms.rel = "noopener noreferrer";
3437
+ cloudflaredTerms.textContent = t("terms");
3438
+ const cloudflaredPurge = element("button", "dsh-mobile-control__danger");
3439
+ cloudflaredPurge.type = "button";
3440
+ cloudflaredPurge.textContent = t("purgeCloudflared");
3441
+ cloudflaredDetailsBody.append(cloudflaredDetailsText, cloudflaredStorage, cloudflaredOfficial, cloudflaredTerms, cloudflaredPurge);
3442
+ cloudflaredDetails.append(cloudflaredDetailsSummary, cloudflaredDetailsBody);
3443
+ cloudflaredSetup.append(cloudflaredSetupTitle, cloudflaredTunnelTitle, cloudflaredMode, cloudflaredModeHint, cloudflaredTunnel, cloudflaredTunnelSave, cloudflaredTunnelForget, cloudflaredComponentStatus, cloudflaredInstall, cloudflaredNote, cloudflaredDetails);
2846
3444
  const selfHosted = element("details", "dsh-mobile-control__self-hosted");
2847
3445
  const selfHostedSummary = element("summary", "dsh-mobile-control__self-hosted-summary");
2848
3446
  const selfHostedSummaryText = element("span");
@@ -2865,7 +3463,17 @@ window.__ModuleLoader__.load({
2865
3463
  frpChoiceDescription.textContent = t("frpDescription");
2866
3464
  frpChoiceTop.append(frpChoiceName);
2867
3465
  frpChoice.append(frpChoiceTop, frpChoiceDescription);
2868
- selfHostedBody.append(frpChoice);
3466
+ const originChoice = element("button", "dsh-mobile-control__provider is-origin");
3467
+ originChoice.type = "button";
3468
+ originChoice.setAttribute("aria-pressed", "false");
3469
+ const originChoiceTop = element("span", "dsh-mobile-control__provider-top");
3470
+ const originChoiceName = element("strong");
3471
+ originChoiceName.textContent = t("originName");
3472
+ const originChoiceDescription = element("span", "dsh-mobile-control__provider-description");
3473
+ originChoiceDescription.textContent = t("originDescription");
3474
+ originChoiceTop.append(originChoiceName);
3475
+ originChoice.append(originChoiceTop, originChoiceDescription);
3476
+ selfHostedBody.append(frpChoice, originChoice);
2869
3477
  selfHosted.append(selfHostedSummary, selfHostedBody);
2870
3478
  providerSection.append(providerHeading, providerInfo, providerChoices, selfHosted);
2871
3479
  const frpSetup = element("section", "dsh-mobile-control__frp-setup");
@@ -3046,6 +3654,82 @@ window.__ModuleLoader__.load({
3046
3654
  frpVerifySummary.textContent = t("frpStep4Title");
3047
3655
  frpVerifyGroup.append(frpVerifySummary, frpStep4);
3048
3656
  frpSetup.append(frpSetupTitle, frpOverview, frpConnectionGroup, frpVpsGroup, frpComponentGroup, frpVerifyGroup, frpDetails);
3657
+ const originSetup = element("section", "dsh-mobile-control__origin-setup");
3658
+ originSetup.hidden = true;
3659
+ const originSetupTitle = element("h3", "dsh-mobile-control__section-title");
3660
+ originSetupTitle.textContent = t("prepareOrigin");
3661
+ const originHelp = element("p", "dsh-mobile-control__component-note");
3662
+ originHelp.id = "dsh-mobile-origin-help";
3663
+ originHelp.textContent = t("originHelp");
3664
+ const originWarning = element("p", "dsh-mobile-control__origin-warning");
3665
+ originWarning.textContent = t("originWarning");
3666
+ const originFields = element("div", "dsh-mobile-control__origin-fields");
3667
+ const originPublicLabel = element("label", "dsh-mobile-control__field");
3668
+ originPublicLabel.textContent = t("originPublicOrigin");
3669
+ const originPublic = element("input");
3670
+ originPublic.type = "url";
3671
+ originPublic.placeholder = "https://phone.example.com:8815";
3672
+ originPublic.maxLength = 512;
3673
+ const originHostLabel = element("label", "dsh-mobile-control__field");
3674
+ originHostLabel.textContent = t("originListenHost");
3675
+ const originHost = element("input");
3676
+ originHost.type = "text";
3677
+ originHost.value = "127.0.0.1";
3678
+ originHost.maxLength = 15;
3679
+ const originPortLabel = element("label", "dsh-mobile-control__field");
3680
+ originPortLabel.textContent = t("originListenPort");
3681
+ const originPort = element("input");
3682
+ originPort.type = "number";
3683
+ originPort.inputMode = "numeric";
3684
+ originPort.min = "1024";
3685
+ originPort.max = "65535";
3686
+ originPort.step = "1";
3687
+ originPort.value = "3444";
3688
+ const originCidrsLabel = element("label", "dsh-mobile-control__field");
3689
+ originCidrsLabel.textContent = t("originAllowedCidrs");
3690
+ const originCidrs = element("input");
3691
+ originCidrs.type = "text";
3692
+ originCidrs.value = "127.0.0.0/8";
3693
+ originCidrs.maxLength = 1040;
3694
+ for (const input of [
3695
+ originPublic,
3696
+ originHost,
3697
+ originPort,
3698
+ originCidrs
3699
+ ]) {
3700
+ input.required = true;
3701
+ input.autocomplete = "off";
3702
+ input.spellcheck = false;
3703
+ input.setAttribute("aria-describedby", "dsh-mobile-origin-help dsh-mobile-origin-feedback");
3704
+ }
3705
+ originPublicLabel.append(originPublic);
3706
+ originHostLabel.append(originHost);
3707
+ originPortLabel.append(originPort);
3708
+ originCidrsLabel.append(originCidrs);
3709
+ originFields.append(originPublicLabel, originHostLabel, originPortLabel, originCidrsLabel);
3710
+ const originFeedback = element("p", "dsh-mobile-control__origin-feedback");
3711
+ originFeedback.id = "dsh-mobile-origin-feedback";
3712
+ originFeedback.setAttribute("role", "status");
3713
+ originFeedback.setAttribute("aria-live", "polite");
3714
+ originFeedback.hidden = true;
3715
+ const originConfigure = element("button", "dsh-mobile-control__primary");
3716
+ originConfigure.type = "button";
3717
+ originConfigure.textContent = t("originSaveStart");
3718
+ const originPurge = element("button", "dsh-mobile-control__danger");
3719
+ originPurge.type = "button";
3720
+ originPurge.textContent = t("originPurge");
3721
+ originPurge.hidden = true;
3722
+ const originFormActions = element("div", "dsh-mobile-control__actions");
3723
+ originFormActions.append(originConfigure, originPurge);
3724
+ const originBackend = element("div", "dsh-mobile-control__origin-backend");
3725
+ originBackend.hidden = true;
3726
+ const originBackendLabel = element("span", "dsh-mobile-control__access-label");
3727
+ const originBackendAddress = element("code", "dsh-mobile-control__storage");
3728
+ const originCopyBackend = element("button", "dsh-mobile-control__secondary");
3729
+ originCopyBackend.type = "button";
3730
+ originCopyBackend.textContent = t("originCopyBackend");
3731
+ originBackend.append(originBackendLabel, originBackendAddress, originCopyBackend);
3732
+ originSetup.append(originSetupTitle, originHelp, originWarning, originFields, originFeedback, originFormActions, originBackend);
3049
3733
  const tailscaleInfo = element("details", "dsh-mobile-control__details");
3050
3734
  const tailscaleInfoSummary = element("summary");
3051
3735
  tailscaleInfoSummary.textContent = t("tailscaleHelp");
@@ -3065,7 +3749,7 @@ window.__ModuleLoader__.load({
3065
3749
  providerSetupMeta.append(providerSetupName, remoteStateBadge);
3066
3750
  providerSetupHeader.append(providerSetupHeading, providerSetupMeta);
3067
3751
  const providerSetupBody = element("div", "dsh-mobile-control__provider-setup-body");
3068
- providerSetupBody.append(cpolarSetup, frpSetup, tailscaleInfo);
3752
+ providerSetupBody.append(cpolarSetup, cloudflaredSetup, frpSetup, originSetup, tailscaleInfo);
3069
3753
  const remoteAccess = element("div", "dsh-mobile-control__access");
3070
3754
  remoteAccess.hidden = true;
3071
3755
  const remoteAccessLabel = element("span", "dsh-mobile-control__access-label");
@@ -3372,7 +4056,7 @@ window.__ModuleLoader__.load({
3372
4056
  header.append(title, headerActions);
3373
4057
  actions.append(toggle, pair, linkPair);
3374
4058
  lanView.append(lanSetup, access, qrBox, status, extensionStatus, actions, manageRow, devicePanel);
3375
- remoteView.append(remoteIntro, providerSection, remoteWorkspace);
4059
+ remoteView.append(remoteIntro, httpFrameWarning, providerSection, remoteWorkspace);
3376
4060
  diagnosticsView.append(diagnosticsIntro, diagnosticsSummary, diagnosticsToolbar, diagnosticsFeedback, diagnosticsChecks, wsPathsSection, diagnosticsDetails);
3377
4061
  panel.append(header, releaseNotice, updateCard, appDownload, switcher, lanView, remoteView, diagnosticsView);
3378
4062
  root.append(panel);
@@ -3391,8 +4075,23 @@ window.__ModuleLoader__.load({
3391
4075
  let remoteSetupOpenedAt = 0;
3392
4076
  let remoteReconnectBusy = false;
3393
4077
  let remoteProviderBusy = false;
4078
+ let remoteFailureText;
3394
4079
  let cpolarInstalled = false;
3395
4080
  let cpolarConfigured = false;
4081
+ let cloudflaredInstalled = false;
4082
+ let cloudflaredDownloadSize = "";
4083
+ let cloudflaredTunnelMode = "quick";
4084
+ let cloudflaredModeDraft = null;
4085
+ let cloudflaredTunnelConfigured = false;
4086
+ let configuredCloudflaredHostname = "";
4087
+ let configuredCloudflaredPort = 3444;
4088
+ let cloudflaredHostnameDirty = false;
4089
+ let cloudflaredPortDirty = false;
4090
+ let originConfigured = false;
4091
+ let originFormDirty = false;
4092
+ let originFormBusy = false;
4093
+ let remoteSnapshotEpoch = 0;
4094
+ let latestRemoteState = {};
3396
4095
  let frpInstalled = false;
3397
4096
  let frpConfigured = false;
3398
4097
  let frpLayoutInitialized = false;
@@ -3697,24 +4396,38 @@ window.__ModuleLoader__.load({
3697
4396
  });
3698
4397
  });
3699
4398
  const renderRemote = (data) => {
4399
+ latestRemoteState = data;
3700
4400
  remoteRunning = data.running === true;
3701
- remoteProvider = data.provider === "cpolar" ? "cpolar" : data.provider === "frp" ? "frp" : "tailscale";
4401
+ remoteProvider = parseMobileRemoteProvider(data.provider);
3702
4402
  const cpolar = remoteProvider === "cpolar";
4403
+ const cloudflared = remoteProvider === "cloudflared";
3703
4404
  const frp = remoteProvider === "frp";
4405
+ const origin = remoteProvider === "origin";
3704
4406
  const tailscale = remoteProvider === "tailscale";
3705
4407
  tailscaleChoice.classList.toggle("is-selected", tailscale);
3706
4408
  cpolarChoice.classList.toggle("is-selected", cpolar);
4409
+ cloudflaredChoice.classList.toggle("is-selected", cloudflared);
3707
4410
  frpChoice.classList.toggle("is-selected", frp);
4411
+ originChoice.classList.toggle("is-selected", origin);
3708
4412
  tailscaleChoice.setAttribute("aria-checked", String(tailscale));
3709
4413
  cpolarChoice.setAttribute("aria-checked", String(cpolar));
4414
+ cloudflaredChoice.setAttribute("aria-checked", String(cloudflared));
4415
+ tailscaleChoice.tabIndex = tailscale ? 0 : -1;
4416
+ cpolarChoice.tabIndex = cpolar ? 0 : -1;
4417
+ cloudflaredChoice.tabIndex = cloudflared ? 0 : -1;
3710
4418
  frpChoice.setAttribute("aria-pressed", String(frp));
3711
- providerSetupName.textContent = cpolar ? "cpolar" : frp ? t("frpName") : "Tailscale Funnel";
4419
+ originChoice.setAttribute("aria-pressed", String(origin));
4420
+ providerSetupName.textContent = cpolar ? "cpolar" : cloudflared ? "cloudflared" : frp ? t("frpName") : origin ? t("originName") : "Tailscale Funnel";
3712
4421
  tailscaleChoice.disabled = remoteProviderBusy;
3713
4422
  cpolarChoice.disabled = remoteProviderBusy;
4423
+ cloudflaredChoice.disabled = remoteProviderBusy;
3714
4424
  frpChoice.disabled = remoteProviderBusy;
4425
+ originChoice.disabled = remoteProviderBusy;
3715
4426
  cpolarSetup.hidden = !cpolar;
4427
+ cloudflaredSetup.hidden = !cloudflared;
3716
4428
  frpSetup.hidden = !frp;
3717
- if (frp) selfHosted.open = true;
4429
+ originSetup.hidden = !origin;
4430
+ if (frp || origin) selfHosted.open = true;
3718
4431
  tailscaleInfo.hidden = !tailscale;
3719
4432
  remoteReset.textContent = tailscale ? t("resetRemoteLogin") : t("resetRemoteDevices");
3720
4433
  const providers = data.providers !== null && typeof data.providers === "object" ? data.providers : {};
@@ -3736,6 +4449,52 @@ window.__ModuleLoader__.load({
3736
4449
  cpolarConfigure.disabled = remoteProviderBusy;
3737
4450
  cpolarPurge.hidden = !cpolarInstalled && !cpolarConfigured;
3738
4451
  cpolarComponentStatus.textContent = !cpolarSupported ? t("cpolarUnsupported") : !cpolarInstalled ? t("cpolarNotInstalled") : !cpolarConfigured ? t("cpolarNeedsToken", { version: componentVersion }) : t("cpolarReady", { version: componentVersion });
4452
+ const cloudflaredProvider = providers.cloudflared !== null && typeof providers.cloudflared === "object" ? providers.cloudflared : {};
4453
+ const cloudflaredComponent = cloudflaredProvider.component !== null && typeof cloudflaredProvider.component === "object" ? cloudflaredProvider.component : {};
4454
+ cloudflaredInstalled = cloudflaredComponent.installed === true;
4455
+ const cloudflaredSupported = cloudflaredComponent.supported !== false;
4456
+ const cloudflaredComponentError = typeof cloudflaredComponent.errorCode === "string" ? cloudflaredComponent.errorCode : "";
4457
+ const cloudflaredVersion = typeof cloudflaredComponent.version === "string" ? cloudflaredComponent.version : "";
4458
+ const cloudflaredDownloadBytes = typeof cloudflaredComponent.downloadBytes === "number" ? cloudflaredComponent.downloadBytes : 0;
4459
+ if (cloudflaredDownloadBytes > 0) cloudflaredDownloadSize = formatMegabytes(cloudflaredDownloadBytes);
4460
+ const cloudflaredStoragePath = typeof cloudflaredComponent.storagePath === "string" ? cloudflaredComponent.storagePath : `DSH Mobile ${t("pluginPrivateDirectory")}`;
4461
+ cloudflaredStorage.textContent = cloudflaredStoragePath;
4462
+ cloudflaredStorage.title = cloudflaredStoragePath;
4463
+ cloudflaredChoiceBadge.textContent = cloudflaredInstalled ? t("ready") : t("cloudflaredBadge");
4464
+ cloudflaredInstall.hidden = cloudflaredInstalled || !cloudflaredSupported;
4465
+ cloudflaredInstall.textContent = cloudflaredDownloadBytes > 0 ? t("installWithSize", { size: formatMegabytes(cloudflaredDownloadBytes) }) : t("installOfficial");
4466
+ cloudflaredInstall.disabled = remoteProviderBusy;
4467
+ cloudflaredPurge.hidden = !cloudflaredInstalled;
4468
+ cloudflaredComponentStatus.textContent = !cloudflaredSupported ? t("cloudflaredUnsupported") : !cloudflaredInstalled ? cloudflaredComponentError === "cloudflared_component_invalid" ? t("cloudflaredInvalid") : t("cloudflaredNotInstalled") : t("cloudflaredReady", { version: cloudflaredVersion });
4469
+ const cloudflaredTunnelConfiguration = cloudflaredProvider.configuration !== null && typeof cloudflaredProvider.configuration === "object" ? cloudflaredProvider.configuration : {};
4470
+ cloudflaredTunnelMode = cloudflaredTunnelConfiguration.mode === "named" ? "named" : "quick";
4471
+ cloudflaredTunnelConfigured = cloudflaredTunnelConfiguration.configured === true;
4472
+ configuredCloudflaredHostname = typeof cloudflaredTunnelConfiguration.hostname === "string" ? cloudflaredTunnelConfiguration.hostname : "";
4473
+ configuredCloudflaredPort = typeof cloudflaredTunnelConfiguration.port === "number" ? cloudflaredTunnelConfiguration.port : 3444;
4474
+ const cloudflaredTunnelError = typeof cloudflaredTunnelConfiguration.errorCode === "string" ? cloudflaredTunnelConfiguration.errorCode : "";
4475
+ if (cloudflaredModeDraft !== null && cloudflaredModeDraft === cloudflaredTunnelMode) cloudflaredModeDraft = null;
4476
+ const cloudflaredShownMode = cloudflaredModeDraft ?? cloudflaredTunnelMode;
4477
+ cloudflaredModeQuick.classList.toggle("is-active", cloudflaredShownMode === "quick");
4478
+ cloudflaredModeNamed.classList.toggle("is-active", cloudflaredShownMode === "named");
4479
+ cloudflaredModeQuick.setAttribute("aria-pressed", String(cloudflaredShownMode === "quick"));
4480
+ cloudflaredModeNamed.setAttribute("aria-pressed", String(cloudflaredShownMode === "named"));
4481
+ cloudflaredModeQuick.disabled = remoteProviderBusy;
4482
+ cloudflaredModeNamed.disabled = remoteProviderBusy;
4483
+ cloudflaredModeHint.textContent = cloudflaredShownMode === "named" ? t("cloudflaredTunnelNamedHint") : t("cloudflaredTunnelQuickHint");
4484
+ cloudflaredNote.textContent = cloudflaredShownMode === "named" ? t("cloudflaredNamedNote") : t("cloudflaredQuickNote");
4485
+ cloudflaredTunnel.hidden = cloudflaredShownMode !== "named";
4486
+ cloudflaredTunnelSave.textContent = cloudflaredSaveLabel();
4487
+ if (!cloudflaredHostnameDirty) cloudflaredHostname.value = configuredCloudflaredHostname;
4488
+ if (!cloudflaredPortDirty) cloudflaredPort.value = String(configuredCloudflaredPort);
4489
+ cloudflaredToken.placeholder = cloudflaredTunnelConfigured ? t("cloudflaredTunnelTokenSaved") : t("cloudflaredTunnelTokenPlaceholder");
4490
+ const cloudflaredFieldPort = String(cloudflaredPort.value ?? "").trim();
4491
+ cloudflaredTunnelHint.textContent = t("cloudflaredTunnelHint", { port: cloudflaredFieldPort === "" ? String(configuredCloudflaredPort) : cloudflaredFieldPort });
4492
+ cloudflaredTunnelStatus.textContent = cloudflaredTunnelError !== "" ? t("cloudflaredTunnelInvalid") : cloudflaredTunnelConfigured ? t("cloudflaredTunnelConfigured", {
4493
+ hostname: configuredCloudflaredHostname,
4494
+ port: String(configuredCloudflaredPort)
4495
+ }) : t("cloudflaredTunnelMissing");
4496
+ cloudflaredTunnelSave.disabled = remoteProviderBusy || !cloudflaredInstalled;
4497
+ cloudflaredTunnelForget.hidden = !cloudflaredTunnelConfigured;
3739
4498
  const frpProvider = providers.frp !== null && typeof providers.frp === "object" ? providers.frp : {};
3740
4499
  const frpComponent = frpProvider.component !== null && typeof frpProvider.component === "object" ? frpProvider.component : {};
3741
4500
  const frpConfiguration = frpProvider.configuration !== null && typeof frpProvider.configuration === "object" ? frpProvider.configuration : {};
@@ -3776,7 +4535,34 @@ window.__ModuleLoader__.load({
3776
4535
  frpVpsGroup.open = !frpConfigured;
3777
4536
  frpLayoutInitialized = true;
3778
4537
  }
3779
- selfHostedBadge.textContent = frpConfigured && frpInstalled ? t("ready") : t("advanced");
4538
+ const originProvider = providers.origin !== null && typeof providers.origin === "object" ? providers.origin : {};
4539
+ const originConfiguration = originProvider.configuration !== null && typeof originProvider.configuration === "object" ? originProvider.configuration : {};
4540
+ originConfigured = originConfiguration.configured === true;
4541
+ if (!originFormDirty && !originFormBusy) {
4542
+ originPublic.value = typeof originConfiguration.publicOrigin === "string" ? originConfiguration.publicOrigin : "";
4543
+ originHost.value = typeof originConfiguration.listenHost === "string" ? originConfiguration.listenHost : "127.0.0.1";
4544
+ originPort.value = typeof originConfiguration.listenPort === "number" ? String(originConfiguration.listenPort) : "3444";
4545
+ originCidrs.value = Array.isArray(originConfiguration.allowedCidrs) ? originConfiguration.allowedCidrs.filter((value) => typeof value === "string").join(", ") : "127.0.0.0/8";
4546
+ }
4547
+ originSetup.setAttribute("aria-busy", String(originFormBusy));
4548
+ originConfigure.setAttribute("aria-busy", String(originFormBusy));
4549
+ originConfigure.textContent = originFormBusy ? t("saving") : t("originSaveStart");
4550
+ for (const input of [
4551
+ originPublic,
4552
+ originHost,
4553
+ originPort,
4554
+ originCidrs
4555
+ ]) input.disabled = remoteProviderBusy;
4556
+ originConfigure.disabled = remoteProviderBusy;
4557
+ originPurge.disabled = remoteProviderBusy;
4558
+ originPurge.hidden = !originConfigured && originConfiguration.errorCode !== "origin_config_invalid" && originProvider.running !== true;
4559
+ const originListening = origin && originProvider.state === "ready" && originProvider.running === true;
4560
+ const backendOrigin = originListening && typeof data.backendOrigin === "string" ? data.backendOrigin : typeof originConfiguration.backendOrigin === "string" ? originConfiguration.backendOrigin : "";
4561
+ originBackend.hidden = backendOrigin === "";
4562
+ originBackendLabel.textContent = originListening ? t("originBackendListening") : t("originBackendSaved");
4563
+ originBackendAddress.textContent = backendOrigin;
4564
+ originCopyBackend.disabled = remoteProviderBusy || backendOrigin === "";
4565
+ selfHostedBadge.textContent = origin ? originListening ? t("originBackendReady") : t("advanced") : frpConfigured && frpInstalled ? t("ready") : t("advanced");
3780
4566
  const state = typeof data.state === "string" ? data.state : "error";
3781
4567
  const errorCode = typeof data.errorCode === "string" ? data.errorCode : "";
3782
4568
  const remoteOrigin = typeof data.origin === "string" ? data.origin : "";
@@ -3792,7 +4578,7 @@ window.__ModuleLoader__.load({
3792
4578
  remoteStateBadge.classList.toggle("is-ready", remoteReady);
3793
4579
  remoteStateBadge.classList.toggle("is-busy", state === "starting" || state === "connecting" || state === "needs-login");
3794
4580
  remoteStateBadge.classList.toggle("is-attention", state === "error" || state === "unavailable");
3795
- remoteStateBadge.textContent = remoteReady ? t("ready") : state === "starting" || state === "connecting" || state === "needs-login" ? t("remoteStateConnecting") : state === "error" || state === "unavailable" ? t("remoteStateAttention") : t("remoteStateOff");
4581
+ remoteStateBadge.textContent = remoteReady ? origin ? t("originBackendReady") : t("ready") : state === "starting" || state === "connecting" || state === "needs-login" ? t("remoteStateConnecting") : state === "error" || state === "unavailable" ? t("remoteStateAttention") : t("remoteStateOff");
3796
4582
  remoteAccess.hidden = !remoteReady;
3797
4583
  remoteAccessLink.href = remoteOrigin;
3798
4584
  remoteAccessLink.textContent = remoteOrigin;
@@ -3800,58 +4586,35 @@ window.__ModuleLoader__.load({
3800
4586
  remoteStatus.classList.toggle("is-running", remoteReady);
3801
4587
  const labels = {
3802
4588
  off: t("remoteOff"),
3803
- unavailable: cpolar ? t("remoteUnavailableCpolar") : frp ? t("remoteUnavailableFrp") : t("remoteUnavailableTailscale"),
3804
- starting: cpolar ? t("remoteStartingCpolar") : frp ? t("remoteStartingFrp") : t("remoteStartingTailscale"),
4589
+ unavailable: cpolar ? t("remoteUnavailableCpolar") : cloudflared ? t("remoteUnavailableCloudflared") : frp ? t("remoteUnavailableFrp") : origin ? t("originUnavailable") : t("remoteUnavailableTailscale"),
4590
+ starting: cpolar ? t("remoteStartingCpolar") : cloudflared ? t("remoteStartingCloudflared") : frp ? t("remoteStartingFrp") : origin ? t("originStarting") : t("remoteStartingTailscale"),
3805
4591
  "needs-login": t("remoteNeedsLogin"),
3806
- connecting: cpolar ? t("remoteConnectingCpolar") : frp ? t("remoteConnectingFrp") : t("remoteConnectingTailscale"),
3807
- ready: t("remoteReady"),
4592
+ connecting: cpolar ? t("remoteConnectingCpolar") : cloudflared ? t("remoteConnectingCloudflared") : frp ? t("remoteConnectingFrp") : t("remoteConnectingTailscale"),
4593
+ ready: origin ? t("originReady") : t("remoteReady"),
3808
4594
  error: t("remoteError")
3809
4595
  };
3810
- const errorLabels = {
3811
- funnel_permission_required: t("funnelPermission"),
3812
- funnel_https_required: t("funnelHttps"),
3813
- funnel_start_failed: t("funnelStart"),
3814
- funnel_start_timeout: t("funnelTimeout"),
3815
- tailscale_dns_missing: t("tailscaleDnsMissing"),
3816
- gateway_start_failed: t("gatewayStartFailed"),
3817
- control_channel_failed: t("controlChannelFailed"),
3818
- cpolar_component_missing: t("cpolarMissing"),
3819
- cpolar_component_invalid: t("cpolarInvalid"),
3820
- cpolar_config_missing: t("cpolarConfigMissing"),
3821
- cpolar_config_invalid: t("cpolarConfigInvalid"),
3822
- cpolar_port_unavailable: t("cpolarPortUnavailable"),
3823
- cpolar_launch_failed: t("cpolarLaunchFailed"),
3824
- cpolar_start_timeout: t("cpolarTimeout"),
3825
- cpolar_stopped: t("cpolarStopped"),
3826
- cpolar_exited: t("cpolarExited"),
3827
- cpolar_invalid_output: t("cpolarOutputInvalid"),
3828
- cpolar_invalid_origin: t("cpolarOriginInvalid"),
3829
- frp_component_missing: t("frpMissing"),
3830
- frp_component_invalid: t("frpInvalid"),
3831
- frp_config_missing: t("frpConfigMissing"),
3832
- frp_config_verify_failed: t("frpConfigVerifyFailed"),
3833
- frp_vhost_publicly_reachable: t("frpVhostPublic"),
3834
- frp_vhost_probe_failed: t("frpVhostProbeFailed"),
3835
- frp_launch_failed: t("frpLaunchFailed"),
3836
- frp_start_timeout: t("frpTimeout"),
3837
- frp_discovery_mismatch: t("frpDiscoveryMismatch"),
3838
- frp_discovery_invalid: t("frpDiscoveryInvalid"),
3839
- frp_stopped: t("frpStopped"),
3840
- frp_exited: t("frpExited")
3841
- };
4596
+ const errorLabels = translatedRemoteErrors();
3842
4597
  remoteStatus.textContent = remoteSetupPending && needsFunnelSetup ? t("setupOpened") : state === "error" ? errorLabels[errorCode] ?? labels.error : labels[state] ?? labels.error;
4598
+ if (remoteFailureText !== void 0) {
4599
+ remoteStatus.textContent = remoteFailureText;
4600
+ remoteFailureText = void 0;
4601
+ }
3843
4602
  remoteGuide.hidden = !needsFunnelSetup;
3844
4603
  remoteSetup.disabled = remoteSetupUrl === "" || remoteReconnectBusy;
3845
4604
  remoteSetupRetry.disabled = remoteReconnectBusy;
3846
4605
  remoteToggle.textContent = remoteRunning ? t("disableRemote") : t("enableRemote");
3847
- const providerPrepared = cpolar ? cpolarInstalled && cpolarConfigured : frp ? frpInstalled && frpConfigured : true;
3848
- remoteToggle.disabled = remoteProviderBusy || !providerPrepared;
4606
+ const providerPrepared = cpolar ? cpolarInstalled && cpolarConfigured : cloudflared ? cloudflaredInstalled : frp ? frpInstalled && frpConfigured : origin ? originConfigured : true;
4607
+ remoteToggle.disabled = remoteProviderBusy || !providerPrepared && !(origin && remoteRunning);
3849
4608
  remoteLogin.hidden = !tailscale || state !== "needs-login" || remoteLoginUrl === "";
3850
4609
  remoteReconnect.hidden = needsFunnelSetup || state !== "error" && state !== "unavailable" || !providerPrepared;
3851
- remoteActions.hidden = !providerPrepared;
3852
- remotePair.disabled = !remoteReady;
3853
- remoteCopyLink.disabled = !remoteReady;
3854
- remoteDevices.disabled = !remoteReady;
4610
+ remoteActions.hidden = !providerPrepared && !(origin && remoteRunning);
4611
+ remotePair.disabled = !remoteReady || originFormBusy;
4612
+ remoteCopyLink.disabled = !remoteReady || originFormBusy;
4613
+ remoteDevices.disabled = !remoteReady || originFormBusy;
4614
+ if (origin) {
4615
+ remoteReconnect.disabled = remoteProviderBusy || remoteReconnectBusy;
4616
+ remoteReset.disabled = remoteProviderBusy;
4617
+ } else remoteReset.disabled = false;
3855
4618
  if (!remoteReady) {
3856
4619
  remoteQr.hidden = true;
3857
4620
  remotePairLink.hidden = true;
@@ -3862,10 +4625,13 @@ window.__ModuleLoader__.load({
3862
4625
  };
3863
4626
  let remoteLoadInFlight = false;
3864
4627
  const loadRemote = () => {
3865
- if (remoteLoadInFlight) return;
4628
+ if (remoteLoadInFlight || originFormBusy) return;
4629
+ const epoch = remoteSnapshotEpoch;
3866
4630
  remoteLoadInFlight = true;
3867
- controlRequestJson("/api/mobile-access/remote/control").then(renderRemote, (error) => {
3868
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
4631
+ controlRequestJson("/api/mobile-access/remote/control").then((data) => {
4632
+ if (epoch === remoteSnapshotEpoch) renderRemote(data);
4633
+ }, (error) => {
4634
+ if (epoch === remoteSnapshotEpoch) remoteStatus.textContent = t("requestFailed", { error: String(error) });
3869
4635
  }).finally(() => {
3870
4636
  remoteLoadInFlight = false;
3871
4637
  });
@@ -3876,13 +4642,16 @@ window.__ModuleLoader__.load({
3876
4642
  remoteProviderBusy = true;
3877
4643
  tailscaleChoice.disabled = true;
3878
4644
  cpolarChoice.disabled = true;
4645
+ cloudflaredChoice.disabled = true;
3879
4646
  frpChoice.disabled = true;
3880
- remoteStatus.textContent = provider === "cpolar" ? t("switchingCpolar") : provider === "frp" ? t("switchingFrp") : t("switchingTailscale");
4647
+ originChoice.disabled = true;
4648
+ remoteStatus.textContent = provider === "cpolar" ? t("switchingCpolar") : provider === "cloudflared" ? t("switchingCloudflared") : provider === "frp" ? t("switchingFrp") : provider === "origin" ? t("switchingOrigin") : t("switchingTailscale");
3881
4649
  controlRequestJson("/api/mobile-access/remote/provider", {
3882
4650
  method: "POST",
3883
4651
  body: JSON.stringify({ provider })
3884
4652
  }).then(renderRemote, (error) => {
3885
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
4653
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
4654
+ remoteStatus.textContent = remoteFailureText;
3886
4655
  }).finally(() => {
3887
4656
  remoteProviderBusy = false;
3888
4657
  loadRemote();
@@ -3894,9 +4663,159 @@ window.__ModuleLoader__.load({
3894
4663
  cpolarChoice.addEventListener("click", () => {
3895
4664
  chooseRemoteProvider("cpolar");
3896
4665
  });
4666
+ cloudflaredChoice.addEventListener("click", () => {
4667
+ chooseRemoteProvider("cloudflared");
4668
+ });
3897
4669
  frpChoice.addEventListener("click", () => {
3898
4670
  chooseRemoteProvider("frp");
3899
4671
  });
4672
+ originChoice.addEventListener("click", () => {
4673
+ chooseRemoteProvider("origin");
4674
+ });
4675
+ cloudflaredInstall.addEventListener("click", () => {
4676
+ if (remoteProviderBusy) return;
4677
+ if (!window.confirm(t("installConfirmCloudflared", { size: cloudflaredDownloadSize }))) return;
4678
+ remoteProviderBusy = true;
4679
+ cloudflaredInstall.disabled = true;
4680
+ cloudflaredInstall.textContent = t("downloading");
4681
+ remoteStatus.textContent = t("installingCloudflared");
4682
+ controlRequestJson("/api/mobile-access/remote/cloudflared/component/install", {
4683
+ method: "POST",
4684
+ body: JSON.stringify({ confirm: true })
4685
+ }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
4686
+ remoteFailureText = remoteFailureTextFor(error, "installFailed");
4687
+ remoteStatus.textContent = remoteFailureText;
4688
+ }).finally(() => {
4689
+ remoteProviderBusy = false;
4690
+ loadRemote();
4691
+ });
4692
+ });
4693
+ cloudflaredPurge.addEventListener("click", () => {
4694
+ if (remoteProviderBusy) return;
4695
+ if (!window.confirm(t("purgeCloudflaredConfirm"))) return;
4696
+ remoteProviderBusy = true;
4697
+ cloudflaredPurge.disabled = true;
4698
+ remoteStatus.textContent = t("purgingCloudflared");
4699
+ controlRequestJson("/api/mobile-access/remote/cloudflared/component/purge", {
4700
+ method: "POST",
4701
+ body: JSON.stringify({ confirm: true })
4702
+ }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
4703
+ remoteFailureText = t("purgeFailed", { error: String(error) });
4704
+ remoteStatus.textContent = remoteFailureText;
4705
+ }).finally(() => {
4706
+ remoteProviderBusy = false;
4707
+ cloudflaredPurge.disabled = false;
4708
+ loadRemote();
4709
+ });
4710
+ });
4711
+ const showCloudflaredMode = (mode) => {
4712
+ cloudflaredModeDraft = mode;
4713
+ cloudflaredModeQuick.classList.toggle("is-active", mode === "quick");
4714
+ cloudflaredModeNamed.classList.toggle("is-active", mode === "named");
4715
+ cloudflaredModeQuick.setAttribute("aria-pressed", String(mode === "quick"));
4716
+ cloudflaredModeNamed.setAttribute("aria-pressed", String(mode === "named"));
4717
+ cloudflaredModeHint.textContent = mode === "named" ? t("cloudflaredTunnelNamedHint") : t("cloudflaredTunnelQuickHint");
4718
+ cloudflaredNote.textContent = mode === "named" ? t("cloudflaredNamedNote") : t("cloudflaredQuickNote");
4719
+ cloudflaredTunnel.hidden = mode !== "named";
4720
+ cloudflaredTunnelSave.textContent = cloudflaredSaveLabel();
4721
+ };
4722
+ cloudflaredModeQuick.addEventListener("click", () => {
4723
+ if (!remoteProviderBusy) showCloudflaredMode("quick");
4724
+ });
4725
+ cloudflaredModeNamed.addEventListener("click", () => {
4726
+ if (!remoteProviderBusy) showCloudflaredMode("named");
4727
+ });
4728
+ cloudflaredHostname.addEventListener("input", () => {
4729
+ cloudflaredHostnameDirty = true;
4730
+ });
4731
+ cloudflaredPort.addEventListener("input", () => {
4732
+ cloudflaredPortDirty = true;
4733
+ });
4734
+ cloudflaredTunnelSave.addEventListener("click", () => {
4735
+ if (remoteProviderBusy) return;
4736
+ const mode = cloudflaredModeDraft ?? cloudflaredTunnelMode;
4737
+ const hostname = String(cloudflaredHostname.value ?? "").trim();
4738
+ const portValue = String(cloudflaredPort.value ?? "").trim();
4739
+ const port = Number(portValue);
4740
+ const token = String(cloudflaredToken.value ?? "");
4741
+ if (mode === "named") {
4742
+ if (hostname === "") {
4743
+ remoteStatus.textContent = t("cloudflaredTunnelHostnameInvalid");
4744
+ return;
4745
+ }
4746
+ if (portValue === "" || !Number.isSafeInteger(port) || port < 1024 || port > 65535) {
4747
+ remoteStatus.textContent = t("cloudflaredTunnelPortInvalid");
4748
+ return;
4749
+ }
4750
+ if (port === 3443) {
4751
+ remoteStatus.textContent = t("cloudflaredTunnelPortReserved");
4752
+ return;
4753
+ }
4754
+ if (token === "" && !cloudflaredTunnelConfigured) {
4755
+ remoteStatus.textContent = t("cloudflaredTunnelConfigMissing");
4756
+ return;
4757
+ }
4758
+ if (token !== "" && (token.length < 32 || !/^[A-Za-z0-9_=+/-]+$/u.test(token))) {
4759
+ remoteStatus.textContent = t("cloudflaredTunnelTokenInvalid");
4760
+ return;
4761
+ }
4762
+ }
4763
+ remoteProviderBusy = true;
4764
+ cloudflaredTunnelSave.disabled = true;
4765
+ cloudflaredTunnelSave.textContent = t("saving");
4766
+ remoteStatus.textContent = mode === "named" ? t("cloudflaredTunnelSaving") : t("switchingCloudflared");
4767
+ controlRequestJson("/api/mobile-access/remote/cloudflared/tunnel", {
4768
+ method: "POST",
4769
+ body: JSON.stringify(mode === "named" ? {
4770
+ mode,
4771
+ hostname,
4772
+ port,
4773
+ token
4774
+ } : { mode: "quick" })
4775
+ }).then((data) => {
4776
+ cloudflaredToken.value = "";
4777
+ cloudflaredModeDraft = null;
4778
+ cloudflaredHostnameDirty = false;
4779
+ cloudflaredPortDirty = false;
4780
+ remoteFailureText = "";
4781
+ renderRemote(data);
4782
+ }, (error) => {
4783
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
4784
+ remoteStatus.textContent = remoteFailureText;
4785
+ }).finally(() => {
4786
+ remoteProviderBusy = false;
4787
+ cloudflaredTunnelSave.disabled = false;
4788
+ cloudflaredTunnelSave.textContent = cloudflaredSaveLabel();
4789
+ loadRemote();
4790
+ });
4791
+ });
4792
+ cloudflaredTunnelForget.addEventListener("click", () => {
4793
+ if (remoteProviderBusy) return;
4794
+ if (!window.confirm(t("cloudflaredTunnelForgetConfirm"))) return;
4795
+ remoteProviderBusy = true;
4796
+ cloudflaredTunnelForget.disabled = true;
4797
+ remoteStatus.textContent = t("cloudflaredTunnelForgetting");
4798
+ controlRequestJson("/api/mobile-access/remote/cloudflared/tunnel/purge", {
4799
+ method: "POST",
4800
+ body: JSON.stringify({ confirm: true })
4801
+ }).then((data) => {
4802
+ cloudflaredToken.value = "";
4803
+ cloudflaredHostname.value = "";
4804
+ cloudflaredPort.value = "3444";
4805
+ cloudflaredModeDraft = null;
4806
+ cloudflaredHostnameDirty = false;
4807
+ cloudflaredPortDirty = false;
4808
+ remoteFailureText = "";
4809
+ renderRemote(data);
4810
+ }, (error) => {
4811
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
4812
+ remoteStatus.textContent = remoteFailureText;
4813
+ }).finally(() => {
4814
+ remoteProviderBusy = false;
4815
+ cloudflaredTunnelForget.disabled = false;
4816
+ loadRemote();
4817
+ });
4818
+ });
3900
4819
  cpolarInstall.addEventListener("click", () => {
3901
4820
  if (remoteProviderBusy) return;
3902
4821
  if (!window.confirm(t("installConfirm"))) return;
@@ -3908,7 +4827,8 @@ window.__ModuleLoader__.load({
3908
4827
  method: "POST",
3909
4828
  body: JSON.stringify({ confirm: true })
3910
4829
  }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
3911
- remoteStatus.textContent = t("installFailed", { error: String(error) });
4830
+ remoteFailureText = remoteFailureTextFor(error, "installFailed");
4831
+ remoteStatus.textContent = remoteFailureText;
3912
4832
  }).finally(() => {
3913
4833
  remoteProviderBusy = false;
3914
4834
  loadRemote();
@@ -3937,7 +4857,8 @@ window.__ModuleLoader__.load({
3937
4857
  body: JSON.stringify({ running: true })
3938
4858
  });
3939
4859
  }).then(renderRemote, (error) => {
3940
- remoteStatus.textContent = t("configureFailed", { error: String(error) });
4860
+ remoteFailureText = t("configureFailed", { error: String(error) });
4861
+ remoteStatus.textContent = remoteFailureText;
3941
4862
  }).finally(() => {
3942
4863
  remoteProviderBusy = false;
3943
4864
  cpolarConfigure.setAttribute("aria-busy", "false");
@@ -3955,7 +4876,8 @@ window.__ModuleLoader__.load({
3955
4876
  method: "POST",
3956
4877
  body: JSON.stringify({ confirm: true })
3957
4878
  }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
3958
- remoteStatus.textContent = t("purgeFailed", { error: String(error) });
4879
+ remoteFailureText = t("purgeFailed", { error: String(error) });
4880
+ remoteStatus.textContent = remoteFailureText;
3959
4881
  }).finally(() => {
3960
4882
  remoteProviderBusy = false;
3961
4883
  cpolarPurge.disabled = false;
@@ -4255,7 +5177,8 @@ window.__ModuleLoader__.load({
4255
5177
  method: "POST",
4256
5178
  body: JSON.stringify({ confirm: true })
4257
5179
  }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
4258
- remoteStatus.textContent = t("installFailed", { error: String(error) });
5180
+ remoteFailureText = remoteFailureTextFor(error, "installFailed");
5181
+ remoteStatus.textContent = remoteFailureText;
4259
5182
  }).finally(() => {
4260
5183
  remoteProviderBusy = false;
4261
5184
  loadRemote();
@@ -4284,7 +5207,8 @@ window.__ModuleLoader__.load({
4284
5207
  body: JSON.stringify({ running: true })
4285
5208
  });
4286
5209
  }).then(renderRemote, (error) => {
4287
- remoteStatus.textContent = t("configureFailed", { error: String(error) });
5210
+ remoteFailureText = t("configureFailed", { error: String(error) });
5211
+ remoteStatus.textContent = remoteFailureText;
4288
5212
  }).finally(() => {
4289
5213
  remoteProviderBusy = false;
4290
5214
  frpConfigure.setAttribute("aria-busy", "false");
@@ -4301,20 +5225,132 @@ window.__ModuleLoader__.load({
4301
5225
  method: "POST",
4302
5226
  body: JSON.stringify({ confirm: true })
4303
5227
  }, LONG_CONTROL_REQUEST_TIMEOUT_MS).then(renderRemote, (error) => {
4304
- remoteStatus.textContent = t("purgeFailed", { error: String(error) });
5228
+ remoteFailureText = t("purgeFailed", { error: String(error) });
5229
+ remoteStatus.textContent = remoteFailureText;
4305
5230
  }).finally(() => {
4306
5231
  remoteProviderBusy = false;
4307
5232
  frpPurge.disabled = false;
4308
5233
  loadRemote();
4309
5234
  });
4310
5235
  });
5236
+ const clearOriginValidation = () => {
5237
+ for (const input of [
5238
+ originPublic,
5239
+ originHost,
5240
+ originPort,
5241
+ originCidrs
5242
+ ]) input.removeAttribute("aria-invalid");
5243
+ };
5244
+ const showOriginFeedback = (message, error = false) => {
5245
+ originFeedback.textContent = message;
5246
+ originFeedback.hidden = false;
5247
+ originFeedback.classList.toggle("is-error", error);
5248
+ };
5249
+ const showOriginError = (error) => {
5250
+ const code = error instanceof Error ? error.message : String(error);
5251
+ const key = ORIGIN_ERROR_MESSAGE_KEYS[code];
5252
+ const fields = {
5253
+ origin_public_origin_invalid: originPublic,
5254
+ origin_listen_host_invalid: originHost,
5255
+ origin_listen_address_unavailable: originHost,
5256
+ origin_listen_port_invalid: originPort,
5257
+ origin_listen_port_reserved: originPort,
5258
+ origin_listen_port_in_use: originPort,
5259
+ origin_allowed_cidrs_invalid: originCidrs
5260
+ };
5261
+ clearOriginValidation();
5262
+ fields[code]?.setAttribute("aria-invalid", "true");
5263
+ showOriginFeedback(key === void 0 ? t("configureFailed", { error: String(error) }) : t(key), true);
5264
+ };
5265
+ const setOriginFormBusy = (busy) => {
5266
+ originFormBusy = busy;
5267
+ remoteProviderBusy = busy;
5268
+ remoteSnapshotEpoch += 1;
5269
+ renderRemote(latestRemoteState);
5270
+ };
5271
+ for (const input of [
5272
+ originPublic,
5273
+ originHost,
5274
+ originPort,
5275
+ originCidrs
5276
+ ]) input.addEventListener("input", () => {
5277
+ originFormDirty = true;
5278
+ clearOriginValidation();
5279
+ originFeedback.hidden = true;
5280
+ });
5281
+ originConfigure.addEventListener("click", () => {
5282
+ if (remoteProviderBusy || remoteProvider !== "origin") return;
5283
+ clearOriginValidation();
5284
+ for (const input of [
5285
+ originPublic,
5286
+ originHost,
5287
+ originPort,
5288
+ originCidrs
5289
+ ]) if (!input.reportValidity()) return;
5290
+ const form = {
5291
+ publicOrigin: originPublic.value.trim(),
5292
+ listenHost: originHost.value.trim(),
5293
+ listenPort: Number(originPort.value),
5294
+ allowedCidrs: originCidrs.value.trim().split(/[\s,]+/u).filter(Boolean)
5295
+ };
5296
+ setOriginFormBusy(true);
5297
+ showOriginFeedback(t("originSaving"));
5298
+ controlRequestJson("/api/mobile-access/remote/origin/configure", {
5299
+ method: "POST",
5300
+ body: JSON.stringify(form)
5301
+ }).then(() => controlRequestJson("/api/mobile-access/remote/control", {
5302
+ method: "POST",
5303
+ body: JSON.stringify({ running: true })
5304
+ })).then((data) => {
5305
+ originFormDirty = false;
5306
+ renderRemote(data);
5307
+ if (data.state === "error") showOriginError(new Error(typeof data.errorCode === "string" ? data.errorCode : "origin_gateway_start_failed"));
5308
+ else originFeedback.hidden = true;
5309
+ }, showOriginError).finally(() => {
5310
+ setOriginFormBusy(false);
5311
+ [
5312
+ originPublic,
5313
+ originHost,
5314
+ originPort,
5315
+ originCidrs
5316
+ ].find((input) => input.getAttribute("aria-invalid") === "true")?.focus();
5317
+ loadRemote();
5318
+ });
5319
+ });
5320
+ originPurge.addEventListener("click", () => {
5321
+ if (remoteProviderBusy || !window.confirm(t("originPurgeConfirm"))) return;
5322
+ setOriginFormBusy(true);
5323
+ showOriginFeedback(t("originPurging"));
5324
+ controlRequestJson("/api/mobile-access/remote/origin/purge", {
5325
+ method: "POST",
5326
+ body: JSON.stringify({ confirm: true })
5327
+ }).then((data) => {
5328
+ originFormDirty = false;
5329
+ clearOriginValidation();
5330
+ renderRemote(data);
5331
+ showOriginFeedback(t("originPurged"));
5332
+ }, showOriginError).finally(() => {
5333
+ setOriginFormBusy(false);
5334
+ loadRemote();
5335
+ });
5336
+ });
5337
+ originCopyBackend.addEventListener("click", () => {
5338
+ const address = originBackendAddress.textContent ?? "";
5339
+ if (address === "" || remoteProviderBusy) return;
5340
+ Promise.resolve().then(() => navigator.clipboard.writeText(address)).then(() => {
5341
+ showOriginFeedback(t("originBackendCopied"));
5342
+ }, (error) => {
5343
+ showOriginFeedback(t("requestFailed", { error: String(error) }), true);
5344
+ });
5345
+ });
4311
5346
  remoteToggle.addEventListener("click", () => {
4312
5347
  remoteToggle.disabled = true;
4313
5348
  controlRequestJson("/api/mobile-access/remote/control", {
4314
5349
  method: "POST",
4315
5350
  body: JSON.stringify({ running: !remoteRunning })
4316
5351
  }).then(renderRemote, (error) => {
4317
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5352
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5353
+ remoteStatus.textContent = remoteFailureText;
4318
5354
  }).finally(loadRemote);
4319
5355
  });
4320
5356
  remoteLogin.addEventListener("click", () => {
@@ -4326,12 +5362,13 @@ window.__ModuleLoader__.load({
4326
5362
  remoteReconnect.disabled = true;
4327
5363
  remoteSetup.disabled = true;
4328
5364
  remoteSetupRetry.disabled = true;
4329
- remoteStatus.textContent = remoteProvider === "cpolar" ? t("reconnectingCpolar") : remoteProvider === "frp" ? t("reconnectingFrp") : t("reconnectingTailscale");
5365
+ remoteStatus.textContent = remoteProvider === "cpolar" ? t("reconnectingCpolar") : remoteProvider === "cloudflared" ? t("reconnectingCloudflared") : remoteProvider === "frp" ? t("reconnectingFrp") : remoteProvider === "origin" ? t("reconnectingOrigin") : t("reconnectingTailscale");
4330
5366
  controlRequestJson("/api/mobile-access/remote/reconnect", {
4331
5367
  method: "POST",
4332
5368
  body: "{}"
4333
5369
  }).then(renderRemote, (error) => {
4334
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5370
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5371
+ remoteStatus.textContent = remoteFailureText;
4335
5372
  }).finally(() => {
4336
5373
  remoteReconnectBusy = false;
4337
5374
  remoteReconnect.disabled = false;
@@ -4387,7 +5424,8 @@ window.__ModuleLoader__.load({
4387
5424
  remotePair.addEventListener("click", () => {
4388
5425
  remotePair.disabled = true;
4389
5426
  openRemotePairing().catch((error) => {
4390
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5427
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5428
+ remoteStatus.textContent = remoteFailureText;
4391
5429
  }).finally(() => {
4392
5430
  remotePair.disabled = !remoteReady;
4393
5431
  });
@@ -4395,7 +5433,8 @@ window.__ModuleLoader__.load({
4395
5433
  remoteCopyLink.addEventListener("click", () => {
4396
5434
  remoteCopyLink.disabled = true;
4397
5435
  (activeRemotePairUrl !== "" && activeRemotePairExpiresAt > Date.now() ? copyRemotePairUrl() : openRemotePairing()).catch((error) => {
4398
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5436
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5437
+ remoteStatus.textContent = remoteFailureText;
4399
5438
  }).finally(() => {
4400
5439
  remoteCopyLink.disabled = !remoteReady;
4401
5440
  });
@@ -4424,7 +5463,8 @@ window.__ModuleLoader__.load({
4424
5463
  method: "POST",
4425
5464
  body: JSON.stringify({ deviceId: id })
4426
5465
  }).then(loadRemoteDevices, (error) => {
4427
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5466
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5467
+ remoteStatus.textContent = remoteFailureText;
4428
5468
  });
4429
5469
  });
4430
5470
  row.append(label, meta, revoke);
@@ -4433,7 +5473,8 @@ window.__ModuleLoader__.load({
4433
5473
  };
4434
5474
  const loadRemoteDevices = () => {
4435
5475
  controlRequestJson("/api/mobile-access/remote/devices").then(renderRemoteDevices, (error) => {
4436
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5476
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5477
+ remoteStatus.textContent = remoteFailureText;
4437
5478
  });
4438
5479
  };
4439
5480
  remoteDevices.addEventListener("click", () => {
@@ -4442,13 +5483,14 @@ window.__ModuleLoader__.load({
4442
5483
  if (show) loadRemoteDevices();
4443
5484
  });
4444
5485
  remoteReset.addEventListener("click", () => {
4445
- const prompt = remoteProvider === "cpolar" ? t("resetCpolarConfirm") : remoteProvider === "frp" ? t("resetFrpConfirm") : t("resetTailscaleConfirm");
5486
+ const prompt = remoteProvider === "cpolar" ? t("resetCpolarConfirm") : remoteProvider === "cloudflared" ? t("resetCloudflaredConfirm") : remoteProvider === "frp" ? t("resetFrpConfirm") : remoteProvider === "origin" ? t("resetOriginConfirm") : t("resetTailscaleConfirm");
4446
5487
  if (!window.confirm(prompt)) return;
4447
5488
  controlRequestJson("/api/mobile-access/remote/reset", {
4448
5489
  method: "POST",
4449
5490
  body: JSON.stringify({ confirm: true })
4450
5491
  }).then(renderRemote, (error) => {
4451
- remoteStatus.textContent = t("requestFailed", { error: String(error) });
5492
+ remoteFailureText = remoteFailureTextFor(error, "requestFailed");
5493
+ remoteStatus.textContent = remoteFailureText;
4452
5494
  });
4453
5495
  });
4454
5496
  const renderDiagnostics = (data) => {
@@ -4479,12 +5521,21 @@ window.__ModuleLoader__.load({
4479
5521
  const templates = DIAGNOSTIC_REASON_MESSAGES[locale][reason];
4480
5522
  if (templates === void 0) return serverCopy;
4481
5523
  const facts = entry.facts !== null && typeof entry.facts === "object" ? entry.facts : {};
5524
+ const providerNames = {
5525
+ tailscale: "Tailscale Funnel",
5526
+ cpolar: "cpolar",
5527
+ cloudflared: "cloudflared",
5528
+ frp: t("frpName"),
5529
+ origin: t("originName")
5530
+ };
5531
+ const providerId = typeof facts.provider === "string" ? facts.provider : "";
5532
+ const controllerKey = typeof facts.controllerCode === "string" ? REMOTE_ERROR_MESSAGE_KEYS[facts.controllerCode] : void 0;
4482
5533
  const values = {
4483
- provider: facts.provider === "tailscale" || facts.provider === "cpolar" || facts.provider === "frp" ? facts.provider : "",
5534
+ provider: providerNames[providerId] ?? "",
4484
5535
  latencyMs: typeof facts.latencyMs === "number" && Number.isFinite(facts.latencyMs) ? new Intl.NumberFormat(localeTag).format(facts.latencyMs) : "",
4485
5536
  interfaceName: typeof facts.interfaceName === "string" ? facts.interfaceName : "",
4486
5537
  endpointSuffix: typeof facts.endpointSuffix === "string" ? facts.endpointSuffix : "",
4487
- controllerCode: typeof facts.controllerCode === "string" ? facts.controllerCode : ""
5538
+ controllerCode: controllerKey === void 0 ? "" : t(controllerKey)
4488
5539
  };
4489
5540
  const interpolate = (template) => template.replace(/\{(\w+)\}/gu, (_match, key) => values[key] ?? "");
4490
5541
  let detail = interpolate(templates[0]);
@@ -4499,6 +5550,7 @@ window.__ModuleLoader__.load({
4499
5550
  }
4500
5551
  if (reason === "remote-controller-error") {
4501
5552
  const actionKey = {
5553
+ ...ORIGIN_ERROR_MESSAGE_KEYS,
4502
5554
  component_missing: "remoteUnavailableTailscale",
4503
5555
  funnel_permission_required: "funnelPermission",
4504
5556
  funnel_https_required: "funnelHttps",
@@ -4516,6 +5568,22 @@ window.__ModuleLoader__.load({
4516
5568
  cpolar_start_timeout: "cpolarTimeout",
4517
5569
  cpolar_stopped: "cpolarStopped",
4518
5570
  cpolar_exited: "cpolarExited",
5571
+ cloudflared_component_missing: "cloudflaredMissing",
5572
+ cloudflared_component_invalid: "cloudflaredInvalid",
5573
+ cloudflared_component_unsupported: "cloudflaredComponentUnsupported",
5574
+ cloudflared_port_unavailable: "cloudflaredPortUnavailable",
5575
+ cloudflared_port_reservation_failed: "cloudflaredPortReservationFailed",
5576
+ cloudflared_launch_failed: "cloudflaredLaunchFailed",
5577
+ cloudflared_start_timeout: "cloudflaredTimeout",
5578
+ cloudflared_stopped: "cloudflaredStopped",
5579
+ cloudflared_exited: "cloudflaredExited",
5580
+ cloudflared_invalid_origin: "cloudflaredOriginInvalid",
5581
+ cloudflared_tunnel_port_unavailable: "cloudflaredTunnelPortUnavailable",
5582
+ cloudflared_tunnel_config_invalid: "cloudflaredTunnelInvalid",
5583
+ cloudflared_tunnel_target_invalid: "cloudflaredTunnelTargetInvalid",
5584
+ cloudflared_download_hash_mismatch: "cloudflaredDownloadHashMismatch",
5585
+ cloudflared_download_size_mismatch: "cloudflaredDownloadSizeMismatch",
5586
+ cloudflared_executable_hash_mismatch: "cloudflaredExecutableHashMismatch",
4519
5587
  frp_component_missing: "frpMissing",
4520
5588
  frp_component_invalid: "frpInvalid",
4521
5589
  frp_config_missing: "frpConfigMissing",
@@ -4918,6 +5986,15 @@ window.__ModuleLoader__.load({
4918
5986
  if (generation !== void 0) result.set("x-dsh-mobile-extension-generation", generation);
4919
5987
  return result;
4920
5988
  }
5989
+ /** Build the JSON request used by a mobile extension to invoke a Host action. */
5990
+ function extensionActionRequestInit(generation, input, signal) {
5991
+ return {
5992
+ method: "POST",
5993
+ headers: extensionGenerationHeaders(generation, { "content-type": "application/json" }),
5994
+ body: JSON.stringify(input ?? {}),
5995
+ signal
5996
+ };
5997
+ }
4921
5998
  function registerUniqueDisposable(entries, claimedIds, id, mount) {
4922
5999
  if (claimedIds.has(id)) throw new Error(`duplicate lifecycle id: ${id}`);
4923
6000
  claimedIds.add(id);
@@ -5482,12 +6559,7 @@ window.__ModuleLoader__.load({
5482
6559
  host: {
5483
6560
  invoke: (action, input) => {
5484
6561
  ensureCurrent();
5485
- return mobileRequest(`/mobile-access/extensions/${encodeURIComponent(id)}/actions/${encodeURIComponent(action)}`, {
5486
- method: "POST",
5487
- headers: extensionGenerationHeaders(hostGeneration),
5488
- body: JSON.stringify(input ?? {}),
5489
- signal: controller.signal
5490
- }).then(async (response) => {
6562
+ return mobileRequest(`/mobile-access/extensions/${encodeURIComponent(id)}/actions/${encodeURIComponent(action)}`, extensionActionRequestInit(hostGeneration, input, controller.signal)).then(async (response) => {
5491
6563
  const value = await response.json();
5492
6564
  if (!response.ok) throw new Error(typeof value === "object" && value !== null && "error" in value ? String(value.error) : `HTTP ${String(response.status)}`);
5493
6565
  return value;
@@ -5950,31 +7022,38 @@ window.__ModuleLoader__.load({
5950
7022
  /** Theme-aware desktop Mobile Access panel styles. */
5951
7023
  const CONTROL_STYLES = `
5952
7024
  .dsh-mobile-control{position:fixed;z-index:1000;left:16px;bottom:112px;font:14px/1.45 system-ui;color:var(--dsw-alias-label-primary,#16181d)}
5953
- .dsh-mobile-control__panel{box-sizing:border-box;width:min(380px,calc(100vw - 32px));max-height:calc(100vh - 140px);overflow-y:auto;padding:16px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:18px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 18px 50px rgb(15 23 42 / 18%)}
5954
- .dsh-mobile-control__header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px}.dsh-mobile-control__panel h2{margin:0;font-size:17px;line-height:24px}.dsh-mobile-control__header-actions{display:flex;align-items:center;gap:2px}.dsh-mobile-control__update-plugin,.dsh-mobile-control__diagnostic-entry,.dsh-mobile-control__close{display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:44px;padding:0;border:0;border-radius:10px;background:transparent;color:inherit;cursor:pointer}.dsh-mobile-control__update-plugin,.dsh-mobile-control__diagnostic-entry{padding:0 8px;color:#2563eb;font:650 12px/1 system-ui;white-space:nowrap}.dsh-mobile-control__update-plugin[hidden]{display:none}.dsh-mobile-control__update-plugin:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__close{font-size:24px;line-height:1}.dsh-mobile-control__update-plugin:hover:not(:disabled),.dsh-mobile-control__diagnostic-entry:hover,.dsh-mobile-control__close:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}
5955
- .dsh-mobile-control__release-notice{margin:-2px 0 10px;padding:8px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#eff6ff);color:var(--dsw-alias-label-primary,#1d4ed8);font-size:11px;line-height:1.45}.dsh-mobile-control__release-notice.is-error{color:#dc2626}.dsh-mobile-control__release-notice[hidden]{display:none}.dsh-mobile-control__update-card{margin:0 0 10px;padding:11px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__update-card[hidden]{display:none}.dsh-mobile-control__update-card-title{margin:0 0 7px;color:var(--dsw-alias-label-primary,#16181d);font:650 13px/1.4 system-ui}.dsh-mobile-control__update-notes{display:block;box-sizing:border-box;max-height:150px;overflow-y:auto;margin:0 0 8px;padding:8px 10px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:9px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#475569);white-space:pre-wrap;word-break:break-word;font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.dsh-mobile-control__update-notes[hidden]{display:none}.dsh-mobile-control__update-notice{margin:0 0 9px;padding:7px 9px;border-radius:8px;background:#fff4d6;color:#92400e;font-size:11px;line-height:1.5}.dsh-mobile-control__update-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px}.dsh-mobile-control__update-actions .dsh-mobile-control__primary,.dsh-mobile-control__update-actions .dsh-mobile-control__secondary{min-height:34px;padding:0 12px}.dsh-mobile-control__app-download{display:flex;align-items:center;justify-content:space-between;box-sizing:border-box;min-height:38px;margin:0 0 10px;padding:8px 11px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f7f8fa);color:var(--dsw-alias-label-primary,#16181d);font:600 12px/1.3 system-ui;text-decoration:none}.dsh-mobile-control__app-download[hidden]{display:none}.dsh-mobile-control__app-download::after{color:#2563eb;font-size:14px;content:"↗"}.dsh-mobile-control__app-download:hover{border-color:#9fb9e8;background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-bg-layer-2,#f5f8ff));color:var(--dsw-alias-label-primary,#1d4ed8)}
5956
- .dsh-mobile-control__switcher{display:grid;grid-template-columns:repeat(2,1fr);gap:4px;margin:0 0 14px;padding:4px;border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f3f5f8)}.dsh-mobile-control__switcher[hidden]{display:none}.dsh-mobile-control__tab{min-height:36px;border:0;border-radius:9px;background:transparent;color:var(--dsw-alias-label-secondary,#606873);font:600 13px/1 system-ui;cursor:pointer}.dsh-mobile-control__tab.is-active{background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);box-shadow:0 1px 3px rgb(15 23 42 / 10%)}.dsh-mobile-control__view[hidden]{display:none}.dsh-mobile-control__intro{margin:0 0 12px;color:var(--dsw-alias-label-secondary,#606873);font-size:12px;line-height:1.55}.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions{display:grid;grid-template-columns:1fr 1fr;gap:8px}.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions button[hidden]{display:none}
5957
- .dsh-mobile-control__provider-section{position:relative;margin:0 0 14px}.dsh-mobile-control__section-title{margin:0 0 8px;color:var(--dsw-alias-label-primary,#16181d);font:650 13px/1.4 system-ui}.dsh-mobile-control__provider-section>.dsh-mobile-control__section-title{padding-right:42px}.dsh-mobile-control__provider-choices{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.dsh-mobile-control__provider{display:flex;min-width:0;flex-direction:column;gap:6px;min-height:94px;padding:10px 11px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);text-align:left;cursor:pointer;touch-action:manipulation;transition:border-color 160ms ease,background-color 160ms ease,box-shadow 160ms ease}.dsh-mobile-control__provider:hover{border-color:#6f96db;background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-bg-layer-1,#f8fbff))}.dsh-mobile-control__provider.is-selected{border-color:#2563eb;background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-bg-layer-1,#f5f8ff));box-shadow:0 0 0 1px #2563eb inset}.dsh-mobile-control__provider:disabled{cursor:wait;opacity:.62}.dsh-mobile-control__provider-top{display:flex;min-width:0;align-items:flex-start;justify-content:space-between;gap:5px}.dsh-mobile-control__provider-top strong{min-width:0;font-size:12px;line-height:1.3}.dsh-mobile-control__provider-badge{flex:none;padding:2px 5px;border-radius:999px;background:#e8f0ff;color:#1d4ed8;font:650 9px/1.25 system-ui}.dsh-mobile-control__provider-badge.is-cpolar{background:#eaf8f2;color:#087454}.dsh-mobile-control__provider-description{color:var(--dsw-alias-label-secondary,#606873);font-size:10px;line-height:1.45}.dsh-mobile-control__provider-info{position:absolute;z-index:5;top:-13px;right:-8px}.dsh-mobile-control__provider-info-button{display:flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary,#475569);cursor:pointer;touch-action:manipulation}.dsh-mobile-control__provider-info-button:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f5f9);color:#2563eb}.dsh-mobile-control__provider-info-glyph{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:18px;height:18px;border:1.5px solid currentColor;border-radius:50%;font:700 12px/1 system-ui}.dsh-mobile-control__provider-info-popover{position:absolute;z-index:6;top:38px;right:4px;box-sizing:border-box;width:min(292px,calc(100vw - 72px));padding:10px 12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 10px 28px rgb(15 23 42 / 16%)}.dsh-mobile-control__provider-info-popover[hidden]{display:none}.dsh-mobile-control__provider-info-popover strong,.dsh-mobile-control__provider-info-popover span{display:block}.dsh-mobile-control__provider-info-popover strong{margin-bottom:3px;font-size:12px}.dsh-mobile-control__provider-info-popover span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}
5958
- .dsh-mobile-control__self-hosted{margin:8px 0 0;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__self-hosted-summary{display:flex;box-sizing:border-box;min-height:48px;align-items:center;justify-content:space-between;gap:10px;padding:8px 11px;cursor:pointer;list-style-position:inside}.dsh-mobile-control__self-hosted-summary>span:first-child{display:flex;min-width:0;flex-direction:column;gap:1px}.dsh-mobile-control__self-hosted-summary strong{font-size:11px}.dsh-mobile-control__self-hosted-summary span span{color:var(--dsw-alias-label-secondary,#606873);font-size:9px;line-height:1.35}.dsh-mobile-control__provider-badge.is-frp{background:#eef0f3;color:#475569}.dsh-mobile-control__self-hosted-body{padding:0 8px 8px}.dsh-mobile-control__provider.is-frp{width:100%;min-height:64px;background:var(--dsw-alias-bg-layer-2,#fff)}
5959
- .dsh-mobile-control__cpolar-setup{margin:0 0 12px;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__cpolar-setup[hidden],.dsh-mobile-control__cpolar-account[hidden],.dsh-mobile-control__details[hidden],.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions[hidden],.dsh-mobile-control__danger[hidden]{display:none}.dsh-mobile-control__component-status,.dsh-mobile-control__component-note{margin:0 0 10px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__primary{width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:600 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__cpolar-account{margin-top:10px}.dsh-mobile-control__link-row{display:flex;flex-wrap:wrap;gap:6px 12px;margin:0 0 10px}.dsh-mobile-control__text-link{color:#2563eb;font-size:11px;text-decoration:none}.dsh-mobile-control__text-link:hover{text-decoration:underline}.dsh-mobile-control__token-label{display:flex;flex-direction:column;gap:5px;margin:0 0 8px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__token{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2,#fff));color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__cpolar-connect{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;min-height:44px;padding:10px 14px;border-radius:12px;font:650 13px/1.2 system-ui;cursor:pointer;transition:background-color 160ms ease,border-color 160ms ease,opacity 160ms ease}.dsh-mobile-control__cpolar-connect:hover:not(:disabled){border-color:#1d4ed8;background:#1d4ed8}.dsh-mobile-control__cpolar-connect:active:not(:disabled){border-color:#1e40af;background:#1e40af}.dsh-mobile-control__cpolar-connect:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__details{margin:10px 0 0;border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb);padding-top:9px}.dsh-mobile-control__details>summary{min-height:30px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:30px;cursor:pointer}.dsh-mobile-control__details-body{display:flex;flex-wrap:wrap;align-items:center;gap:7px 12px;padding:4px 0}.dsh-mobile-control__details-body p{flex:1 0 100%;margin:0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__storage{display:block;flex:1 0 100%;max-width:100%;overflow:hidden;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#475569);font:10px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__danger{flex:1 0 100%;min-height:38px;margin-top:3px;padding:7px 10px;border:1px solid #dc2626;border-radius:9px;background:transparent;color:#dc2626;font:12px/1.3 system-ui;cursor:pointer}
5960
- .dsh-mobile-control__frp-setup{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__frp-setup[hidden]{display:none}.dsh-mobile-control__frp-step{padding:11px 0}.dsh-mobile-control__frp-step + .dsh-mobile-control__frp-step{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__frp-step>strong{display:block;margin-bottom:3px;font-size:12px;line-height:1.4}.dsh-mobile-control__frp-step>p{margin:0 0 9px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes{margin:0 0 9px;padding:0;list-style:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes li{position:relative;margin:0;padding-left:12px}.dsh-mobile-control__frp-changes li+li{margin-top:5px}.dsh-mobile-control__frp-changes li::before{content:"•";position:absolute;left:2px;color:var(--dsw-alias-label-tertiary,#98a1ad)}.dsh-mobile-control__frp-step>.dsh-mobile-control__frp-requirement{padding:8px 9px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-primary,#384152);font-size:11px}.dsh-mobile-control__frp-fields{display:grid;grid-template-columns:minmax(0,1fr) 96px;gap:8px}.dsh-mobile-control__field{display:flex;min-width:0;flex-direction:column;gap:5px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__field:nth-child(3),.dsh-mobile-control__field:nth-child(4){grid-column:1/-1}.dsh-mobile-control__field input{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__frp-action{box-sizing:border-box;width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__frp-action:disabled{cursor:not-allowed;opacity:.5}.dsh-mobile-control__remote-workspace{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f7f8fa)}.dsh-mobile-control__stage-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:9px}.dsh-mobile-control__stage-header .dsh-mobile-control__section-title{margin:0}.dsh-mobile-control__stage-meta{display:flex;min-width:0;align-items:center;justify-content:flex-end;gap:5px}.dsh-mobile-control__stage-value{max-width:115px;overflow:hidden;color:var(--dsw-alias-label-primary,#16181d);font:650 10px/1.3 system-ui;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__state-badge{flex:none;padding:3px 7px;border-radius:999px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#606873);font:650 9px/1.25 system-ui}.dsh-mobile-control__state-badge.is-ready{background:#e6f7f0;color:#087454}.dsh-mobile-control__state-badge.is-busy{background:#e8f0ff;color:#1d4ed8}.dsh-mobile-control__state-badge.is-attention{background:#fff4dc;color:#935100}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__status{box-sizing:border-box;margin:0 0 10px;padding:9px 10px;border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);font-size:11px;line-height:1.45}.dsh-mobile-control__provider-setup-body{margin:0 0 10px}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup{margin:0}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__section-title,.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__frp-setup>.dsh-mobile-control__section-title{display:none}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__details{margin:0;padding:9px 10px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__actions{margin-top:2px}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__qr{margin:10px 0 0}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__manage-row{margin-top:10px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}
5961
- .dsh-mobile-control__frp-overview{display:grid;grid-template-columns:32px minmax(0,1fr);align-items:center;gap:10px;margin:0 0 10px;padding:10px;border:1px solid #a9dfc9;border-radius:11px;background:#edf9f4}.dsh-mobile-control__frp-overview[hidden]{display:none}.dsh-mobile-control__frp-overview-mark{display:grid;width:32px;height:32px;place-items:center;border-radius:50%;background:#087454;color:#fff;font:700 15px/1 system-ui}.dsh-mobile-control__frp-overview-body{display:flex;min-width:0;flex-direction:column;gap:2px}.dsh-mobile-control__frp-overview-body strong{color:#075d46;font-size:12px;line-height:1.35}.dsh-mobile-control__frp-overview-body span{overflow:hidden;color:#357061;font:10px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__frp-group{margin:8px 0 0;overflow:hidden;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__frp-group>summary{box-sizing:border-box;min-height:44px;padding:12px 34px 10px 12px;color:var(--dsw-alias-label-primary,#16181d);font:650 12px/1.4 system-ui;cursor:pointer}.dsh-mobile-control__frp-group[open]>summary{border-bottom:1px solid var(--dsw-alias-border-subtle,#e1e5eb);background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step{padding:12px}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step>strong:first-child{display:none}.dsh-mobile-control__frp-group .dsh-mobile-control__frp-step{border-top:0}.dsh-mobile-control__frp-setup>.dsh-mobile-control__frp-step{margin-bottom:8px;padding:10px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}
5962
- .dsh-mobile-control__lan-setup{box-sizing:border-box;margin:0 0 12px;padding:14px;border:1px solid #6f96db;border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f5f8ff)}.dsh-mobile-control__lan-setup[hidden],.dsh-mobile-control__lan-setup-form[hidden]{display:none}.dsh-mobile-control__lan-setup>.dsh-mobile-control__section-title{margin-bottom:5px}.dsh-mobile-control__lan-setup>.dsh-mobile-control__intro{margin-bottom:12px}.dsh-mobile-control__lan-setup-form{display:grid;gap:9px}.dsh-mobile-control__lan-setup .dsh-mobile-control__field select{box-sizing:border-box;width:100%;min-height:44px;padding:9px 32px 9px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:14px/1.4 system-ui}.dsh-mobile-control__lan-setup .dsh-mobile-control__component-note{margin:0}.dsh-mobile-control__lan-setup-actions{display:grid;grid-template-columns:1fr 1.35fr;gap:8px}.dsh-mobile-control__lan-setup-actions button{box-sizing:border-box;min-width:0;min-height:44px;padding:9px 10px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__lan-setup-actions button:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__lan-setup>.dsh-mobile-control__status{margin:10px 0 0;line-height:1.5}.dsh-mobile-control__lan-setup>.dsh-mobile-control__status:empty{display:none}
5963
- .dsh-mobile-control__access{display:flex;align-items:baseline;gap:6px;min-width:0;margin:0 0 12px}.dsh-mobile-control__access[hidden]{display:none}.dsh-mobile-control__access-label{flex:none;color:var(--dsw-alias-label-secondary,#606873);white-space:nowrap}.dsh-mobile-control__access-label::after{content:":"}.dsh-mobile-control__access-link{min-width:0;overflow:hidden;color:#2563eb;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__access-link:hover{text-decoration:underline}.dsh-mobile-control__qr{display:flex;justify-content:center;margin:0 0 12px}.dsh-mobile-control__qr[hidden]{display:none}.dsh-mobile-control__qr img{border-radius:12px;background:#fff;padding:8px}
5964
- .dsh-mobile-control__status{margin:0 0 14px;overflow-wrap:anywhere;color:var(--dsw-alias-label-secondary,#606873)}.dsh-mobile-control__status::before{display:inline-block;width:8px;height:8px;margin-right:7px;border-radius:50%;background:#98a1ad;content:""}.dsh-mobile-control__status.is-running::before{background:#16a36a}.dsh-mobile-control__status.is-key{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;word-break:break-all}
7025
+ .dsh-mobile-control__panel{box-sizing:border-box;width:min(380px,calc(100vw - 32px));max-height:calc(100vh - 140px);overflow-y:auto;padding:16px;border:1px solid var(--dsw-alias-border-l2,#e1e5eb);border-radius:18px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 18px 50px rgb(15 23 42 / 18%)}
7026
+ .dsh-mobile-control__header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px}.dsh-mobile-control__panel h2{margin:0;font-size:17px;line-height:24px}.dsh-mobile-control__header-actions{display:flex;align-items:center;gap:2px}.dsh-mobile-control__update-plugin,.dsh-mobile-control__diagnostic-entry,.dsh-mobile-control__close{display:inline-flex;align-items:center;justify-content:center;min-width:44px;height:44px;padding:0;border:0;border-radius:10px;background:transparent;color:inherit;cursor:pointer}.dsh-mobile-control__update-plugin,.dsh-mobile-control__diagnostic-entry{padding:0 8px;color:var(--dsw-alias-label-primary-bluish,#2563eb);font:650 12px/1 system-ui;white-space:nowrap}.dsh-mobile-control__update-plugin[hidden]{display:none}.dsh-mobile-control__update-plugin:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__close{font-size:24px;line-height:1}.dsh-mobile-control__update-plugin:hover:not(:disabled),.dsh-mobile-control__diagnostic-entry:hover,.dsh-mobile-control__close:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}
7027
+ .dsh-mobile-control__release-notice{margin:-2px 0 10px;padding:8px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#eff6ff);color:var(--dsw-alias-label-primary,#1d4ed8);font-size:11px;line-height:1.45}.dsh-mobile-control__release-notice.is-error{color:var(--dsw-alias-label-primary,#dc2626);border-left:3px solid var(--dsw-alias-state-error-primary,#dc2626)}.dsh-mobile-control__release-notice[hidden]{display:none}.dsh-mobile-control__update-card{margin:0 0 10px;padding:11px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__update-card[hidden]{display:none}.dsh-mobile-control__update-card-title{margin:0 0 7px;color:var(--dsw-alias-label-primary,#16181d);font:650 13px/1.4 system-ui}.dsh-mobile-control__update-notes{display:block;box-sizing:border-box;max-height:150px;overflow-y:auto;margin:0 0 8px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#e1e5eb);border-radius:9px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#475569);white-space:pre-wrap;word-break:break-word;font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.dsh-mobile-control__update-notes[hidden]{display:none}.dsh-mobile-control__update-notice{margin:0 0 9px;padding:7px 9px;border-radius:8px;background:var(--dsw-alias-state-warn-tertiary,#fff4d6);color:var(--dsw-alias-state-warn-label,#92400e);font-size:11px;line-height:1.5}.dsh-mobile-control__update-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px}.dsh-mobile-control__update-actions .dsh-mobile-control__primary,.dsh-mobile-control__update-actions .dsh-mobile-control__secondary{min-height:36px;padding:0 12px}.dsh-mobile-control__app-download{display:flex;align-items:center;justify-content:space-between;box-sizing:border-box;min-height:44px;margin:0 0 10px;padding:8px 11px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f7f8fa);color:var(--dsw-alias-label-primary,#16181d);font:600 12px/1.3 system-ui;text-decoration:none}.dsh-mobile-control__app-download[hidden]{display:none}.dsh-mobile-control__app-download::after{color:var(--dsw-alias-label-primary-bluish,#2563eb);font-size:14px;content:"↗"}.dsh-mobile-control__app-download:hover{border-color:#9fb9e8;background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-bg-layer-2,#f5f8ff));color:var(--dsw-alias-label-primary,#1d4ed8)}
7028
+ .dsh-mobile-control__switcher{display:grid;grid-template-columns:repeat(2,1fr);gap:4px;margin:0 0 14px;padding:4px;border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f3f5f8)}.dsh-mobile-control__switcher[hidden]{display:none}.dsh-mobile-control__tab{min-height:36px;border:0;border-radius:9px;background:transparent;color:var(--dsw-alias-label-secondary,#606873);font:600 13px/1 system-ui;cursor:pointer}.dsh-mobile-control__tab.is-active{background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);box-shadow:0 1px 3px rgb(15 23 42 / 10%)}.dsh-mobile-control__view[hidden]{display:none}.dsh-mobile-control__intro{margin:0 0 12px;color:var(--dsw-alias-label-secondary,#606873);font-size:12px;line-height:1.55}.dsh-mobile-control__http-frame-warning{box-sizing:border-box;margin:-4px 0 12px;padding:8px 10px;border:1px solid #d6a83f;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#fff7df);color:var(--dsw-alias-label-primary,#694b00);font-size:11px;line-height:1.5;overflow-wrap:anywhere}.dsh-mobile-control__http-frame-warning[hidden]{display:none}.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions{display:grid;grid-template-columns:1fr 1fr;gap:8px}.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions button[hidden]{display:none}
7029
+ .dsh-mobile-control__provider-section{position:relative;margin:0 0 14px}.dsh-mobile-control__section-title{margin:0 0 8px;color:var(--dsw-alias-label-primary,#16181d);font:650 13px/1.4 system-ui}.dsh-mobile-control__provider-section>.dsh-mobile-control__section-title{padding-right:42px}.dsh-mobile-control__provider-choices{display:grid;grid-template-columns:1fr;gap:8px}.dsh-mobile-control__provider{display:flex;min-width:0;flex-direction:column;gap:6px;min-height:94px;padding:10px 11px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);text-align:left;cursor:pointer;touch-action:manipulation;transition:border-color 160ms ease,background-color 160ms ease,box-shadow 160ms ease}.dsh-mobile-control__provider:hover{border-color:#6f96db;background:var(--dsw-alias-interactive-bg-hover-solid,var(--dsw-alias-bg-layer-1,#f8fbff))}.dsh-mobile-control__provider.is-selected{border-color:var(--dsw-alias-label-primary-bluish,#2563eb);background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-bg-layer-1,#f5f8ff));box-shadow:0 0 0 1px var(--dsw-alias-label-primary-bluish,#2563eb) inset}.dsh-mobile-control__provider:disabled{cursor:wait;opacity:.62}.dsh-mobile-control__provider-top{display:flex;min-width:0;align-items:flex-start;justify-content:space-between;gap:5px}.dsh-mobile-control__provider-top strong{min-width:0;font-size:12px;line-height:1.3}.dsh-mobile-control__provider-badge{flex:none;padding:2px 5px;border-radius:999px;background:var(--dsw-alias-state-business-tertiary,#e8f0ff);color:var(--dsw-alias-label-primary,#1d4ed8);font:650 11px/1.25 system-ui}.dsh-mobile-control__provider-badge.is-cpolar{background:var(--dsw-alias-state-success-tertiary,#eaf8f2);color:var(--dsw-alias-label-primary,#087454)}.dsh-mobile-control__provider-description{color:var(--dsw-alias-label-secondary,#606873);font-size:12px;line-height:1.45}.dsh-mobile-control__provider-info{position:absolute;z-index:5;top:-13px;right:-8px}.dsh-mobile-control__provider-info-button{display:flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--dsw-alias-label-secondary,#475569);cursor:pointer;touch-action:manipulation}.dsh-mobile-control__provider-info-button:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f5f9);color:var(--dsw-alias-label-primary-bluish,#2563eb)}.dsh-mobile-control__provider-info-glyph{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:18px;height:18px;border:1.5px solid currentColor;border-radius:50%;font:700 12px/1 system-ui}.dsh-mobile-control__provider-info-popover{position:absolute;z-index:6;top:38px;right:4px;box-sizing:border-box;width:min(292px,calc(100vw - 72px));padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 10px 28px rgb(15 23 42 / 16%)}.dsh-mobile-control__provider-info-popover[hidden]{display:none}.dsh-mobile-control__provider-info-popover strong,.dsh-mobile-control__provider-info-popover span{display:block}.dsh-mobile-control__provider-info-popover strong{margin-bottom:3px;font-size:12px}.dsh-mobile-control__provider-info-popover span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}
7030
+ .dsh-mobile-control__self-hosted{margin:8px 0 0;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__self-hosted-summary{display:flex;box-sizing:border-box;min-height:48px;align-items:center;justify-content:space-between;gap:10px;padding:8px 11px;cursor:pointer;list-style-position:inside}.dsh-mobile-control__self-hosted-summary>span:first-child{display:flex;min-width:0;flex-direction:column;gap:1px}.dsh-mobile-control__self-hosted-summary strong{font-size:11px}.dsh-mobile-control__self-hosted-summary span span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.35}.dsh-mobile-control__provider-badge.is-frp{background:var(--dsw-alias-bg-layer-1,#eef0f3);color:var(--dsw-alias-label-secondary,#475569)}.dsh-mobile-control__self-hosted-body{padding:0 8px 8px}.dsh-mobile-control__provider.is-frp{width:100%;min-height:64px;background:var(--dsw-alias-bg-layer-2,#fff)}
7031
+ /* The cloudflared setup section deliberately reuses this card, so
7032
+ .dsh-mobile-control__cloudflared-setup is a DOM hook for targeting rather than a
7033
+ style rule of its own. */
7034
+ .dsh-mobile-control__cpolar-setup{margin:0 0 12px;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__cpolar-setup[hidden],.dsh-mobile-control__cpolar-account[hidden],.dsh-mobile-control__details[hidden],.dsh-mobile-control__view.is-remote .dsh-mobile-control__actions[hidden],.dsh-mobile-control__danger[hidden]{display:none}.dsh-mobile-control__component-status,.dsh-mobile-control__component-note{margin:0 0 10px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.55}.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__primary{width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:600 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__cpolar-account{margin-top:10px}.dsh-mobile-control__link-row{display:flex;flex-wrap:wrap;gap:6px 12px;margin:0 0 10px}.dsh-mobile-control__text-link{color:var(--dsw-alias-label-primary-bluish,#2563eb);font-size:11px;text-decoration:none}.dsh-mobile-control__text-link:hover{text-decoration:underline}.dsh-mobile-control__token-label{display:flex;flex-direction:column;gap:5px;margin:0 0 8px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__token{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2,#fff));color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__cpolar-connect{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;min-height:44px;padding:10px 14px;border-radius:12px;font:650 13px/1.2 system-ui;cursor:pointer;transition:background-color 160ms ease,border-color 160ms ease,opacity 160ms ease}.dsh-mobile-control__cpolar-connect:hover:not(:disabled){border-color:#1d4ed8;background:#1d4ed8}.dsh-mobile-control__cpolar-connect:active:not(:disabled){border-color:#1e40af;background:#1e40af}.dsh-mobile-control__cpolar-connect:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__details{margin:10px 0 0;border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb);padding-top:9px}.dsh-mobile-control__details>summary{min-height:36px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:30px;cursor:pointer}.dsh-mobile-control__details-body{display:flex;flex-wrap:wrap;align-items:center;gap:7px 12px;padding:4px 0}.dsh-mobile-control__details-body p{flex:1 0 100%;margin:0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__storage{display:block;flex:1 0 100%;max-width:100%;overflow:hidden;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#475569);font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__danger{flex:1 0 100%;min-height:44px;margin-top:3px;padding:7px 10px;border:1px solid var(--dsw-alias-state-error-primary,#dc2626);border-radius:9px;background:transparent;color:var(--dsw-alias-label-primary,#dc2626);font:12px/1.3 system-ui;cursor:pointer}
7035
+ .dsh-mobile-control__danger:hover:not(:disabled),.dsh-mobile-control__device-revoke:hover{background:var(--dsw-alias-interactive-bg-hover-danger,rgb(220 38 38 / 8%))}
7036
+ .dsh-mobile-control__frp-setup{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__frp-setup[hidden]{display:none}.dsh-mobile-control__frp-step{padding:11px 0}.dsh-mobile-control__frp-step + .dsh-mobile-control__frp-step{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__frp-step>strong{display:block;margin-bottom:3px;font-size:12px;line-height:1.4}.dsh-mobile-control__frp-step>p{margin:0 0 9px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes{margin:0 0 9px;padding:0;list-style:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__frp-changes li{position:relative;margin:0;padding-left:12px}.dsh-mobile-control__frp-changes li+li{margin-top:5px}.dsh-mobile-control__frp-changes li::before{content:"•";position:absolute;left:2px;color:var(--dsw-alias-label-tertiary,#98a1ad)}.dsh-mobile-control__frp-step>.dsh-mobile-control__frp-requirement{padding:8px 9px;border-radius:9px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-primary,#384152);font-size:11px}.dsh-mobile-control__frp-fields{display:grid;grid-template-columns:minmax(0,1fr) 96px;gap:8px}.dsh-mobile-control__field{display:flex;min-width:0;flex-direction:column;gap:5px;color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__field:nth-child(3),.dsh-mobile-control__field:nth-child(4){grid-column:1/-1}.dsh-mobile-control__field input{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__frp-action{box-sizing:border-box;width:100%;min-height:44px;padding:9px 12px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__frp-action:disabled{cursor:not-allowed;opacity:.5}.dsh-mobile-control__remote-workspace{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f7f8fa)}.dsh-mobile-control__stage-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:9px}.dsh-mobile-control__stage-header .dsh-mobile-control__section-title{margin:0}.dsh-mobile-control__stage-meta{display:flex;min-width:0;align-items:center;justify-content:flex-end;gap:5px}.dsh-mobile-control__stage-value{max-width:115px;overflow:hidden;color:var(--dsw-alias-label-primary,#16181d);font:650 11px/1.3 system-ui;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__state-badge{flex:none;padding:3px 7px;border-radius:999px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#606873);font:650 11px/1.25 system-ui}.dsh-mobile-control__state-badge.is-ready{background:var(--dsw-alias-state-success-tertiary,#e6f7f0);color:var(--dsw-alias-label-primary,#087454)}.dsh-mobile-control__state-badge.is-busy{background:var(--dsw-alias-state-business-tertiary,#e8f0ff);color:var(--dsw-alias-label-primary,#1d4ed8)}.dsh-mobile-control__state-badge.is-attention{background:var(--dsw-alias-state-warn-tertiary,#fff4dc);color:var(--dsw-alias-label-primary,#935100)}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__status{box-sizing:border-box;margin:0 0 10px;padding:9px 10px;border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);font-size:11px;line-height:1.45}.dsh-mobile-control__provider-setup-body{margin:0 0 10px}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup{margin:0}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__cpolar-setup>.dsh-mobile-control__section-title,.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__frp-setup>.dsh-mobile-control__section-title{display:none}.dsh-mobile-control__provider-setup-body>.dsh-mobile-control__details{margin:0;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__actions{margin-top:2px}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__qr{margin:10px 0 0}.dsh-mobile-control__remote-workspace>.dsh-mobile-control__manage-row{margin-top:10px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}
7037
+ .dsh-mobile-control__frp-overview{display:grid;grid-template-columns:32px minmax(0,1fr);align-items:center;gap:10px;margin:0 0 10px;padding:10px;border:1px solid var(--dsw-alias-state-success-primary,#a9dfc9);border-radius:11px;background:var(--dsw-alias-state-success-tertiary,#edf9f4)}.dsh-mobile-control__frp-overview[hidden]{display:none}.dsh-mobile-control__frp-overview-mark{display:grid;width:32px;height:32px;place-items:center;border-radius:50%;background:#087454;color:#fff;font:700 15px/1 system-ui}.dsh-mobile-control__frp-overview-body{display:flex;min-width:0;flex-direction:column;gap:2px}.dsh-mobile-control__frp-overview-body strong{color:var(--dsw-alias-label-primary,#075d46);font-size:12px;line-height:1.35}.dsh-mobile-control__frp-overview-body span{overflow:hidden;color:var(--dsw-alias-label-secondary,#357061);font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__frp-group{margin:8px 0 0;overflow:hidden;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__frp-group>summary{box-sizing:border-box;min-height:44px;padding:12px 34px 10px 12px;color:var(--dsw-alias-label-primary,#16181d);font:650 12px/1.4 system-ui;cursor:pointer}.dsh-mobile-control__frp-group[open]>summary{border-bottom:1px solid var(--dsw-alias-border-l2,#e1e5eb);background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step{padding:12px}.dsh-mobile-control__frp-group>.dsh-mobile-control__frp-step>strong:first-child{display:none}.dsh-mobile-control__frp-group .dsh-mobile-control__frp-step{border-top:0}.dsh-mobile-control__frp-setup>.dsh-mobile-control__frp-step{margin-bottom:8px;padding:10px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:11px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__tunnel{margin:0 0 10px}.dsh-mobile-control__tunnel[hidden]{display:none}
7038
+ .dsh-mobile-control__lan-setup{box-sizing:border-box;margin:0 0 12px;padding:14px;border:1px solid #6f96db;border-radius:15px;background:var(--dsw-alias-bg-layer-1,#f5f8ff)}.dsh-mobile-control__lan-setup[hidden],.dsh-mobile-control__lan-setup-form[hidden]{display:none}.dsh-mobile-control__lan-setup>.dsh-mobile-control__section-title{margin-bottom:5px}.dsh-mobile-control__lan-setup>.dsh-mobile-control__intro{margin-bottom:12px}.dsh-mobile-control__lan-setup-form{display:grid;gap:9px}.dsh-mobile-control__lan-setup .dsh-mobile-control__field select{box-sizing:border-box;width:100%;min-height:44px;padding:9px 32px 9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:14px/1.4 system-ui}.dsh-mobile-control__lan-setup .dsh-mobile-control__component-note{margin:0}.dsh-mobile-control__lan-setup-actions{display:grid;grid-template-columns:1fr 1.35fr;gap:8px}.dsh-mobile-control__lan-setup-actions button{box-sizing:border-box;min-width:0;min-height:44px;padding:9px 10px;border-radius:10px;font:650 12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__lan-setup-actions button:disabled{cursor:wait;opacity:.55}.dsh-mobile-control__lan-setup>.dsh-mobile-control__status{margin:10px 0 0;line-height:1.5}.dsh-mobile-control__lan-setup>.dsh-mobile-control__status:empty{display:none}
7039
+ .dsh-mobile-control__access{display:flex;align-items:baseline;gap:6px;min-width:0;margin:0 0 12px}.dsh-mobile-control__access[hidden]{display:none}.dsh-mobile-control__access-label{flex:none;color:var(--dsw-alias-label-secondary,#606873);white-space:nowrap}.dsh-mobile-control__access-label::after{content:":"}.dsh-mobile-control__access-link{min-width:0;overflow:hidden;color:var(--dsw-alias-label-primary-bluish,#2563eb);text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__access-link:hover{text-decoration:underline}.dsh-mobile-control__qr{display:flex;justify-content:center;margin:0 0 12px}.dsh-mobile-control__qr[hidden]{display:none}.dsh-mobile-control__qr img{border-radius:12px;background:#fff;padding:8px}
7040
+ .dsh-mobile-control__status{margin:0 0 14px;overflow-wrap:anywhere;color:var(--dsw-alias-label-secondary,#606873)}.dsh-mobile-control__status::before{display:inline-block;width:8px;height:8px;margin-right:7px;border-radius:50%;background:var(--dsw-alias-label-tertiary,#98a1ad);content:""}.dsh-mobile-control__status.is-running::before{background:var(--dsw-alias-state-success-primary,#16a36a)}.dsh-mobile-control__status.is-key{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;word-break:break-all}
5965
7041
  .dsh-mobile-control__guide{margin:0 0 14px;padding:12px;border:1px solid #6f96db;border-radius:12px;background:var(--dsw-alias-bg-layer-1,#eff6ff)}.dsh-mobile-control__guide[hidden]{display:none}.dsh-mobile-control__guide-title{margin:0;color:var(--dsw-alias-label-primary,#172554);font:650 13px/1.45 system-ui}.dsh-mobile-control__guide-summary,.dsh-mobile-control__guide-note{margin:4px 0 0;color:var(--dsw-alias-label-secondary,#475569);font-size:12px;line-height:1.5}.dsh-mobile-control__guide-steps{margin:8px 0 0;padding-left:20px;color:var(--dsw-alias-label-primary,#1e293b);font-size:12px;line-height:1.6}.dsh-mobile-control__guide-note{color:var(--dsw-alias-label-secondary,#64748b)}.dsh-mobile-control__guide-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}.dsh-mobile-control__guide-actions button{min-width:0;min-height:44px;padding:8px;border-radius:10px;font:12px/1.25 system-ui;cursor:pointer}.dsh-mobile-control__guide-actions button:disabled{cursor:not-allowed;opacity:.45}
5966
7042
  .dsh-mobile-control__extensions{margin:0 0 12px;color:var(--dsw-alias-label-secondary,#606873);font-size:12px}
5967
- .dsh-mobile-control__view.is-diagnostics{--dsh-diagnostic-ok:#087454;--dsh-diagnostic-warning:#a35b00;--dsh-diagnostic-error:#c62828;--dsh-diagnostic-info:#526071}.dsh-mobile-control__diagnostic-summary{box-sizing:border-box;margin:0;padding:13px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:16px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__diagnostic-summary-main{display:grid;grid-template-columns:36px minmax(0,1fr);align-items:center;gap:11px}.dsh-mobile-control__diagnostic-summary-icon{position:relative;display:block;width:36px;height:36px;border-radius:50%;background:#e8edf3;color:var(--dsh-diagnostic-info)}.dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-summary-icon::after{position:absolute;content:""}.dsh-mobile-control__diagnostic-summary-body{display:flex;min-width:0;flex-direction:column;gap:2px}.dsh-mobile-control__diagnostic-summary-body strong{font-size:13px;line-height:1.35}.dsh-mobile-control__diagnostic-summary-body span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__diagnostic-summary-meta{display:block;margin-top:11px;padding-top:9px;border-top:1px solid var(--dsw-alias-border-subtle,#dbe1e8);color:var(--dsw-alias-label-secondary,#606873);font-size:10px;line-height:1.45}.dsh-mobile-control__diagnostic-summary.is-ok .dsh-mobile-control__diagnostic-summary-icon{background:#e6f7f0;color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-summary.is-ok .dsh-mobile-control__diagnostic-summary-icon::before{top:10px;left:10px;width:13px;height:7px;border-bottom:2px solid currentColor;border-left:2px solid currentColor;transform:rotate(-45deg)}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon{background:#fff4dc;color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon{background:#fdecec;color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon::before{top:8px;left:17px;width:2px;height:13px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon::after,.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon::after{bottom:8px;left:17px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon{background:#e8f0ff;color:#2563eb}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon::before{inset:9px;border:2px solid rgb(37 99 235 / 24%);border-top-color:currentColor;border-radius:50%;animation:dsh-diagnostic-spin .8s linear infinite}
7043
+ .dsh-mobile-control__view.is-diagnostics{--dsh-diagnostic-ok:var(--dsw-alias-state-success-primary,#087454);--dsh-diagnostic-warning:var(--dsw-alias-state-warn-primary,#a35b00);--dsh-diagnostic-error:var(--dsw-alias-state-error-primary,#c62828);--dsh-diagnostic-info:var(--dsw-alias-label-secondary,#526071)}.dsh-mobile-control__diagnostic-summary{box-sizing:border-box;margin:0;padding:13px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:16px;background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__diagnostic-summary-main{display:grid;grid-template-columns:36px minmax(0,1fr);align-items:center;gap:11px}.dsh-mobile-control__diagnostic-summary-icon{position:relative;display:block;width:36px;height:36px;border-radius:50%;background:var(--dsw-alias-bg-layer-1,#e8edf3);color:var(--dsh-diagnostic-info)}.dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-summary-icon::after{position:absolute;content:""}.dsh-mobile-control__diagnostic-summary-body{display:flex;min-width:0;flex-direction:column;gap:2px}.dsh-mobile-control__diagnostic-summary-body strong{font-size:13px;line-height:1.35}.dsh-mobile-control__diagnostic-summary-body span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5}.dsh-mobile-control__diagnostic-summary-meta{display:block;margin-top:11px;padding-top:9px;border-top:1px solid var(--dsw-alias-border-l2,#dbe1e8);color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.45}.dsh-mobile-control__diagnostic-summary.is-ok .dsh-mobile-control__diagnostic-summary-icon{background:var(--dsw-alias-state-success-tertiary,#e6f7f0);color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-summary.is-ok .dsh-mobile-control__diagnostic-summary-icon::before{top:10px;left:10px;width:13px;height:7px;border-bottom:2px solid currentColor;border-left:2px solid currentColor;transform:rotate(-45deg)}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon{background:var(--dsw-alias-state-warn-tertiary,#fff4dc);color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon{background:var(--dsw-alias-state-error-secondary,#fdecec);color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon::before{top:8px;left:17px;width:2px;height:13px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-summary.is-attention .dsh-mobile-control__diagnostic-summary-icon::after,.dsh-mobile-control__diagnostic-summary.is-error .dsh-mobile-control__diagnostic-summary-icon::after{bottom:8px;left:17px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon{background:var(--dsw-alias-state-business-tertiary,#e8f0ff);color:var(--dsw-alias-label-primary-bluish,#2563eb)}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon::before{inset:9px;border:2px solid rgb(37 99 235 / 24%);border-top-color:currentColor;border-radius:50%;animation:dsh-diagnostic-spin .8s linear infinite}
5968
7044
  .dsh-mobile-control__diagnostic-summary.is-idle .dsh-mobile-control__diagnostic-summary-icon::before{top:8px;left:17px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-summary.is-idle .dsh-mobile-control__diagnostic-summary-icon::after{top:13px;left:17px;width:2px;height:11px;border-radius:2px;background:currentColor}
5969
- .dsh-mobile-control__diagnostic-toolbar{display:grid;grid-template-columns:1fr;gap:8px;margin-top:10px}.dsh-mobile-control__diagnostic-toolbar.has-report{grid-template-columns:1fr 1fr}.dsh-mobile-control__diagnostic-run,.dsh-mobile-control__diagnostic-copy{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border-radius:11px;font:650 12px/1.3 system-ui;cursor:pointer;touch-action:manipulation}.dsh-mobile-control__diagnostic-copy[hidden]{display:none}.dsh-mobile-control__diagnostic-run:disabled{cursor:wait;opacity:.58}.dsh-mobile-control__diagnostic-feedback{margin:8px 0 0;padding:8px 10px;border-radius:9px;background:#eff6ff;color:#1d4ed8;font-size:11px;line-height:1.45}.dsh-mobile-control__diagnostic-feedback[hidden]{display:none}
5970
- .dsh-mobile-control__diagnostic-checks{display:grid;gap:12px;margin-top:12px;animation:dsh-diagnostic-reveal 160ms ease-out both}.dsh-mobile-control__diagnostic-checks[hidden]{display:none}.dsh-mobile-control__diagnostic-group{overflow:hidden;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__diagnostic-group-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 11px;border-bottom:1px solid var(--dsw-alias-border-subtle,#e1e5eb);background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__diagnostic-group-header h3{margin:0;font:650 11px/1.4 system-ui}.dsh-mobile-control__diagnostic-group-header span{color:var(--dsw-alias-label-secondary,#606873);font-size:10px}.dsh-mobile-control__diagnostic-list{display:flex;flex-direction:column}.dsh-mobile-control__diagnostic-check{display:grid;grid-template-columns:26px minmax(0,1fr);gap:9px;padding:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__diagnostic-check + .dsh-mobile-control__diagnostic-check{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__diagnostic-marker{position:relative;width:26px;height:26px;border-radius:50%;background:#edf1f5;color:var(--dsh-diagnostic-info)}.dsh-mobile-control__diagnostic-marker::before,.dsh-mobile-control__diagnostic-marker::after{position:absolute;content:""}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-marker{background:#e6f7f0;color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-marker::before{top:7px;left:7px;width:9px;height:5px;border-bottom:1.8px solid currentColor;border-left:1.8px solid currentColor;transform:rotate(-45deg)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker{background:#fff4dc;color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker{background:#fdecec;color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker::before,.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker::before{top:6px;left:12px;width:2px;height:9px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker::after,.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker::after{bottom:6px;left:12px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-check.is-info .dsh-mobile-control__diagnostic-marker::before{top:6px;left:12px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-check.is-info .dsh-mobile-control__diagnostic-marker::after{top:10px;left:12px;width:2px;height:9px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-check-body{min-width:0}.dsh-mobile-control__diagnostic-check-header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.dsh-mobile-control__diagnostic-check-header strong{min-width:0;font-size:12px;line-height:1.4}.dsh-mobile-control__diagnostic-badge{flex:none;padding:2px 6px;border-radius:999px;background:#edf1f5;color:var(--dsh-diagnostic-info);font:650 10px/1.3 system-ui}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-badge{background:#e6f7f0;color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-badge{background:#fff4dc;color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-badge{background:#fdecec;color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-check p{margin:4px 0 0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5;overflow-wrap:anywhere}.dsh-mobile-control__diagnostic-check .dsh-mobile-control__diagnostic-action{margin-top:7px;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f8fafc);color:var(--dsw-alias-label-primary,#16181d)}.dsh-mobile-control__diagnostic-action span{display:inline-block;margin-right:6px;color:#2563eb;font-weight:700}.dsh-mobile-control__diagnostic-details{margin-top:12px}.dsh-mobile-control__diagnostic-details[hidden]{display:none}.dsh-mobile-control__diagnostic-details>summary{box-sizing:border-box;min-height:44px;line-height:44px}.dsh-mobile-control__diagnostic-report{box-sizing:border-box;max-height:220px;margin:4px 0 0;overflow:auto;padding:10px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#606873);font:10px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}
7045
+ .dsh-mobile-control__diagnostic-toolbar{display:grid;grid-template-columns:1fr;gap:8px;margin-top:10px}.dsh-mobile-control__diagnostic-toolbar.has-report{grid-template-columns:1fr 1fr}.dsh-mobile-control__diagnostic-run,.dsh-mobile-control__diagnostic-copy{box-sizing:border-box;width:100%;min-height:44px;padding:9px 10px;border-radius:11px;font:650 12px/1.3 system-ui;cursor:pointer;touch-action:manipulation}.dsh-mobile-control__diagnostic-copy[hidden]{display:none}.dsh-mobile-control__diagnostic-run:disabled{cursor:wait;opacity:.58}.dsh-mobile-control__diagnostic-feedback{margin:8px 0 0;padding:8px 10px;border-radius:9px;background:var(--dsw-alias-state-business-tertiary,#eff6ff);color:var(--dsw-alias-label-primary,#1d4ed8);font-size:11px;line-height:1.45}.dsh-mobile-control__diagnostic-feedback[hidden]{display:none}
7046
+ .dsh-mobile-control__diagnostic-checks{display:grid;gap:12px;margin-top:12px;animation:dsh-diagnostic-reveal 160ms ease-out both}.dsh-mobile-control__diagnostic-checks[hidden]{display:none}.dsh-mobile-control__diagnostic-group{overflow:hidden;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__diagnostic-group-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 11px;border-bottom:1px solid var(--dsw-alias-border-l2,#e1e5eb);background:var(--dsw-alias-bg-layer-1,#f8fafc)}.dsh-mobile-control__diagnostic-group-header h3{margin:0;font:650 11px/1.4 system-ui}.dsh-mobile-control__diagnostic-group-header span{color:var(--dsw-alias-label-secondary,#606873);font-size:11px}.dsh-mobile-control__diagnostic-list{display:flex;flex-direction:column}.dsh-mobile-control__diagnostic-check{display:grid;grid-template-columns:26px minmax(0,1fr);gap:9px;padding:11px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__diagnostic-check + .dsh-mobile-control__diagnostic-check{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__diagnostic-marker{position:relative;width:26px;height:26px;border-radius:50%;background:var(--dsw-alias-bg-layer-1,#edf1f5);color:var(--dsh-diagnostic-info)}.dsh-mobile-control__diagnostic-marker::before,.dsh-mobile-control__diagnostic-marker::after{position:absolute;content:""}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-marker{background:var(--dsw-alias-state-success-tertiary,#e6f7f0);color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-marker::before{top:7px;left:7px;width:9px;height:5px;border-bottom:1.8px solid currentColor;border-left:1.8px solid currentColor;transform:rotate(-45deg)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker{background:var(--dsw-alias-state-warn-tertiary,#fff4dc);color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker{background:var(--dsw-alias-state-error-secondary,#fdecec);color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker::before,.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker::before{top:6px;left:12px;width:2px;height:9px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-marker::after,.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-marker::after{bottom:6px;left:12px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-check.is-info .dsh-mobile-control__diagnostic-marker::before{top:6px;left:12px;width:2px;height:2px;border-radius:50%;background:currentColor}.dsh-mobile-control__diagnostic-check.is-info .dsh-mobile-control__diagnostic-marker::after{top:10px;left:12px;width:2px;height:9px;border-radius:2px;background:currentColor}.dsh-mobile-control__diagnostic-check-body{min-width:0}.dsh-mobile-control__diagnostic-check-header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.dsh-mobile-control__diagnostic-check-header strong{min-width:0;font-size:12px;line-height:1.4}.dsh-mobile-control__diagnostic-badge{flex:none;padding:2px 6px;border-radius:999px;background:var(--dsw-alias-bg-layer-1,#edf1f5);color:var(--dsh-diagnostic-info);font:650 11px/1.3 system-ui}.dsh-mobile-control__diagnostic-check.is-ok .dsh-mobile-control__diagnostic-badge{background:var(--dsw-alias-state-success-tertiary,#e6f7f0);color:var(--dsh-diagnostic-ok)}.dsh-mobile-control__diagnostic-check.is-warning .dsh-mobile-control__diagnostic-badge{background:var(--dsw-alias-state-warn-tertiary,#fff4dc);color:var(--dsh-diagnostic-warning)}.dsh-mobile-control__diagnostic-check.is-error .dsh-mobile-control__diagnostic-badge{background:var(--dsw-alias-state-error-secondary,#fdecec);color:var(--dsh-diagnostic-error)}.dsh-mobile-control__diagnostic-check p{margin:4px 0 0;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;line-height:1.5;overflow-wrap:anywhere}.dsh-mobile-control__diagnostic-check .dsh-mobile-control__diagnostic-action{margin-top:7px;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-bg-layer-1,#f8fafc);color:var(--dsw-alias-label-primary,#16181d)}.dsh-mobile-control__diagnostic-action span{display:inline-block;margin-right:6px;color:var(--dsw-alias-label-primary-bluish,#2563eb);font-weight:700}.dsh-mobile-control__diagnostic-details{margin-top:12px}.dsh-mobile-control__diagnostic-details[hidden]{display:none}.dsh-mobile-control__diagnostic-details>summary{box-sizing:border-box;min-height:44px;line-height:44px}.dsh-mobile-control__diagnostic-report{box-sizing:border-box;max-height:220px;margin:4px 0 0;overflow:auto;padding:10px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:10px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-secondary,#606873);font:11px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}
5971
7047
  .dsh-mobile-control__diagnostic-checks{transition:opacity 150ms ease}.dsh-mobile-control__diagnostic-checks.is-refreshing{opacity:.52}
5972
7048
  @keyframes dsh-diagnostic-spin{to{transform:rotate(360deg)}}@keyframes dsh-diagnostic-reveal{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
5973
- .dsh-mobile-control__actions{display:flex;flex-wrap:nowrap;gap:6px}.dsh-mobile-control__actions[hidden],.dsh-mobile-control__manage-row[hidden]{display:none}.dsh-mobile-control__actions button{flex:1 1 0;min-width:0;min-height:40px;padding:8px 4px;border-radius:10px;font:12px/1.2 system-ui;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dsh-mobile-control__secondary{border:1px solid var(--dsw-alias-border-normal,#cfd5dd);background:transparent;color:inherit}.dsh-mobile-control__primary{border:1px solid #2563eb;background:#2563eb;color:#fff}.dsh-mobile-control__actions button:disabled{cursor:not-allowed;opacity:.45}
7049
+ .dsh-mobile-control__actions{display:flex;flex-wrap:nowrap;gap:6px}.dsh-mobile-control__actions[hidden],.dsh-mobile-control__manage-row[hidden]{display:none}.dsh-mobile-control__actions button{flex:1 1 0;min-width:0;min-height:44px;padding:8px 4px;border-radius:10px;font:12px/1.2 system-ui;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dsh-mobile-control__secondary{border:1px solid var(--dsw-alias-border-l3,#cfd5dd);background:transparent;color:inherit}.dsh-mobile-control__primary{border:1px solid #2563eb;background:#2563eb;color:#fff}.dsh-mobile-control__actions button:disabled{cursor:not-allowed;opacity:.45}
5974
7050
  .dsh-mobile-control button:focus-visible,.dsh-mobile-control a:focus-visible,.dsh-mobile-control input:focus-visible,.dsh-mobile-control select:focus-visible,.dsh-mobile-control summary:focus-visible{outline:3px solid rgb(37 99 235 / 28%);outline-offset:2px}
7051
+ /* 42px matches the DSH sidebar footer row; it is not one of the panel target tiers. */
5975
7052
  .dsh-mobile-control__trigger{box-sizing:border-box;flex:1 1 auto;display:flex;align-items:center;gap:8px;width:100%;height:42px;margin:4px 0;padding:0 10px 0 8px;border:0;border-radius:12px;background:transparent;color:var(--dsw-alias-label-primary,#16181d);font-family:inherit;font-size:14px;line-height:22px;cursor:pointer;overflow:hidden}.dsh-mobile-control__trigger:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}.dsh-mobile-control__trigger:active,.dsh-mobile-control__trigger[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-active,#e8ebf0)}.dsh-mobile-control__trigger:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,currentColor);outline-offset:2px}.dsh-mobile-control__trigger.is-rail{flex:0 0 auto;width:36px;height:36px;margin:8px 0 10px;padding:0;justify-content:center;gap:0;border-radius:50%}.dsh-mobile-control__trigger-icon{display:block;flex:none}.dsh-mobile-control__trigger-label{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
5976
- .dsh-mobile-control__manage-row{display:flex;justify-content:space-between;gap:8px;margin-top:10px}.dsh-mobile-control__manage{flex:1 1 0;min-width:0;min-height:34px;padding:6px 8px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:transparent;color:inherit;font:12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__devices{margin-top:10px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:10px;padding:8px;max-height:220px;overflow-y:auto}.dsh-mobile-control__device-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px;margin:0}.dsh-mobile-control__device{display:flex;align-items:center;gap:8px;padding:6px 2px}.dsh-mobile-control__device + .dsh-mobile-control__device{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__device-label{flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.dsh-mobile-control__device-meta{flex:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;white-space:nowrap}.dsh-mobile-control__device-revoke{flex:none;min-height:28px;padding:4px 8px;border:1px solid #dc2626;border-radius:8px;background:transparent;color:#dc2626;font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__ws-paths{margin:10px 0 0;padding:12px;border:1px solid var(--dsw-alias-border-subtle,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__ws-paths-list{margin:0 0 8px;padding:0;list-style:none}.dsh-mobile-control__ws-paths-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px}.dsh-mobile-control__ws-paths-item{display:flex;align-items:center;gap:8px;padding:6px 0}.dsh-mobile-control__ws-paths-item + .dsh-mobile-control__ws-paths-item{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__ws-paths-item code{flex:1 1 0;min-width:0;overflow:hidden;font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__ws-paths-remove{flex:none;min-height:28px;padding:4px 8px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:8px;background:transparent;color:inherit;font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__trigger,.dsh-mobile-control__diagnostic-entry{position:relative}.dsh-mobile-control__ws-dot{position:absolute;top:2px;right:2px;z-index:1;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:#dc2626;color:#fff;font:650 10px/16px system-ui;text-align:center;cursor:pointer}.dsh-mobile-control__ws-dot::after{content:'';position:absolute;inset:-5px}.dsh-mobile-control__ws-sr-status{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.dsh-mobile-control__ws-paths-row{display:flex;gap:8px}.dsh-mobile-control__ws-paths-row .dsh-mobile-control__primary{width:auto;flex:none;padding:9px 14px;border-radius:10px}.dsh-mobile-control__ws-paths-group{padding:6px 0}.dsh-mobile-control__ws-paths-group + .dsh-mobile-control__ws-paths-group{border-top:1px solid var(--dsw-alias-border-subtle,#e1e5eb)}.dsh-mobile-control__ws-paths-group-head{display:flex;align-items:center;gap:8px}.dsh-mobile-control__ws-count{flex:none;padding:1px 7px;border-radius:999px;background:#e8f0ff;color:#1d4ed8;font:650 10px/1.6 system-ui}.dsh-mobile-control__ws-paths-item.is-child{padding-left:12px}.dsh-mobile-control__ws-paths-group-head .dsh-mobile-control__primary,.dsh-mobile-control__ws-paths-item .dsh-mobile-control__primary{width:auto;flex:none;min-height:34px;padding:6px 12px;border-radius:10px}.dsh-mobile-control__ws-paths-row input{flex:1 1 0;min-width:0;box-sizing:border-box;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-normal,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__ws-paths-row button{flex:none}.dsh-mobile-control__ws-paths-detected-title{margin:0 0 2px;font:600 12px/1.4 system-ui}.dsh-mobile-control__ws-paths-advanced{margin-top:8px}.dsh-mobile-control__ws-paths-advanced summary{cursor:pointer;color:var(--dsw-alias-label-secondary,#606873);font:12px/1.4 system-ui;user-select:none}.dsh-mobile-control__ws-paths-advanced summary:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,currentColor);outline-offset:2px;border-radius:4px}.dsh-mobile-control__ws-paths-advanced[open] summary{margin-bottom:6px}.dsh-mobile-control__ws-paths-advanced .dsh-mobile-control__status{margin:6px 0 0}
5977
- @media (max-width:359px){.dsh-mobile-control__provider-choices{grid-template-columns:1fr}.dsh-mobile-control__provider{min-height:68px}}@media (prefers-reduced-motion:reduce){.dsh-mobile-control__provider,.dsh-mobile-control__cpolar-connect{transition:none}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-checks{animation:none}}
7053
+ .dsh-mobile-control__manage-row{display:flex;justify-content:space-between;gap:8px;margin-top:10px}.dsh-mobile-control__manage{flex:1 1 0;min-width:0;min-height:36px;padding:6px 8px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:transparent;color:inherit;font:12px/1.3 system-ui;cursor:pointer}.dsh-mobile-control__devices{margin-top:10px;border:1px solid var(--dsw-alias-border-l2,#e1e5eb);border-radius:10px;padding:8px;max-height:220px;overflow-y:auto}.dsh-mobile-control__device-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px;margin:0}.dsh-mobile-control__device{display:flex;align-items:center;gap:8px;padding:6px 2px}.dsh-mobile-control__device + .dsh-mobile-control__device{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__device-label{flex:1 1 0;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.dsh-mobile-control__device-meta{flex:none;color:var(--dsw-alias-label-secondary,#606873);font-size:11px;white-space:nowrap}.dsh-mobile-control__device-revoke{flex:none;min-height:36px;padding:4px 8px;border:1px solid var(--dsw-alias-state-error-primary,#dc2626);border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary,#dc2626);font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__ws-paths{margin:10px 0 0;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__ws-paths-list{margin:0 0 8px;padding:0;list-style:none}.dsh-mobile-control__ws-paths-empty{color:var(--dsw-alias-label-secondary,#606873);font-size:12px}.dsh-mobile-control__ws-paths-item{display:flex;align-items:center;gap:8px;padding:6px 0}.dsh-mobile-control__ws-paths-item + .dsh-mobile-control__ws-paths-item{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__ws-paths-item code{flex:1 1 0;min-width:0;overflow:hidden;font:11px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__ws-paths-remove{flex:none;min-height:36px;padding:4px 8px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:8px;background:transparent;color:inherit;font:12px/1.2 system-ui;cursor:pointer}.dsh-mobile-control__trigger,.dsh-mobile-control__diagnostic-entry{position:relative}.dsh-mobile-control__ws-dot{position:absolute;top:2px;right:2px;z-index:1;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--dsw-alias-state-error-primary,#dc2626);color:#fff;font:650 11px/16px system-ui;text-align:center;cursor:pointer}.dsh-mobile-control__ws-dot::after{content:'';position:absolute;inset:-5px}.dsh-mobile-control__ws-sr-status{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.dsh-mobile-control__ws-paths-row{display:flex;gap:8px}.dsh-mobile-control__ws-paths-row .dsh-mobile-control__primary{width:auto;flex:none;padding:9px 14px;border-radius:10px}.dsh-mobile-control__ws-paths-group{padding:6px 0}.dsh-mobile-control__ws-paths-group + .dsh-mobile-control__ws-paths-group{border-top:1px solid var(--dsw-alias-border-l2,#e1e5eb)}.dsh-mobile-control__ws-paths-group-head{display:flex;align-items:center;gap:8px}.dsh-mobile-control__ws-count{flex:none;padding:1px 7px;border-radius:999px;background:var(--dsw-alias-state-business-tertiary,#e8f0ff);color:var(--dsw-alias-label-primary,#1d4ed8);font:650 11px/1.6 system-ui}.dsh-mobile-control__ws-paths-item.is-child{padding-left:12px}.dsh-mobile-control__ws-paths-group-head .dsh-mobile-control__primary,.dsh-mobile-control__ws-paths-item .dsh-mobile-control__primary{width:auto;flex:none;min-height:36px;padding:6px 12px;border-radius:10px}.dsh-mobile-control__ws-paths-row input{flex:1 1 0;min-width:0;box-sizing:border-box;min-height:44px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#16181d);font:16px/1.4 system-ui}.dsh-mobile-control__ws-paths-row button{flex:none}.dsh-mobile-control__ws-paths-detected-title{margin:0 0 2px;font:600 12px/1.4 system-ui}.dsh-mobile-control__ws-paths-advanced{margin-top:8px}.dsh-mobile-control__ws-paths-advanced summary{cursor:pointer;color:var(--dsw-alias-label-secondary,#606873);font:12px/1.4 system-ui;user-select:none}.dsh-mobile-control__ws-paths-advanced summary:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,currentColor);outline-offset:2px;border-radius:4px}.dsh-mobile-control__ws-paths-advanced[open] summary{margin-bottom:6px}.dsh-mobile-control__ws-paths-advanced .dsh-mobile-control__status{margin:6px 0 0}
7054
+ @media (max-width:359px){.dsh-mobile-control__provider{min-height:68px}}@media (prefers-reduced-motion:reduce){.dsh-mobile-control__provider,.dsh-mobile-control__cpolar-connect{transition:none}.dsh-mobile-control__diagnostic-summary.is-running .dsh-mobile-control__diagnostic-summary-icon::before,.dsh-mobile-control__diagnostic-checks{animation:none}}
7055
+
7056
+ .dsh-mobile-control__origin-setup{margin:0;padding:12px;border:1px solid var(--dsw-alias-border-l2,#dbe1e8);border-radius:13px;background:var(--dsw-alias-bg-layer-2,#fff)}.dsh-mobile-control__origin-setup[hidden],.dsh-mobile-control__origin-backend[hidden],.dsh-mobile-control__origin-feedback[hidden]{display:none}.dsh-mobile-control__origin-fields{display:grid;grid-template-columns:minmax(0,1fr) 110px;gap:12px}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field{font-size:12px}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field:nth-child(1),.dsh-mobile-control__origin-fields>.dsh-mobile-control__field:nth-child(4){grid-column:1/-1}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field:nth-child(2){grid-column:1}.dsh-mobile-control__origin-fields>.dsh-mobile-control__field:nth-child(3){grid-column:2}.dsh-mobile-control__origin-warning{margin:12px 0;padding:10px 12px;border-left:3px solid var(--dsw-alias-state-warn-primary,#a56810);border-radius:4px;background:var(--dsw-alias-bg-layer-1,#f3f5f8);color:var(--dsw-alias-label-primary,#384152);font-size:12px;line-height:1.6}.dsh-mobile-control__origin-feedback{font-size:12px;line-height:1.6;overflow-wrap:anywhere}.dsh-mobile-control__origin-feedback.is-error{color:var(--dsw-alias-state-error-primary,#bc3030)}.dsh-mobile-control__origin-fields input[aria-invalid=true]{border-color:var(--dsw-alias-state-error-primary,#bc3030)}.dsh-mobile-control__origin-backend{display:flex;min-width:0;flex-direction:column;align-items:stretch;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#dbe1e8)}.dsh-mobile-control__origin-backend button{align-self:flex-start;min-height:44px}.dsh-mobile-control__self-hosted-body .is-origin{margin-top:8px}
5978
7057
  `;
5979
7058
  /** Mount the desktop control or mobile feature enhancements. */
5980
7059
  function apply(ctx) {
@@ -5983,12 +7062,12 @@ window.__ModuleLoader__.load({
5983
7062
  return trustAuthenticatedGatewayConnection(ctx.get("connection"));
5984
7063
  }, "dsh-mobile: authenticated gateway client trust");
5985
7064
  ctx.effect(() => {
5986
- const loopback = isLoopbackHost(location.hostname) && !new URLSearchParams(location.search).has("dsh-mobile-preview");
7065
+ const desktopAdmin = isDesktopAdminSurface(location.hostname, location.search, window.__DSH_MOBILE_FRONTEND__);
5987
7066
  const style = element("style");
5988
7067
  style.dataset.plugin = "dsh-mobile";
5989
- style.textContent = loopback ? CONTROL_STYLES : NATIVE_MOBILE_STYLES;
7068
+ style.textContent = desktopAdmin ? CONTROL_STYLES : NATIVE_MOBILE_STYLES;
5990
7069
  document.head.append(style);
5991
- if (!loopback) {
7070
+ if (!desktopAdmin) {
5992
7071
  const removeSettingsAction = ctx.slots.inject("settings.general.item", () => ctx.slots.register({
5993
7072
  name: "settings.general.item",
5994
7073
  id: "dsh-mobile-switch-computer",
@@ -6066,6 +7145,7 @@ window.__ModuleLoader__.load({
6066
7145
  exports.diagnosticEntriesForRender = diagnosticEntriesForRender;
6067
7146
  exports.diagnosticOverallForChecks = diagnosticOverallForChecks;
6068
7147
  exports.diagnosticServerCopy = diagnosticServerCopy;
7148
+ exports.extensionActionRequestInit = extensionActionRequestInit;
6069
7149
  exports.extensionAssetUrl = extensionAssetUrl;
6070
7150
  exports.extensionGenerationHeaders = extensionGenerationHeaders;
6071
7151
  exports.extensionRouteUrl = extensionRouteUrl;
@@ -6076,6 +7156,7 @@ window.__ModuleLoader__.load({
6076
7156
  exports.normalizeDiagnosticOverall = normalizeDiagnosticOverall;
6077
7157
  exports.normalizeDiagnosticStatus = normalizeDiagnosticStatus;
6078
7158
  exports.parseMobileExtensionManifest = parseMobileExtensionManifest;
7159
+ exports.parseMobileRemoteProvider = parseMobileRemoteProvider;
6079
7160
  exports.publishAuthoritativeExtensionIds = publishAuthoritativeExtensionIds;
6080
7161
  exports.reconcileRemovedExtensions = reconcileRemovedExtensions;
6081
7162
  exports.registerUniqueDisposable = registerUniqueDisposable;