dsh-mobile 0.6.12 → 0.6.14

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
@@ -1219,10 +1219,10 @@ window.__ModuleLoader__.load({
1219
1219
  originDescription: "Use managed Caddy or your existing HTTPS reverse proxy with DSH Mobile’s private, authenticated backend.",
1220
1220
  prepareOrigin: "Connect your HTTPS reverse proxy",
1221
1221
  originPublicOrigin: "Public HTTPS address",
1222
- originListenHost: "Private listen IPv4",
1222
+ originListenHost: "Private listen IP (IPv4 / IPv6)",
1223
1223
  originListenPort: "HTTP backend port",
1224
1224
  originAllowedCidrs: "Allowed proxy source CIDRs",
1225
- 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.",
1225
+ originHelp: "Same computer: use 127.0.0.1 or ::1. Across a private network, use this computer’s private IPv4 or IPv6 ULA and allow the proxy’s direct source with /32 or /128 in the same address family. This is the NGINX-to-DSH connection, not the phone’s public IP. Separate CIDRs with spaces or commas.",
1226
1226
  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.",
1227
1227
  originSaveStart: "Save and start backend",
1228
1228
  originBackendSaved: "Saved HTTP backend (not running)",
@@ -1257,15 +1257,15 @@ window.__ModuleLoader__.load({
1257
1257
  reconnectingOrigin: "Restarting the private HTTP backend…",
1258
1258
  resetOriginConfirm: "Stop this backend and reset remote pairing? All paired remote devices must pair again. Reverse-proxy settings and LAN devices are kept.",
1259
1259
  originPublicOriginInvalid: "Enter a public HTTPS origin only, such as https://phone.example.com:8815; no path, query, credentials, or private address.",
1260
- originListenHostInvalid: "Use an explicit loopback or private IPv4 address on this computer, not 0.0.0.0 or a public address.",
1260
+ originListenHostInvalid: "Use this computer’s explicit loopback/private IPv4, ::1, or IPv6 ULA (fc00::/7). Wildcard and public addresses are not allowed.",
1261
1261
  originListenPortInvalid: "Use a whole-number port from 1024 to 65535; lower ports need privileges the plugin does not have.",
1262
1262
  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.",
1263
- 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.",
1263
+ originAllowedCidrsInvalid: "Allow 1–16 canonical loopback/private CIDRs in the listener’s address family. For a private-network listener, include a non-loopback proxy source, preferably IPv4/32 or IPv6/128. IPv6 supports ::1/128 and ULA (fc00::/7); public ranges are not accepted.",
1264
1264
  originSettingsInvalid: "The reverse-proxy settings are invalid. Check the four fields and save again.",
1265
1265
  originConfigMissing: "Save the reverse-proxy settings before starting the backend.",
1266
1266
  originConfigInvalid: "The saved settings are invalid. Correct and save them again, or clear only these settings.",
1267
1267
  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.",
1268
- originAddressUnavailable: "The listen address is not available on this computer. Choose its current private IPv4.",
1268
+ originAddressUnavailable: "The listen address is not available on this computer. Choose its current private IPv4, ::1, or IPv6 ULA.",
1269
1269
  originStartFailed: "The private backend could not start. Check its address, port, permissions, and local logs.",
1270
1270
  originConfigTargetInvalid: "The saved proxy settings file is not a regular file. Remove it and save the settings again.",
1271
1271
  originControllerUnavailable: "The reverse-proxy provider is not available in this session. Restart DSH and try again."
@@ -1278,10 +1278,10 @@ window.__ModuleLoader__.load({
1278
1278
  originDescription: "Usa Caddy gestito o il tuo reverse proxy HTTPS con il backend privato autenticato di DSH Mobile.",
1279
1279
  prepareOrigin: "Collega il tuo reverse proxy HTTPS",
1280
1280
  originPublicOrigin: "Indirizzo HTTPS pubblico",
1281
- originListenHost: "IPv4 privato di ascolto",
1281
+ originListenHost: "IP privato di ascolto (IPv4 / IPv6)",
1282
1282
  originListenPort: "Porta backend HTTP",
1283
1283
  originAllowedCidrs: "CIDR sorgente del proxy consentiti",
1284
- 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.",
1284
+ originHelp: "Stesso computer: usa 127.0.0.1 o ::1. Su una rete privata usa l’IPv4 privato o l’IPv6 ULA di questo computer e consenti la sorgente diretta del proxy con /32 o /128 nella stessa famiglia. Si tratta della connessione NGINX–DSH, non dell’IP pubblico del telefono. Separa i CIDR con spazi o virgole.",
1285
1285
  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.",
1286
1286
  originSaveStart: "Salva e avvia backend",
1287
1287
  originBackendSaved: "Backend HTTP salvato (non attivo)",
@@ -1316,15 +1316,15 @@ window.__ModuleLoader__.load({
1316
1316
  reconnectingOrigin: "Riavvio del backend HTTP privato…",
1317
1317
  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.",
1318
1318
  originPublicOriginInvalid: "Inserisci solo un’origine HTTPS pubblica, ad esempio https://phone.example.com:8815, senza percorso, query, credenziali o indirizzo privato.",
1319
- originListenHostInvalid: "Usa un IPv4 loopback o privato esplicito di questo computer, non 0.0.0.0 né un indirizzo pubblico.",
1319
+ originListenHostInvalid: "Usa l’IPv4 loopback/privato, ::1 o un IPv6 ULA (fc00::/7) esplicito di questo computer. Indirizzi jolly e pubblici non sono ammessi.",
1320
1320
  originListenPortInvalid: "Usa una porta intera da 1024 a 65535; le porte più basse richiedono privilegi che il plugin non ha.",
1321
1321
  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.",
1322
- 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.",
1322
+ originAllowedCidrsInvalid: "Consenti 1–16 CIDR loopback/privati canonici della stessa famiglia dell’ascolto. Su rete privata includi una sorgente proxy non loopback, preferibilmente IPv4/32 o IPv6/128. IPv6 supporta ::1/128 e ULA (fc00::/7); le reti pubbliche non sono ammesse.",
1323
1323
  originSettingsInvalid: "Impostazioni reverse proxy non valide. Controlla i quattro campi e salva di nuovo.",
1324
1324
  originConfigMissing: "Salva le impostazioni reverse proxy prima di avviare il backend.",
1325
1325
  originConfigInvalid: "Impostazioni salvate non valide. Correggile e salvale di nuovo, oppure cancella solo queste impostazioni.",
1326
1326
  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.",
1327
- originAddressUnavailable: "L’indirizzo di ascolto non è disponibile su questo computer. Scegli il suo IPv4 privato attuale.",
1327
+ originAddressUnavailable: "L’indirizzo di ascolto non è disponibile su questo computer. Scegli il suo IPv4 privato attuale, ::1 o un IPv6 ULA.",
1328
1328
  originStartFailed: "Impossibile avviare il backend privato. Controlla indirizzo, porta, permessi e log locali.",
1329
1329
  originConfigTargetInvalid: "Il file delle impostazioni proxy salvato non è un file regolare. Rimuovilo e salva di nuovo le impostazioni.",
1330
1330
  originControllerUnavailable: "Il provider reverse proxy non è disponibile in questa sessione. Riavvia DSH e riprova."
@@ -1337,10 +1337,10 @@ window.__ModuleLoader__.load({
1337
1337
  originDescription: "使用托管 Caddy 或已有 HTTPS 反向代理,接入 DSH Mobile 带认证的私有后端。",
1338
1338
  prepareOrigin: "接入自有 HTTPS 反向代理",
1339
1339
  originPublicOrigin: "公网 HTTPS 地址",
1340
- originListenHost: "私有监听 IPv4",
1340
+ originListenHost: "私有监听 IP(IPv4 / IPv6)",
1341
1341
  originListenPort: "HTTP 后端端口",
1342
1342
  originAllowedCidrs: "允许的代理来源 CIDR",
1343
- originHelp: "代理与 DSH 同机时用 127.0.0.1;代理在另一台局域网设备上时,填写本机私有 IPv4,并只允许代理实际直连来源 IPv4/32。多个 CIDR 用空格或逗号分隔。",
1343
+ originHelp: "同机代理用 127.0.0.1 或 ::1;跨私网代理填写本机私有 IPv4 或 IPv6 ULA,并允许同一地址族的代理直连来源 /32 或 /128。这里检查 NGINX 到 DSH 的连接,不是手机的公网 IP。多个 CIDR 用空格或逗号分隔。",
1344
1344
  originWarning: "HTTP 后端必须留在私网,绝不能直接暴露公网或做公网端口映射。反向代理负责 HTTPS,并须保留外部 Host(含端口)及 WebSocket 升级。",
1345
1345
  originSaveStart: "保存并启动后端",
1346
1346
  originBackendSaved: "已保存的 HTTP 后端(未运行)",
@@ -1375,15 +1375,15 @@ window.__ModuleLoader__.load({
1375
1375
  reconnectingOrigin: "正在重启私有 HTTP 后端…",
1376
1376
  resetOriginConfirm: "停止此后端并重置远程配对?所有已配对的远程设备都需要重新配对;反向代理配置和局域网设备保留。",
1377
1377
  originPublicOriginInvalid: "只填写公网 HTTPS 源地址,例如 https://phone.example.com:8815;不能含路径、查询参数、账号密码或私有地址。",
1378
- originListenHostInvalid: "填写本机明确的回环或私有 IPv4,不能使用 0.0.0.0 或公网地址。",
1378
+ originListenHostInvalid: "填写本机明确的回环/私有 IPv4、::1 或 IPv6 ULA(fc00::/7),不能使用全接口监听或公网地址。",
1379
1379
  originListenPortInvalid: "端口必须是 1024–65535 的整数;更低的端口需要插件没有的特权。",
1380
1380
  originListenPortReserved: "3443 属于局域网网关、3080 属于 DSH 自身,两者始终被占用。请换一个端口,例如 3444 或 3445。",
1381
- originAllowedCidrsInvalid: "填写 1–16 个规范的回环/私有 IPv4 CIDR,不能有主机位;局域网监听时须包含代理实际来源 IPv4/32,不接受公网网段。",
1381
+ originAllowedCidrsInvalid: "填写 1–16 个与监听地址同族的规范回环/私有 CIDR;私网监听须包含非回环代理来源,推荐 IPv4/32 或 IPv6/128。IPv6 支持 ::1/128 和 ULA(fc00::/7),不接受公网网段。",
1382
1382
  originSettingsInvalid: "反向代理配置无效,请检查四个字段后重新保存。",
1383
1383
  originConfigMissing: "请先保存反向代理配置,再启动后端。",
1384
1384
  originConfigInvalid: "已保存的配置无效,请修正并重新保存,或仅清除这项配置。",
1385
1385
  originPortInUse: "后端端口已被占用,请停止冲突进程或改用其他端口。3443、3080 以及已被 cloudflared 隧道占用的端口永远不会空闲。",
1386
- originAddressUnavailable: "本机没有这个监听地址,请选择当前本机的私有 IPv4。",
1386
+ originAddressUnavailable: "本机没有这个监听地址,请选择当前本机的私有 IPv4、::1 或 IPv6 ULA。",
1387
1387
  originStartFailed: "私有后端启动失败,请检查地址、端口、权限和本地日志。",
1388
1388
  originConfigTargetInvalid: "已保存的代理配置文件不是普通文件。请移除后重新保存设置。",
1389
1389
  originControllerUnavailable: "当前会话里反向代理提供方不可用。请重启 DSH 后重试。"
@@ -3861,7 +3861,7 @@ window.__ModuleLoader__.load({
3861
3861
  }
3862
3862
  /**
3863
3863
  * Maximum gap between input or scrolling events before an unused loading intent expires.
3864
- * A held finger may re-arm on its next owned move; an undelivered touch end cannot keep the lease alive.
3864
+ * A held pending intent expires without loading; a fresh gesture can create another intent.
3865
3865
  */
3866
3866
  const HISTORY_SCROLL_QUIET_MS = 1600;
3867
3867
  /**
@@ -3908,9 +3908,21 @@ window.__ModuleLoader__.load({
3908
3908
  * that compensation as another upward fling, clicked "Load earlier" again, and shook
3909
3909
  * the view in a chain. Only fresh input arms the gate, one arm buys at most one
3910
3910
  * successful load, and an arm with no further input expires quietly.
3911
+ *
3912
+ * A touch-driven eligible scroll never clicks while the gesture is live: DSH retains
3913
+ * a paging anchor while a page loads, and its preservation writes fight a drag still
3914
+ * in progress. The gate records a pending intent instead and spends it only once the
3915
+ * gesture settles — a `scrollend` after every owned contact ends, or the quiet lease
3916
+ * expiring with no contact listed — and only while the reader still holds the top
3917
+ * zone. The intent dies with its arm: it is never spendable after the arm ends, and
3918
+ * the lease re-anchors ownership instead of clicking under a listed finger. Wheel
3919
+ * input outside a touch gesture keeps the immediate click: a wheel flick has no held
3920
+ * gesture to fight.
3911
3921
  */
3912
3922
  function createHistoryAutoLoadGate(hooks) {
3913
3923
  let armed = false;
3924
+ let armedByWheel = false;
3925
+ let pendingIntent = false;
3914
3926
  const touchIds = /* @__PURE__ */ new Set();
3915
3927
  let disposed = false;
3916
3928
  let previousTop = hooks.getTop();
@@ -3922,10 +3934,32 @@ window.__ModuleLoader__.load({
3922
3934
  hooks.cancel(quietId);
3923
3935
  quietId = void 0;
3924
3936
  };
3937
+ /**
3938
+ * Spend a pending loading intent only after every owned contact ends in the top zone.
3939
+ * @returns whether a load was issued.
3940
+ */
3941
+ const settle = () => {
3942
+ if (!pendingIntent || touchIds.size > 0) return false;
3943
+ if (hooks.getTop() > AUTO_HISTORY_THRESHOLD_PX) {
3944
+ pendingIntent = false;
3945
+ return false;
3946
+ }
3947
+ if (!hooks.tryLoad()) return false;
3948
+ pendingIntent = false;
3949
+ return true;
3950
+ };
3925
3951
  const onQuiet = () => {
3926
3952
  quietPending = false;
3927
3953
  quietId = void 0;
3954
+ if (touchIds.size > 0) {
3955
+ if (pendingIntent) touchIds.clear();
3956
+ pendingIntent = false;
3957
+ armed = false;
3958
+ return;
3959
+ }
3960
+ settle();
3928
3961
  armed = false;
3962
+ pendingIntent = false;
3929
3963
  };
3930
3964
  const restartQuiet = () => {
3931
3965
  cancelQuiet();
@@ -3933,18 +3967,24 @@ window.__ModuleLoader__.load({
3933
3967
  quietPending = true;
3934
3968
  quietId = hooks.schedule(onQuiet, HISTORY_SCROLL_QUIET_MS);
3935
3969
  };
3936
- const arm = () => {
3970
+ const arm = (byWheel) => {
3937
3971
  armed = true;
3972
+ armedByWheel = byWheel;
3938
3973
  restartQuiet();
3939
3974
  };
3940
3975
  const onScroll = () => {
3941
3976
  const top = hooks.getTop();
3942
3977
  const eligible = armed && hooks.shouldLoad(previousTop, top);
3943
3978
  previousTop = top;
3944
- if (eligible && hooks.tryLoad()) {
3945
- armed = false;
3946
- cancelQuiet();
3947
- return;
3979
+ if (eligible) {
3980
+ if (armedByWheel) {
3981
+ if (hooks.tryLoad()) {
3982
+ armed = false;
3983
+ pendingIntent = false;
3984
+ cancelQuiet();
3985
+ return;
3986
+ }
3987
+ } else pendingIntent = true;
3948
3988
  }
3949
3989
  if (armed) restartQuiet();
3950
3990
  };
@@ -3952,15 +3992,20 @@ window.__ModuleLoader__.load({
3952
3992
  handleEvent: (event) => {
3953
3993
  if (disposed) return;
3954
3994
  switch (event.type) {
3955
- case "touchstart":
3956
- for (const touch of Array.from(event.changedTouches ?? [])) touchIds.add(touch.identifier);
3957
- if ((event.changedTouches?.length ?? 0) > 0) arm();
3995
+ case "touchstart": {
3996
+ const changed = Array.from(event.changedTouches ?? []);
3997
+ if (changed.length === 0) break;
3998
+ if (touchIds.size === 0) pendingIntent = false;
3999
+ for (const touch of changed) touchIds.add(touch.identifier);
4000
+ arm(false);
3958
4001
  break;
4002
+ }
3959
4003
  case "touchmove":
3960
- if (Array.from(event.changedTouches ?? []).some((touch) => touchIds.has(touch.identifier))) arm();
4004
+ if (Array.from(event.changedTouches ?? []).some((touch) => touchIds.has(touch.identifier))) arm(false);
3961
4005
  break;
3962
4006
  case "wheel":
3963
- arm();
4007
+ if (touchIds.size === 0) pendingIntent = false;
4008
+ arm(touchIds.size === 0);
3964
4009
  break;
3965
4010
  case "touchend":
3966
4011
  case "touchcancel": {
@@ -3970,6 +4015,15 @@ window.__ModuleLoader__.load({
3970
4015
  break;
3971
4016
  }
3972
4017
  case "scrollend":
4018
+ if (pendingIntent && settle()) {
4019
+ armed = false;
4020
+ cancelQuiet();
4021
+ break;
4022
+ }
4023
+ if (pendingIntent) {
4024
+ restartQuiet();
4025
+ break;
4026
+ }
3973
4027
  armed = false;
3974
4028
  cancelQuiet();
3975
4029
  break;
@@ -3978,6 +4032,8 @@ window.__ModuleLoader__.load({
3978
4032
  },
3979
4033
  reset: (top) => {
3980
4034
  armed = false;
4035
+ armedByWheel = false;
4036
+ pendingIntent = false;
3981
4037
  touchIds.clear();
3982
4038
  previousTop = top;
3983
4039
  cancelQuiet();
@@ -3985,20 +4041,27 @@ window.__ModuleLoader__.load({
3985
4041
  dispose: () => {
3986
4042
  disposed = true;
3987
4043
  armed = false;
4044
+ armedByWheel = false;
4045
+ pendingIntent = false;
3988
4046
  touchIds.clear();
3989
4047
  cancelQuiet();
3990
4048
  },
3991
4049
  state: () => ({
3992
4050
  armed,
3993
- previousTop
4051
+ previousTop,
4052
+ pendingIntent
3994
4053
  })
3995
4054
  };
3996
4055
  }
3997
- /** Attach a gate to one scroller; the disposer removes exactly the listeners it added. */
4056
+ /** Observe descendant input but only this scroller's movement; return its listener disposer. */
3998
4057
  function bindHistoryAutoLoadGate(scroller, gate) {
3999
- for (const type of HISTORY_INTENT_EVENTS) scroller.addEventListener(type, gate.handleEvent, HISTORY_INTENT_LISTENER_OPTIONS);
4058
+ const onEvent = (event) => {
4059
+ if ((event.type === "scroll" || event.type === "scrollend") && event.target !== scroller) return;
4060
+ gate.handleEvent(event);
4061
+ };
4062
+ for (const type of HISTORY_INTENT_EVENTS) scroller.addEventListener(type, onEvent, HISTORY_INTENT_LISTENER_OPTIONS);
4000
4063
  return () => {
4001
- for (const type of HISTORY_INTENT_EVENTS) scroller.removeEventListener(type, gate.handleEvent, HISTORY_INTENT_LISTENER_OPTIONS);
4064
+ for (const type of HISTORY_INTENT_EVENTS) scroller.removeEventListener(type, onEvent, HISTORY_INTENT_LISTENER_OPTIONS);
4002
4065
  };
4003
4066
  }
4004
4067
  /**
@@ -8585,7 +8648,7 @@ window.__ModuleLoader__.load({
8585
8648
  const originHost = element("input");
8586
8649
  originHost.type = "text";
8587
8650
  originHost.value = "127.0.0.1";
8588
- originHost.maxLength = 15;
8651
+ originHost.maxLength = 45;
8589
8652
  const originPortLabel = element("label", "dsh-mobile-control__field");
8590
8653
  originPortLabel.textContent = t("originListenPort");
8591
8654
  const originPort = element("input");
@@ -12413,6 +12476,8 @@ window.__ModuleLoader__.load({
12413
12476
  .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}
12414
12477
  .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}
12415
12478
  /* 42px matches the DSH sidebar footer row; it is not one of the panel target tiers. */
12479
+ /* Other footer entries share the shell's row; only our own action becomes compact. */
12480
+ .dsh-mobile-control__trigger:not(.is-rail):not(:only-child){flex:0 0 44px;width:44px;min-width:44px;padding:0;justify-content:center;gap:0}.dsh-mobile-control__trigger:not(.is-rail):not(:only-child) .dsh-mobile-control__trigger-label{display:none}
12416
12481
  .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}
12417
12482
  .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}
12418
12483
  @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}}