dsh-remote-plugin 0.6.17 → 0.6.19

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/public/index.html CHANGED
@@ -3,12 +3,12 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
- <meta name="theme-color" content="#05348B">
6
+ <meta name="theme-color" content="#0D1117">
7
7
  <meta name="mobile-web-app-capable" content="yes">
8
8
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
9
9
  <title>DSH Remote</title>
10
10
  <script>/* 统一入口: 桌面浏览器(宽屏且 UA 非移动)访问根地址自动切换到桌面端, 手机/窄窗口继续 App 界面 */ (function(){try{var proto=location.protocol;var ua=navigator.userAgent||'';var mobile=/Android|iPhone|iPad|iPod|Mobile|Windows Phone/i.test(ua);if((proto==='http:'||proto==='https:')&&!mobile&&window.innerWidth>=1024){var p=location.pathname;if(p==='/'||/\/index\.html$/.test(p)){location.replace('desktop/desktop.html'+location.search)}}}catch(e){}})()</script>
11
- <script>/* 首帧前应用皮肤, 避免闪白/闪黑 */ (function(){try{var t=localStorage.getItem('dshTheme');if(t!=='default'&&t!=='dark'&&t!=='light'&&t!=='neutral'){t=window.matchMedia('(prefers-color-scheme: light)').matches?'light':'default'}document.documentElement.setAttribute('data-theme',t)}catch(e){}})()</script>
11
+ <script>/* 首帧前应用皮肤, 避免闪白/闪黑 */ (function(){try{var t=localStorage.getItem('dshTheme');if(t!=='default'&&t!=='dark'&&t!=='light'&&t!=='neutral'&&t!=='mono'){t=window.matchMedia('(prefers-color-scheme: light)').matches?'light':'default'}document.documentElement.setAttribute('data-theme',t)}catch(e){}})()</script>
12
12
  <link rel="manifest" href="manifest.webmanifest">
13
13
  <link rel="icon" href="icon.svg" type="image/svg+xml">
14
14
  <link rel="stylesheet" href="styles.css">
@@ -229,39 +229,74 @@
229
229
  </section>
230
230
 
231
231
  <!-- Token 统计 -->
232
- <section id="view-stats" class="view hidden">
233
- <div class="section-head"><span data-i18n="statsPage.title">Token 统计</span><span id="stats-sub" class="muted"></span></div>
232
+ <section id="view-stats" class="view hidden stats-view" aria-labelledby="stats-page-title">
233
+ <header class="stats-page-head">
234
+ <div class="stats-page-copy">
235
+ <div class="stats-eyebrow" data-i18n="statsPage.eyebrow">USAGE OVERVIEW</div>
236
+ <h1 id="stats-page-title" data-i18n="statsPage.title">Token 统计</h1>
237
+ <p class="stats-page-subtitle" data-i18n="statsPage.subtitle">按日查看 Token 用量与费用估算</p>
238
+ </div>
239
+ <div class="stats-date-wrap">
240
+ <span class="stats-date-label" data-i18n="statsPage.latest">最新日期</span>
241
+ <span id="stats-sub" class="stats-date">—</span>
242
+ </div>
243
+ </header>
244
+ <div class="stats-toolbar">
245
+ <span class="stats-toolbar-label" data-i18n="statsPage.chartMetric">图表指标</span>
246
+ <div id="stats-mode-picker" class="stats-mode-picker" role="group" data-i18n-aria="statsPage.chartMetric">
247
+ <button type="button" class="stats-mode-btn active" data-stats-mode="token" aria-pressed="true" data-i18n="statsPage.modeTokens">Token 数</button>
248
+ <button type="button" class="stats-mode-btn" data-stats-mode="cost" aria-pressed="false" data-i18n="statsPage.modeCost">费用(官方)</button>
249
+ </div>
250
+ </div>
234
251
  <div class="stats-cards" id="stats-cards"></div>
235
- <div id="stats-legend" class="stats-legend"></div>
236
- <div id="stats-chart" class="stats-chart"></div>
237
- <div id="stats-note" class="stats-note"></div>
252
+ <section class="stats-panel" data-stats-panel="cost" aria-labelledby="stats-cost-title">
253
+ <div class="stats-panel-head">
254
+ <div>
255
+ <div class="stats-panel-kicker" data-i18n="statsPage.costKicker">ESTIMATED COST</div>
256
+ <h2 id="stats-cost-title" class="stats-panel-title" data-i18n="statsPage.costTrend">费用趋势</h2>
257
+ </div>
258
+ <div id="stats-legend" class="stats-legend"></div>
259
+ </div>
260
+ <div id="stats-chart" class="stats-chart" role="list" aria-live="polite" data-i18n-aria="statsPage.costChartLabel"></div>
261
+ </section>
262
+ <section class="stats-panel" data-stats-panel="token" aria-labelledby="stats-token-title">
263
+ <div class="stats-panel-head">
264
+ <div>
265
+ <div class="stats-panel-kicker" data-i18n="statsPage.tokenKicker">TOKEN VOLUME</div>
266
+ <h2 id="stats-token-title" class="stats-panel-title" data-i18n="statsPage.tokenTrend">Token 量趋势</h2>
267
+ </div>
268
+ <div id="stats-token-legend" class="stats-legend"></div>
269
+ </div>
270
+ <div id="stats-token-chart" class="stats-chart" role="list" aria-live="polite" data-i18n-aria="statsPage.tokenChartLabel"></div>
271
+ </section>
272
+ <p id="stats-note" class="stats-note"></p>
238
273
  </section>
239
274
 
240
275
  <!-- 设置 -->
241
276
  <section id="view-settings" class="view hidden">
242
277
  <div id="settings-home">
243
278
  <div class="settings-group">
244
- <div class="setting-row" data-settings-group="general" style="cursor:pointer">
279
+ <button type="button" class="setting-row setting-link" data-settings-group="general">
245
280
  <div><div class="setting-name" data-i18n="settings.groupGeneral">通用</div><div class="setting-desc" data-i18n="settings.groupGeneralDesc">显示工具调用、预设提示词</div></div>
246
- </div>
247
- <div class="setting-row" data-settings-group="model" style="cursor:pointer">
281
+ </button>
282
+ <button type="button" class="setting-row setting-link" data-settings-group="model">
248
283
  <div><div class="setting-name" data-i18n="settings.groupModel">模型</div><div class="setting-desc" data-i18n="settings.groupModelDesc">提供方、API 密钥和模型目录</div></div>
249
- </div>
250
- <div class="setting-row" data-settings-group="tests" style="cursor:pointer">
284
+ </button>
285
+ <button type="button" class="setting-row setting-link" data-settings-group="tests">
251
286
  <div><div class="setting-name" data-i18n="settings.groupTests">功能测试</div><div class="setting-desc" data-i18n="settings.groupTestsDesc">验证 Android 和 DSH 原生能力</div></div>
252
- </div>
253
- <div class="setting-row" data-settings-group="servers" style="cursor:pointer">
287
+ </button>
288
+ <button type="button" class="setting-row setting-link" data-settings-group="servers">
254
289
  <div><div class="setting-name" data-i18n="settings.groupServers">服务器</div><div class="setting-desc" data-i18n="settings.groupServersDesc">服务器地址、扫码连接</div></div>
255
- </div>
256
- <div class="setting-row" data-settings-group="notify" style="cursor:pointer">
290
+ </button>
291
+ <button type="button" class="setting-row setting-link" data-settings-group="notify">
257
292
  <div><div class="setting-name" data-i18n="settings.groupNotify">通知</div><div class="setting-desc" data-i18n="settings.groupNotifyDesc">通知、提醒、后台轮询</div></div>
258
- </div>
259
- <div class="setting-row" data-settings-group="theme" style="cursor:pointer">
293
+ </button>
294
+ <button type="button" class="setting-row setting-link" data-settings-group="theme">
260
295
  <div><div class="setting-name" data-i18n="settings.groupTheme">皮肤</div><div class="setting-desc" data-i18n="settings.groupThemeDesc">配色主题</div></div>
261
- </div>
262
- <div class="setting-row" data-settings-group="about" style="cursor:pointer">
296
+ </button>
297
+ <button type="button" class="setting-row setting-link" data-settings-group="about">
263
298
  <div><div class="setting-name" data-i18n="settings.groupAbout">关于</div><div class="setting-desc" data-i18n="settings.groupAboutDesc">DSH 状态、检查更新</div></div>
264
- </div>
299
+ </button>
265
300
  </div>
266
301
  <div class="settings-group">
267
302
  <div class="setting-row">
@@ -301,7 +336,7 @@
301
336
  <p class="about" data-i18n="settings.about">DSH Remote · 局域网 / Tailscale 均可访问<br>仅作为 DSH 本机 3080 端口的带认证代理</p>
302
337
  </div>
303
338
 
304
- <div id="settings-page-general" class="settings-subpage hidden">
339
+ <div id="settings-page-general" class="settings-subpage hidden" data-motion-panel>
305
340
  <div class="settings-group">
306
341
  <div class="setting-row" data-settings-back style="cursor:pointer">
307
342
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -328,7 +363,7 @@
328
363
  </div>
329
364
  </div>
330
365
 
331
- <div id="settings-page-model" class="settings-subpage hidden">
366
+ <div id="settings-page-model" class="settings-subpage hidden" data-motion-panel>
332
367
  <div class="settings-group">
333
368
  <div class="setting-row" data-settings-back style="cursor:pointer">
334
369
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -351,7 +386,7 @@
351
386
  </div>
352
387
  </div>
353
388
 
354
- <div id="settings-page-tests" class="settings-subpage hidden">
389
+ <div id="settings-page-tests" class="settings-subpage hidden" data-motion-panel>
355
390
  <div class="settings-group">
356
391
  <div class="setting-row" data-settings-back style="cursor:pointer">
357
392
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -380,7 +415,7 @@
380
415
  </div>
381
416
  </div>
382
417
 
383
- <div id="settings-page-servers" class="settings-subpage hidden">
418
+ <div id="settings-page-servers" class="settings-subpage hidden" data-motion-panel>
384
419
  <div class="settings-group">
385
420
  <div class="setting-row" data-settings-back style="cursor:pointer">
386
421
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -417,7 +452,7 @@
417
452
  </div>
418
453
  </div>
419
454
 
420
- <div id="settings-page-notify" class="settings-subpage hidden">
455
+ <div id="settings-page-notify" class="settings-subpage hidden" data-motion-panel>
421
456
  <div class="settings-group">
422
457
  <div class="setting-row" data-settings-back style="cursor:pointer">
423
458
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -461,7 +496,7 @@
461
496
  </div>
462
497
  </div>
463
498
 
464
- <div id="settings-page-theme" class="settings-subpage hidden">
499
+ <div id="settings-page-theme" class="settings-subpage hidden" data-motion-panel>
465
500
  <div class="settings-group">
466
501
  <div class="setting-row" data-settings-back style="cursor:pointer">
467
502
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -470,13 +505,13 @@
470
505
  </div>
471
506
  <div class="settings-group">
472
507
  <div class="setting-row">
473
- <div><div class="setting-name" data-i18n="settings.themeTitle">皮肤</div><div class="setting-desc" data-i18n="settings.themeDesc">默认深空 / 落日 / 易北爱乐厅 / 草原孤塔</div></div>
508
+ <div><div class="setting-name" data-i18n="settings.themeTitle">皮肤</div><div class="setting-desc" data-i18n="settings.themeDesc">默认深空 / 落日 / 易北爱乐厅 / 草原孤塔 / 黑曜白</div></div>
474
509
  <button id="btn-theme" class="mini-btn" style="min-width:104px">默认深空</button>
475
510
  </div>
476
511
  </div>
477
512
  </div>
478
513
 
479
- <div id="settings-page-about" class="settings-subpage hidden">
514
+ <div id="settings-page-about" class="settings-subpage hidden" data-motion-panel>
480
515
  <div class="settings-group">
481
516
  <div class="setting-row" data-settings-back style="cursor:pointer">
482
517
  <button class="mini-btn" data-i18n="settings.back">‹ 返回</button>
@@ -572,7 +607,7 @@
572
607
  <span data-i18n="nav.stats">统计</span>
573
608
  </button>
574
609
  <button data-view="view-settings" class="nav-btn">
575
- <span class="nav-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-1.8 1.8-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.5v.2h-2.5v-.2a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.9.3l-.1.1-1.8-1.8.1-.1A1.7 1.7 0 0 0 8.1 15a1.7 1.7 0 0 0-1.5-1H6v-2.5h.2a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.9l-.1-.1 1.8-1.8.1.1a1.7 1.7 0 0 0 1.9.3 1.7 1.7 0 0 0 1-1.5v-.2h2.5v.2a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.9-.3l.1-.1 1.8 1.8-.1.1a1.7 1.7 0 0 0-.3 1.9 1.7 1.7 0 0 0 1.5 1h.2V14h-.2a1.7 1.7 0 0 0-1.5 1Z"/></svg></span>
610
+ <span class="nav-ico"><svg class="settings-gear" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.09a2 2 0 0 1 1 1.73v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.09a2 2 0 0 1-1-1.74v-.51a2 2 0 0 1 1-1.73l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z"/><circle cx="12" cy="12" r="3"/></svg></span>
576
611
  <span data-i18n="nav.settings">设置</span>
577
612
  </button>
578
613
  </nav>
@@ -877,12 +912,14 @@
877
912
  'overview.action.openSession': '打开最近会话', 'overview.action.newSession': '新建会话', 'overview.action.attention': '处理待办', 'overview.action.connect': '去设置连接', 'overview.action.refresh': '重新检查连接',
878
913
  'overview.communityEyebrow': 'COMMUNITY BOARD', 'overview.communityTitle': '公告与投票', 'overview.communityHistory': '历史公告', 'overview.communityEmpty': '暂无未读公告',
879
914
  'overview.communityPoll': '投票', 'overview.communityNotice': '公告', 'overview.communityNew': '新', 'overview.communityVote': '参与投票', 'overview.communityView': '查看公告', 'overview.communityVoted': '已参与',
880
- 'statsPage.title': 'Token 统计',
881
- 'statsPage.todayTokens': '今日 Token', 'statsPage.todayCost': '今日费用', 'statsPage.peakShare': '高峰占比',
915
+ 'statsPage.eyebrow': '使用概览', 'statsPage.title': 'Token 统计', 'statsPage.subtitle': '按日查看 Token 用量与费用估算', 'statsPage.latest': '最新日期', 'statsPage.chartMetric': '图表指标', 'statsPage.modeTokens': 'Token 数', 'statsPage.modeCost': '费用(官方)',
916
+ 'statsPage.costKicker': 'ESTIMATED COST', 'statsPage.tokenKicker': 'TOKEN VOLUME', 'statsPage.costChartLabel': '近 7 天费用趋势', 'statsPage.tokenChartLabel': '近 7 天 Token 用量趋势',
917
+ 'statsPage.today': '今日', 'statsPage.tokenUnit': 'Tokens', 'statsPage.breakdown': '今日用量构成', 'statsPage.estimated': '估算费用', 'statsPage.peakShareLabel': '高峰费用占比 {n}%',
918
+ 'statsPage.todayTokens': '今日 Token', 'statsPage.todayCost': '今日费用', 'statsPage.peakShare': '高峰占比', 'statsPage.costTrend': '费用趋势', 'statsPage.tokenTrend': 'Token 量趋势', 'statsPage.tokenTotal': 'Token 总量',
882
919
  'statsPage.input': '未缓存输入', 'statsPage.cacheRead': '缓存命中', 'statsPage.cacheWrite': '缓存写入', 'statsPage.output': '输出',
883
920
  'statsPage.peak': '高峰', 'statsPage.off': '空闲', 'statsPage.days': '近 {n} 天',
884
921
  'statsPage.gatewayDown': '统计需要网关运行', 'statsPage.empty': '暂无统计,产生会话后自动聚合',
885
- 'statsPage.note': '注:本数据仅在使用 DeepSeek 官方 API 时估算;基于 token 计算,与官网账单可能有出入,一切以官网为准。统计自 2026-08-17 定价生效日起。',
922
+ 'statsPage.note': '注:Token 量按 DSH usage 事件记录,第三方 provider 需提供 usage 才能计入;费用仅对已知 DeepSeek 模型估算,与官网账单可能有出入,一切以官网为准。统计自 2026-08-17 定价生效日起。',
886
923
  'home.newSession': '+ 新会话', 'home.newWorkspace': '+ 工作区', 'home.empty': '暂无会话', 'home.createFailed': '新建会话失败', 'home.created': '会话已创建',
887
924
  'wb.unbound': '未绑定工作台', 'wb.bound': '绑定 {title}', 'wb.noProjects': '暂无项目', 'wb.noSessions': '暂无会话', 'wb.newSession': '新会话', 'wb.orderSaved': '顺序已保存到当前设备', 'wb.flatHidden': '工作台会话已收起({n} 个)', 'wb.archivedHidden': '------隐藏已归档会话------', 'wb.archivedShown': '------显示已归档会话------',
888
925
  'workspace.createTitle': '新建工作区', 'workspace.createDesc': '将在当前文件目录下创建文件夹,并自动打开新会话。', 'workspace.parent': '父目录', 'workspace.namePlaceholder': '例如:my-project', 'workspace.cancel': '取消', 'workspace.create': '创建并打开', 'workspace.nameRequired': '请输入工作区名称', 'workspace.exists': '该目录已存在', 'workspace.invalidName': '名称不能包含路径分隔符', 'workspace.createFailed': '创建工作区失败:{msg}', 'workspace.created': '工作区已创建', 'workspace.createdNoSession': '工作区已创建,但新会话未能打开',
@@ -903,13 +940,13 @@
903
940
  'speed.switched': '已切换到最快服务器:{url}({ms}ms)', 'speed.alreadyBest': '当前已是最快:{url}({ms}ms)',
904
941
  'speed.allDown': '全部服务器不可达',
905
942
  'servers.empty': '未添加备用服务器 · 默认使用当前页面地址', 'servers.untested': '未测速',
906
- 'servers.current': ' · 当前', 'servers.unreachable': '不可达', 'servers.delete': '删除服务器',
943
+ 'servers.current': ' · 当前', 'servers.unreachable': '不可达', 'servers.delete': '删除服务器', 'servers.tokenSet': '令牌已配置', 'servers.tokenMissing': '未配置令牌',
907
944
  'servers.currentDesc': '当前: {url}', 'servers.notSet': '未设置服务器', 'servers.defaultDesc': '默认 = 当前页面地址',
908
945
  'servers.needAddress': '请输入服务器地址', 'servers.badProtocol': '地址需以 http:// 或 https:// 开头',
909
- 'servers.duplicate': '该地址已在列表中', 'servers.added': '已添加服务器', 'servers.removedActive': '已删除当前服务器,重新测速…',
946
+ 'servers.duplicate': '相同地址和令牌已在列表中', 'servers.added': '已添加服务器', 'servers.removedActive': '已删除当前服务器,重新测速…',
910
947
  'servers.currentDescGroup': '当前:{group} · {url}({ms})',
911
948
  'servers.edit': '编辑', 'servers.manualSelected': '已切换至:{url}',
912
- 'servers.promptNote': '服务器备注(可留空):', 'servers.promptEditNote': '编辑备注({url}):',
949
+ 'servers.promptNote': '服务器备注(可留空):', 'servers.promptEditNote': '编辑备注({url}):', 'servers.promptEditToken': '编辑服务器令牌(留空保持不变):',
913
950
  'servers.promptEditUrl': '编辑服务器地址:', 'servers.promptEditGroup': '所属组:', 'servers.edited': '服务器已更新',
914
951
  'speed.switchedOrigin': '已切换到当前页面(同源)', 'speed.manualUsing': '手动模式:{url}({ms})',
915
952
  'groups.current': '当前组', 'groups.add': '+ 组', 'groups.noServer': '组内暂无服务器',
@@ -938,6 +975,7 @@
938
975
  'stats.llmTime': '模型耗时', 'stats.minutes': ' 分钟', 'stats.outputCache': '输出 / 缓存读',
939
976
  'stats.ctxPressure': '上下文压力', 'stats.permission': '权限', 'stats.empty': '暂无统计数据',
940
977
  'goal.title': '目标', 'goal.pause': '暂停', 'goal.resume': '继续', 'goal.complete': '完成', 'goal.edit': '改目标', 'goal.clear': '清除',
978
+ 'goal.collapse': '收起目标到侧边', 'goal.collapseShort': '收起', 'goal.expand': '展开目标', 'goal.collapsedHint': '仍在运行',
941
979
  'goal.none': '当前会话没有目标', 'goal.confirmClear': '清除当前目标?(不会删除会话)', 'goal.confirmComplete': '将目标标记为完成?',
942
980
  'goal.actionFailed': '目标操作失败', 'goal.actionSubmitted': '目标操作已提交',
943
981
  'goal.cannotEmpty': '目标不能为空', 'goal.updateFailed': '更新失败', 'goal.updated': '目标已更新',
@@ -1004,7 +1042,7 @@
1004
1042
  'settings.groupGeneral': '通用', 'settings.groupGeneralDesc': '显示工具调用、预设提示词',
1005
1043
  'settings.groupModel': '模型', 'settings.groupModelDesc': '提供方、API 密钥和模型目录',
1006
1044
  'settings.groupTests': '功能测试', 'settings.groupTestsDesc': '验证 Android 和 DSH 原生能力',
1007
- 'settings.groupServers': '服务器', 'settings.groupServersDesc': '服务器地址、扫码连接',
1045
+ 'settings.groupServers': '服务器', 'settings.groupServersDesc': '服务器地址、令牌与扫码连接',
1008
1046
  'settings.groupNotify': '通知', 'settings.groupNotifyDesc': '通知、提醒、后台轮询',
1009
1047
  'settings.groupTheme': '皮肤', 'settings.groupThemeDesc': '配色主题',
1010
1048
  'settings.groupAbout': '关于', 'settings.groupAboutDesc': 'DSH 状态、检查更新',
@@ -1050,8 +1088,8 @@
1050
1088
  'settings.toolsTitle': '显示工具调用', 'settings.toolsDesc': '关闭后隐藏 🔧/📦 工具调用与结果',
1051
1089
  'settings.mobileEnterTitle': '手机回车行为', 'settings.mobileEnterDesc': '选择手机端按 Enter 时换行或直接发送', 'settings.mobileEnterNewline': '回车换行', 'settings.mobileEnterSend': '回车发送',
1052
1090
  'settings.langTitle': '语言 / Language', 'settings.langDesc': '界面语言,跟随系统或手动切换',
1053
- 'settings.themeTitle': '皮肤', 'settings.themeDesc': '默认深空 / 落日 / 易北爱乐厅 / 草原孤塔',
1054
- 'theme.default': '默认深空', 'theme.dark': '落日', 'theme.light': '易北爱乐厅', 'theme.neutral': '草原孤塔',
1091
+ 'settings.themeTitle': '皮肤', 'settings.themeDesc': '默认深空 / 落日 / 易北爱乐厅 / 草原孤塔 / 黑曜白',
1092
+ 'theme.default': '默认深空', 'theme.dark': '落日', 'theme.light': '易北爱乐厅', 'theme.neutral': '草原孤塔', 'theme.mono': '黑曜白',
1055
1093
  'theme.panelTitle': '选择配色', 'theme.close': '关闭',
1056
1094
  'settings.tokenTitle': '访问令牌', 'settings.change': '更换',
1057
1095
  'settings.hostTitle': 'DSH 状态', 'settings.hostProbing': '探测中…', 'settings.probe': '探测', 'settings.probeFailed': '探测失败',
@@ -1104,12 +1142,14 @@
1104
1142
  'overview.action.openSession': 'Open recent session', 'overview.action.newSession': 'New session', 'overview.action.attention': 'Review pending', 'overview.action.connect': 'Set up connection', 'overview.action.refresh': 'Check connection',
1105
1143
  'overview.communityEyebrow': 'COMMUNITY BOARD', 'overview.communityTitle': 'Announcements & polls', 'overview.communityHistory': 'History', 'overview.communityEmpty': 'No unread announcements',
1106
1144
  'overview.communityPoll': 'Poll', 'overview.communityNotice': 'Notice', 'overview.communityNew': 'New', 'overview.communityVote': 'Vote now', 'overview.communityView': 'View notice', 'overview.communityVoted': 'Voted',
1107
- 'statsPage.title': 'Token stats',
1108
- 'statsPage.todayTokens': 'Tokens today', 'statsPage.todayCost': 'Cost today', 'statsPage.peakShare': 'Peak share',
1145
+ 'statsPage.eyebrow': 'USAGE OVERVIEW', 'statsPage.title': 'Token stats', 'statsPage.subtitle': 'Daily token volume and estimated costs', 'statsPage.latest': 'Latest date', 'statsPage.chartMetric': 'Chart metric', 'statsPage.modeTokens': 'Token count', 'statsPage.modeCost': 'Cost (official)',
1146
+ 'statsPage.costKicker': 'ESTIMATED COST', 'statsPage.tokenKicker': 'TOKEN VOLUME', 'statsPage.costChartLabel': 'Cost trend for the last 7 days', 'statsPage.tokenChartLabel': 'Token volume trend for the last 7 days',
1147
+ 'statsPage.today': 'Today', 'statsPage.tokenUnit': 'Tokens', 'statsPage.breakdown': 'Today’s usage breakdown', 'statsPage.estimated': 'Estimated cost', 'statsPage.peakShareLabel': 'Peak cost share {n}%',
1148
+ 'statsPage.todayTokens': 'Tokens today', 'statsPage.todayCost': 'Cost today', 'statsPage.peakShare': 'Peak share', 'statsPage.costTrend': 'Cost trend', 'statsPage.tokenTrend': 'Token volume trend', 'statsPage.tokenTotal': 'Total tokens',
1109
1149
  'statsPage.input': 'Uncached input', 'statsPage.cacheRead': 'Cache read', 'statsPage.cacheWrite': 'Cache write', 'statsPage.output': 'Output',
1110
1150
  'statsPage.peak': 'Peak', 'statsPage.off': 'Off-peak', 'statsPage.days': 'Last {n} days',
1111
1151
  'statsPage.gatewayDown': 'Stats require the gateway', 'statsPage.empty': 'No stats yet — they aggregate as sessions happen',
1112
- 'statsPage.note': 'Note: estimates assume the official DeepSeek API. Token-based calculation may differ from the official bill; always defer to deepseek.com. Stats start from the 2026-08-17 pricing date.',
1152
+ 'statsPage.note': 'Note: token volume uses DSH usage events; a third-party provider must expose usage to be counted. Costs are estimates for known DeepSeek models and may differ from the official bill; always defer to deepseek.com. Stats start from the 2026-08-17 pricing date.',
1113
1153
  'home.newSession': '+ New session', 'home.newWorkspace': '+ Workspace', 'home.empty': 'No sessions yet', 'home.createFailed': 'Failed to create session', 'home.created': 'Session created',
1114
1154
  'wb.unbound': 'No workbench bound', 'wb.bound': 'Bound {title}', 'wb.noProjects': 'No projects', 'wb.noSessions': 'No sessions', 'wb.newSession': 'New session', 'wb.orderSaved': 'Order saved on this device', 'wb.flatHidden': 'Workbench sessions folded ({n})', 'wb.archivedHidden': '------ Hide archived ------', 'wb.archivedShown': '------ Show archived ------',
1115
1155
  'workspace.createTitle': 'New workspace', 'workspace.createDesc': 'Create a folder in the current file directory and open a new session there.', 'workspace.parent': 'Parent folder', 'workspace.namePlaceholder': 'For example: my-project', 'workspace.cancel': 'Cancel', 'workspace.create': 'Create & open', 'workspace.nameRequired': 'Enter a workspace name', 'workspace.exists': 'That folder already exists', 'workspace.invalidName': 'The name cannot contain path separators', 'workspace.createFailed': 'Could not create workspace: {msg}', 'workspace.created': 'Workspace created', 'workspace.createdNoSession': 'Workspace created, but the new session could not be opened',
@@ -1130,13 +1170,13 @@
1130
1170
  'speed.switched': 'Switched to fastest server: {url} ({ms}ms)', 'speed.alreadyBest': 'Already on fastest: {url} ({ms}ms)',
1131
1171
  'speed.allDown': 'All servers unreachable',
1132
1172
  'servers.empty': 'No backup servers · using the current page URL', 'servers.untested': 'Untested',
1133
- 'servers.current': ' · current', 'servers.unreachable': 'Unreachable', 'servers.delete': 'Remove server',
1173
+ 'servers.current': ' · current', 'servers.unreachable': 'Unreachable', 'servers.delete': 'Remove server', 'servers.tokenSet': 'Token set', 'servers.tokenMissing': 'No token',
1134
1174
  'servers.currentDesc': 'Current: {url}', 'servers.notSet': 'No server set', 'servers.defaultDesc': 'Default = current page URL',
1135
1175
  'servers.needAddress': 'Enter a server address', 'servers.badProtocol': 'Address must start with http:// or https://',
1136
- 'servers.duplicate': 'This address is already in the list', 'servers.added': 'Server added', 'servers.removedActive': 'Removed active server, re-testing…',
1176
+ 'servers.duplicate': 'The same address and token is already in the list', 'servers.added': 'Server added', 'servers.removedActive': 'Removed active server, re-testing…',
1137
1177
  'servers.currentDescGroup': 'Current: {group} · {url} ({ms})',
1138
1178
  'servers.edit': 'Edit', 'servers.manualSelected': 'Switched to: {url}',
1139
- 'servers.promptNote': 'Server note (optional):', 'servers.promptEditNote': 'Edit note ({url}):',
1179
+ 'servers.promptNote': 'Server note (optional):', 'servers.promptEditNote': 'Edit note ({url}):', 'servers.promptEditToken': 'Edit server token (blank keeps the current token):',
1140
1180
  'servers.promptEditUrl': 'Edit server URL:', 'servers.promptEditGroup': 'Group:', 'servers.edited': 'Server updated',
1141
1181
  'speed.switchedOrigin': 'Switched to current page (same origin)', 'speed.manualUsing': 'Manual mode: {url} ({ms})',
1142
1182
  'groups.current': 'Current group', 'groups.add': '+ Group', 'groups.noServer': 'No servers in this group',
@@ -1165,6 +1205,7 @@
1165
1205
  'stats.llmTime': 'LLM time', 'stats.minutes': ' min', 'stats.outputCache': 'Output / cache read',
1166
1206
  'stats.ctxPressure': 'Context pressure', 'stats.permission': 'Permission', 'stats.empty': 'No stats yet',
1167
1207
  'goal.title': 'Goal', 'goal.pause': 'Pause', 'goal.resume': 'Resume', 'goal.complete': 'Complete', 'goal.edit': 'Edit goal', 'goal.clear': 'Clear',
1208
+ 'goal.collapse': 'Collapse goal to the side', 'goal.collapseShort': 'Collapse', 'goal.expand': 'Expand goal', 'goal.collapsedHint': 'Still running',
1168
1209
  'goal.none': 'This session has no goal', 'goal.confirmClear': 'Clear the current goal? (the session stays)', 'goal.confirmComplete': 'Mark the goal as completed?',
1169
1210
  'goal.actionFailed': 'Goal action failed', 'goal.actionSubmitted': 'Goal action submitted',
1170
1211
  'goal.cannotEmpty': 'Goal cannot be empty', 'goal.updateFailed': 'Update failed', 'goal.updated': 'Goal updated',
@@ -1231,7 +1272,7 @@
1231
1272
  'settings.groupGeneral': 'General', 'settings.groupGeneralDesc': 'Tool calls, prompt presets',
1232
1273
  'settings.groupModel': 'Models', 'settings.groupModelDesc': 'Providers, API keys, and model catalogs',
1233
1274
  'settings.groupTests': 'Feature tests', 'settings.groupTestsDesc': 'Check Android and DSH native capabilities',
1234
- 'settings.groupServers': 'Servers', 'settings.groupServersDesc': 'Server address, scan to pair',
1275
+ 'settings.groupServers': 'Servers', 'settings.groupServersDesc': 'Server address, token, and scan to pair',
1235
1276
  'settings.groupNotify': 'Notifications', 'settings.groupNotifyDesc': 'Notifications, reminders, background polling',
1236
1277
  'settings.groupTheme': 'Theme', 'settings.groupThemeDesc': 'Color themes',
1237
1278
  'settings.groupAbout': 'About', 'settings.groupAboutDesc': 'DSH status, updates',
@@ -1277,8 +1318,8 @@
1277
1318
  'settings.toolsTitle': 'Show tool calls', 'settings.toolsDesc': 'Hide 🔧/📦 tool calls and results when off',
1278
1319
  'settings.mobileEnterTitle': 'Mobile Enter behavior', 'settings.mobileEnterDesc': 'Choose whether Enter inserts a newline or sends', 'settings.mobileEnterNewline': 'Enter = newline', 'settings.mobileEnterSend': 'Enter = send',
1279
1320
  'settings.langTitle': 'Language / 语言', 'settings.langDesc': 'UI language, follows system or switch manually',
1280
- 'settings.themeTitle': 'Theme', 'settings.themeDesc': 'Default deep space / Sunset / Elbphilharmonie / Prairie Tower',
1281
- 'theme.default': 'Default', 'theme.dark': 'Sunset', 'theme.light': 'Elbphilharmonie', 'theme.neutral': 'Prairie Tower',
1321
+ 'settings.themeTitle': 'Theme', 'settings.themeDesc': 'Deep Space / Sunset / Elbphilharmonie / Prairie Tower / Monochrome',
1322
+ 'theme.default': 'Default', 'theme.dark': 'Sunset', 'theme.light': 'Elbphilharmonie', 'theme.neutral': 'Prairie Tower', 'theme.mono': 'Monochrome',
1282
1323
  'theme.panelTitle': 'Choose theme', 'theme.close': 'Close',
1283
1324
  'settings.tokenTitle': 'Access token', 'settings.change': 'Change',
1284
1325
  'settings.hostTitle': 'DSH status', 'settings.hostProbing': 'Probing…', 'settings.probe': 'Probe', 'settings.probeFailed': 'Probe failed',
package/public/motion.js CHANGED
@@ -7,8 +7,21 @@
7
7
 
8
8
  const reduceQuery = window.matchMedia?.('(prefers-reduced-motion: reduce)')
9
9
  let reduced = !!reduceQuery?.matches
10
- reduceQuery?.addEventListener?.('change', event => { reduced = !!event.matches })
11
- const activePulses = new WeakSet()
10
+ const activePulses = new Set()
11
+ const recentRuns = new WeakMap()
12
+
13
+ reduceQuery?.addEventListener?.('change', event => {
14
+ reduced = !!event.matches
15
+ if (!reduced) return
16
+ const animated = document.querySelectorAll('.view, .ds-view, [data-motion-view], .modal, .ds-modal, .sheet, .ds-drawer, [data-motion-panel]')
17
+ gsap.killTweensOf(animated)
18
+ clear(animated)
19
+ for (const node of activePulses) {
20
+ gsap.killTweensOf(node)
21
+ clear(node)
22
+ }
23
+ activePulses.clear()
24
+ })
12
25
 
13
26
  function clear(targets) {
14
27
  gsap.set(targets, { clearProps: 'opacity,visibility,transform,willChange' })
@@ -22,17 +35,32 @@
22
35
  return items.map(motionKey).join('|')
23
36
  }
24
37
 
38
+ function startedRecently(node, threshold = 48) {
39
+ const now = window.performance?.now?.() || Date.now()
40
+ const previous = recentRuns.get(node) || 0
41
+ recentRuns.set(node, now)
42
+ return previous > 0 && now - previous < threshold
43
+ }
44
+
25
45
  function view(view) {
26
- if (!view) return
27
- const children = [...view.children].filter(child => !child.classList.contains('hidden')).slice(0, 6)
28
- gsap.killTweensOf([view, ...children])
46
+ if (!view || startedRecently(view)) return
47
+ const children = [...view.children].filter(child => !child.classList.contains('hidden')).slice(0, 8)
48
+ const targets = children.length ? children : [view]
49
+ gsap.killTweensOf([view, ...targets])
50
+ gsap.set(view, { autoAlpha: 1, clearProps: 'transform' })
29
51
  if (reduced) {
30
- clear([view, ...children])
52
+ clear([view, ...targets])
31
53
  return
32
54
  }
33
- const tl = gsap.timeline({ defaults: { ease: 'power2.out' } })
34
- tl.fromTo(view, { autoAlpha: 0, y: 8 }, { autoAlpha: 1, y: 0, duration: 0.22, clearProps: 'transform' })
35
- .fromTo(children, { autoAlpha: 0, y: 6 }, { autoAlpha: 1, y: 0, duration: 0.16, stagger: 0.025, clearProps: 'transform' }, '<0.04')
55
+ gsap.fromTo(targets, { opacity: 0.72, y: 8 }, {
56
+ opacity: 1,
57
+ y: 0,
58
+ duration: 0.24,
59
+ ease: 'power2.out',
60
+ stagger: { each: 0.035, from: 'start' },
61
+ overwrite: 'auto',
62
+ clearProps: 'opacity,visibility,transform,willChange'
63
+ })
36
64
  }
37
65
 
38
66
  function list(container, selector) {
@@ -420,8 +448,57 @@
420
448
  registry.set(selector, state)
421
449
  }
422
450
 
451
+ function panel(node) {
452
+ if (!node || startedRecently(node)) return
453
+ gsap.killTweensOf(node)
454
+ if (reduced) return clear(node)
455
+ gsap.fromTo(node, { autoAlpha: 0, y: 8 }, {
456
+ autoAlpha: 1,
457
+ y: 0,
458
+ duration: 0.2,
459
+ ease: 'power2.out',
460
+ overwrite: 'auto',
461
+ clearProps: 'opacity,visibility,transform,willChange'
462
+ })
463
+ }
464
+
465
+ function popover(node) {
466
+ if (!node || startedRecently(node)) return
467
+ const opensUpward = !!(node.closest('.composer-wrap, .ds-composer') || node.style.bottom)
468
+ gsap.killTweensOf(node)
469
+ if (reduced) return clear(node)
470
+ gsap.fromTo(node, {
471
+ autoAlpha: 0,
472
+ y: opensUpward ? 7 : -7,
473
+ scale: 0.985,
474
+ transformOrigin: opensUpward ? 'bottom left' : 'top right'
475
+ }, {
476
+ autoAlpha: 1,
477
+ y: 0,
478
+ scale: 1,
479
+ duration: 0.18,
480
+ ease: 'power2.out',
481
+ overwrite: 'auto',
482
+ clearProps: 'opacity,visibility,transform,transformOrigin,willChange'
483
+ })
484
+ }
485
+
486
+ function activate(node) {
487
+ if (!node || reduced || startedRecently(node, 90)) return
488
+ const target = node.querySelector('.nav-home-orb, .nav-ico, .ds-nav-ico') || node
489
+ gsap.killTweensOf(target)
490
+ gsap.fromTo(target, { scale: 0.9, y: 2 }, {
491
+ scale: 1,
492
+ y: 0,
493
+ duration: 0.22,
494
+ ease: 'power2.out',
495
+ overwrite: 'auto',
496
+ clearProps: 'transform,willChange'
497
+ })
498
+ }
499
+
423
500
  function overlay(node) {
424
- if (!node) return
501
+ if (!node || startedRecently(node)) return
425
502
  const card = node.querySelector('.modal-card, .ds-modal-card, .sheet, .ds-drawer') || node
426
503
  gsap.killTweensOf([node, card])
427
504
  if (reduced) {
@@ -430,11 +507,18 @@
430
507
  }
431
508
  const isSideDrawer = card.classList.contains('ds-drawer')
432
509
  const isSheet = card.classList.contains('sheet')
510
+ if (card === node) {
511
+ gsap.fromTo(card,
512
+ { autoAlpha: 0, x: isSideDrawer ? 28 : 0, y: isSheet ? 24 : 8, scale: isSideDrawer || isSheet ? 1 : 0.985 },
513
+ { autoAlpha: 1, x: 0, y: 0, scale: 1, duration: isSheet ? 0.24 : 0.2, ease: 'power3.out', overwrite: 'auto', clearProps: 'opacity,visibility,transform,willChange' }
514
+ )
515
+ return
516
+ }
433
517
  const tl = gsap.timeline({ defaults: { ease: 'power3.out' } })
434
- tl.fromTo(node, { autoAlpha: 0 }, { autoAlpha: 1, duration: 0.16 })
518
+ tl.fromTo(node, { autoAlpha: 0 }, { autoAlpha: 1, duration: 0.14, overwrite: 'auto', clearProps: 'opacity,visibility' })
435
519
  .fromTo(card,
436
- { autoAlpha: 0, x: isSideDrawer ? 28 : 0, y: isSheet ? 24 : 8, scale: isSideDrawer || isSheet ? 1 : 0.985 },
437
- { autoAlpha: 1, x: 0, y: 0, scale: 1, duration: isSheet ? 0.24 : 0.2, clearProps: 'transform' },
520
+ { x: isSideDrawer ? 28 : 0, y: isSheet ? 24 : 8, scale: isSideDrawer || isSheet ? 1 : 0.985 },
521
+ { x: 0, y: 0, scale: 1, duration: isSheet ? 0.24 : 0.2, overwrite: 'auto', clearProps: 'transform,willChange' },
438
522
  '<'
439
523
  )
440
524
  }
@@ -449,10 +533,10 @@
449
533
  }
450
534
  }
451
535
 
452
- window.DshMotion = { gsap, reduced: () => reduced, view, list, relayout, bindLongPressReorder, overlay, pulse }
536
+ window.DshMotion = { gsap, reduced: () => reduced, view, list, relayout, bindLongPressReorder, overlay, panel, popover, activate, pulse }
453
537
 
454
538
  document.addEventListener('DOMContentLoaded', () => {
455
- document.querySelectorAll('.view:not(.hidden), .ds-view:not(.hidden)').forEach(node => view(node))
539
+ document.querySelectorAll('.view:not(.hidden), .ds-view:not(.hidden), [data-motion-view]:not(.hidden)').forEach(node => view(node))
456
540
  }, { once: true })
457
541
 
458
542
  const observer = new MutationObserver(records => {
@@ -460,9 +544,13 @@
460
544
  const node = record.target
461
545
  if (!(node instanceof HTMLElement)) continue
462
546
  const becameVisible = record.oldValue?.split(/\s+/).includes('hidden') && !node.classList.contains('hidden')
547
+ const becameActive = !record.oldValue?.split(/\s+/).includes('active') && node.classList.contains('active')
548
+ if (becameActive && node.matches('.nav-btn, .ds-nav-item')) activate(node)
463
549
  if (!becameVisible) continue
464
- if (node.matches('.view, .ds-view')) view(node)
465
- else if (node.matches('.modal, .ds-modal, .sheet, .ds-drawer')) overlay(node)
550
+ if (node.matches('.view, .ds-view, [data-motion-view]')) view(node)
551
+ else if (node.matches('.modal, .ds-modal, .sheet-backdrop, .sheet, .ds-drawer')) overlay(node)
552
+ else if (node.matches('.composer-menu, .ds-group-menu, .ds-feedback-menu, .fb-menu')) popover(node)
553
+ else if (node.matches('[data-motion-panel], .wb-panel, .ds-wb-panel, .doctor-body, .settings-subpage')) panel(node)
466
554
  }
467
555
  })
468
556
  observer.observe(document.documentElement, { subtree: true, attributes: true, attributeFilter: ['class'], attributeOldValue: true })
@@ -4,19 +4,19 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
6
  <title>DSH Remote</title>
7
- <script>/* 插件抽屉首帧沿用当前主题 */ (function(){try{var t=localStorage.getItem('dshTheme');if(t!=='default'&&t!=='dark'&&t!=='light'&&t!=='neutral'){t=window.matchMedia('(prefers-color-scheme: light)').matches?'light':'default'}document.documentElement.setAttribute('data-theme',t)}catch(e){}})()</script>
7
+ <script>/* 插件抽屉首帧沿用当前主题 */ (function(){try{var t=localStorage.getItem('dshTheme');if(t!=='default'&&t!=='dark'&&t!=='light'&&t!=='neutral'&&t!=='mono'){t=window.matchMedia('(prefers-color-scheme: light)').matches?'light':'default'}document.documentElement.setAttribute('data-theme',t)}catch(e){}})()</script>
8
8
  <link rel="stylesheet" href="theme-vars.css">
9
9
  <style>
10
10
  :root { color-scheme: dark; }
11
11
  html[data-theme="light"], html[data-theme="neutral"] { color-scheme: light; }
12
- * { box-sizing: border-box; }
12
+ * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
13
13
  body {
14
14
  margin: 0; min-width: 280px; color: var(--dsr-text); background:
15
15
  radial-gradient(420px 240px at 100% 0, var(--dsr-accent-soft), transparent 72%),
16
16
  var(--dsr-bg); font: 13px/1.45 system-ui, -apple-system, "PingFang SC", "Noto Sans CJK SC", sans-serif;
17
17
  }
18
18
  button, a { font: inherit; }
19
- button { cursor: pointer; }
19
+ button { cursor: pointer; touch-action: manipulation; }
20
20
  .plugin-shell { min-height: 100vh; padding: 18px 16px 16px; display: flex; flex-direction: column; gap: 14px; }
21
21
  .plugin-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
22
22
  .plugin-brand { display: flex; align-items: center; gap: 9px; min-width: 0; }
@@ -41,8 +41,9 @@
41
41
  .plugin-primary:hover, .plugin-primary:focus-visible { filter: brightness(1.08); transform: translateY(-1px); }
42
42
  .plugin-primary:disabled { cursor: wait; opacity: .65; transform: none; }
43
43
  .plugin-actions { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
44
- .plugin-action { min-height: 42px; padding: 7px 5px; border: 1px solid var(--dsr-line); border-radius: 12px; background: var(--dsr-panel); color: var(--dsr-text); font-size: 11px; }
44
+ .plugin-action { min-height: 42px; padding: 7px 5px; border: 1px solid var(--dsr-line); border-radius: 12px; background: var(--dsr-panel); color: var(--dsr-text); font-size: 11px; transition: transform .18s ease, background-color .18s ease, border-color .18s ease; }
45
45
  .plugin-action:hover, .plugin-action:focus-visible { border-color: var(--dsr-accent-line); background: var(--dsr-accent-soft); }
46
+ .plugin-action:active { transform: scale(.98); }
46
47
  .plugin-action svg { display: block; width: 17px; height: 17px; margin: 0 auto 3px; color: var(--dsr-accent-strong); }
47
48
  .plugin-usage { padding: 14px; border: 1px solid var(--dsr-line); border-radius: 16px; background: linear-gradient(145deg, var(--dsr-panel), var(--dsr-bg-2)); }
48
49
  .plugin-section-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; color: var(--dsr-text); font-size: 12px; font-weight: 750; }
@@ -87,7 +88,7 @@
87
88
  </style>
88
89
  </head>
89
90
  <body>
90
- <main class="plugin-shell">
91
+ <main class="plugin-shell" data-motion-view>
91
92
  <header class="plugin-head">
92
93
  <div class="plugin-brand">
93
94
  <span class="plugin-brand-mark" aria-hidden="true"></span>