dsh-desktop-kit 0.0.0-stage → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -2
  3. package/client.js +4101 -0
  4. package/cordis.patch.yml +5 -0
  5. package/docs/ARCHITECTURE.md +114 -0
  6. package/docs/COMPATIBILITY.md +61 -0
  7. package/docs/INSTALL.md +84 -0
  8. package/docs/PRIVACY.md +50 -0
  9. package/docs/ROADMAP.md +57 -0
  10. package/docs/TOKENS.md +118 -0
  11. package/docs/TROUBLESHOOTING.md +68 -0
  12. package/docs/images/hero.png +0 -0
  13. package/index.js +84 -0
  14. package/package.json +70 -4
  15. package/plugins/dsh-gkb-ru-ui/cordis.patch.yml +6 -0
  16. package/plugins/dsh-gkb-ru-ui/lib/client.js +1331 -0
  17. package/plugins/dsh-gkb-ru-ui/lib/index.js +186 -0
  18. package/plugins/dsh-gkb-ru-ui/lib/skills.js +570 -0
  19. package/plugins/dsh-gkb-ru-ui/package.json +28 -0
  20. package/plugins/dsh-gkb-ru-ui/screenshots/01-home-tiles-1440-light.png +0 -0
  21. package/plugins/dsh-gkb-ru-ui/screenshots/02-skills-1440-light.png +0 -0
  22. package/plugins/dsh-gkb-ru-ui/screenshots/03-skills-editor-1440-light.png +0 -0
  23. package/plugins/dsh-gkb-ru-ui/screenshots/04-skills-1440-dark.png +0 -0
  24. package/plugins/dsh-gkb-ru-ui/screenshots/05-skills-390-light.png +0 -0
  25. package/plugins/dsh-gkb-ru-ui/screenshots/06-home-tiles-1440-dark.png +0 -0
  26. package/plugins/dsh-peak-guard/README.md +70 -0
  27. package/plugins/dsh-peak-guard/client.js +741 -0
  28. package/plugins/dsh-peak-guard/cordis.patch.yml +5 -0
  29. package/plugins/dsh-peak-guard/icon.svg +12 -0
  30. package/plugins/dsh-peak-guard/index.js +2 -0
  31. package/plugins/dsh-peak-guard/locale/en.json +6 -0
  32. package/plugins/dsh-peak-guard/locale/ru.json +6 -0
  33. package/plugins/dsh-peak-guard/locale/zh.json +6 -0
  34. package/plugins/dsh-peak-guard/package.json +33 -0
  35. package/plugins/dsh-peak-guard/screenshots/01-real-time-peak-start.png +0 -0
  36. package/plugins/dsh-peak-guard/screenshots/02-real-time-peak-end.png +0 -0
  37. package/plugins/dsh-peak-guard/screenshots/03-peak-card-1440-light.png +0 -0
  38. package/plugins/dsh-peak-guard/screenshots/04-peak-card-1440-dark.png +0 -0
  39. package/plugins/dsh-peak-guard/screenshots/05-peak-card-390-light.png +0 -0
  40. package/plugins/dsh-peak-guard/screenshots/05-peak-card-768-light.png +0 -0
  41. package/plugins/dsh-peak-guard/screenshots/06-peak-card-390-light.png +0 -0
  42. package/plugins/dsh-peak-guard/screenshots/07-live-preview-check.png +0 -0
  43. package/plugins/dsh-peak-guard/screenshots/07-pre-peak-1440-light.png +0 -0
  44. package/plugins/dsh-peak-guard/screenshots/08-peak-start-1440-light.png +0 -0
  45. package/plugins/dsh-peak-guard/screenshots/09-peak-end-1440-light.png +0 -0
  46. package/plugins/dsh-peak-guard/screenshots/10-pre-peak-card-1440-light.png +0 -0
  47. package/plugins/dsh-peak-guard/screenshots/11-pre-peak-card-1440-dark.png +0 -0
  48. package/plugins/dsh-peak-guard/screenshots/12-pre-peak-card-390-light.png +0 -0
  49. package/plugins/dsh-peak-guard/screenshots/qa-report.json +74 -0
  50. package/plugins/dsh-profile-transfer/README.md +62 -0
  51. package/plugins/dsh-profile-transfer/bootstrap/01-INSTALL.cmd +7 -0
  52. package/plugins/dsh-profile-transfer/bootstrap/README-ARCHIVE.md +54 -0
  53. package/plugins/dsh-profile-transfer/bootstrap/README-/320/243/320/241/320/242/320/220/320/235/320/236/320/222/320/232/320/220.md +42 -0
  54. package/plugins/dsh-profile-transfer/bootstrap/install-on-clean-machine.ps1 +119 -0
  55. package/plugins/dsh-profile-transfer/bootstrap//320/243/320/241/320/242/320/220/320/235/320/236/320/222/320/230/320/242/320/254.cmd +5 -0
  56. package/plugins/dsh-profile-transfer/client.js +806 -0
  57. package/plugins/dsh-profile-transfer/cordis.patch.yml +3 -0
  58. package/plugins/dsh-profile-transfer/icon.svg +13 -0
  59. package/plugins/dsh-profile-transfer/index.js +375 -0
  60. package/plugins/dsh-profile-transfer/locale/en.json +6 -0
  61. package/plugins/dsh-profile-transfer/locale/ru.json +6 -0
  62. package/plugins/dsh-profile-transfer/locale/zh.json +6 -0
  63. package/plugins/dsh-profile-transfer/package.json +36 -0
  64. package/plugins/dsh-profile-transfer/screenshots/01-settings-transfer-1440-light.png +0 -0
  65. package/plugins/dsh-profile-transfer/screenshots/02-settings-transfer-preview-1440-light.png +0 -0
  66. package/plugins/dsh-profile-transfer/screenshots/03-settings-transfer-1440-dark.png +0 -0
  67. package/plugins/dsh-profile-transfer/screenshots/04-settings-transfer-390-light.png +0 -0
  68. package/plugins/dsh-profile-transfer/screenshots/05-settings-transfer-768-light.png +0 -0
  69. package/plugins/dsh-profile-transfer/screenshots/06-settings-transfer-390-dark.png +0 -0
  70. package/plugins/dsh-profile-transfer/screenshots/07-settings-transfer-export-result.png +0 -0
  71. package/plugins/dsh-profile-transfer/screenshots/08-settings-transfer-workspaces-1440-light.png +0 -0
  72. package/plugins/dsh-profile-transfer/screenshots/09-settings-transfer-workspaces-1440-dark.png +0 -0
  73. package/plugins/dsh-profile-transfer/screenshots/10-settings-transfer-workspaces-390-light.png +0 -0
  74. package/plugins/dsh-profile-transfer/transfer.js +1004 -0
  75. package/plugins/dsh-project-memory/README.md +71 -0
  76. package/plugins/dsh-project-memory/cordis.patch.yml +4 -0
  77. package/plugins/dsh-project-memory/icon.svg +12 -0
  78. package/plugins/dsh-project-memory/index.js +1240 -0
  79. package/plugins/dsh-project-memory/locale/en.json +6 -0
  80. package/plugins/dsh-project-memory/locale/ru.json +6 -0
  81. package/plugins/dsh-project-memory/locale/zh.json +6 -0
  82. package/plugins/dsh-project-memory/package.json +26 -0
  83. package/plugins/dsh-session-move/README.md +67 -0
  84. package/plugins/dsh-session-move/client.js +719 -0
  85. package/plugins/dsh-session-move/cordis.patch.yml +5 -0
  86. package/plugins/dsh-session-move/icon.svg +14 -0
  87. package/plugins/dsh-session-move/index.js +497 -0
  88. package/plugins/dsh-session-move/locale/en.json +6 -0
  89. package/plugins/dsh-session-move/locale/ru.json +6 -0
  90. package/plugins/dsh-session-move/locale/zh.json +6 -0
  91. package/plugins/dsh-session-move/package.json +31 -0
  92. package/plugins/dsh-session-move/screenshots/01-row-menu-1440-light.png +0 -0
  93. package/plugins/dsh-session-move/screenshots/02-dialog-1440-light.png +0 -0
  94. package/plugins/dsh-session-move/screenshots/03-dialog-390-light.png +0 -0
  95. package/plugins/dsh-session-move/screenshots/04-dialog-1440-dark.png +0 -0
  96. package/plugins/dsh-session-move/screenshots/05-drag-highlight-1440-light.png +0 -0
  97. package/plugins/dsh-session-move/screenshots/06-drop-ungrouped-highlight.png +0 -0
  98. package/plugins/dsh-session-move/screenshots/07-after-drop-ungrouped.png +0 -0
  99. package/plugins/dsh-session-move/screenshots/08-move-dialog.png +0 -0
  100. package/plugins/dsh-session-move/screenshots/09-moved-to-second-workspace.png +0 -0
  101. package/plugins/dsh-session-move/screenshots/qa-report.json +62 -0
  102. package/plugins/dsh-skill-autopilot/cordis.patch.yml +3 -0
  103. package/plugins/dsh-skill-autopilot/index.js +444 -0
  104. package/plugins/dsh-skill-autopilot/package.json +19 -0
  105. package/plugins/dsh-token-lab/cordis.patch.yml +3 -0
  106. package/plugins/dsh-token-lab/lib/client.js +504 -0
  107. package/plugins/dsh-token-lab/lib/index.js +275 -0
  108. package/plugins/dsh-token-lab/package.json +26 -0
  109. package/plugins/dsh-token-lab/screenshots/01-token-lab-1440-light.png +0 -0
  110. package/plugins/dsh-token-lab/screenshots/02-token-lab-1440-dark.png +0 -0
  111. package/skills/code-review-checklist/SKILL.md +55 -0
  112. package/skills/content-writing/README.md +87 -0
  113. package/skills/content-writing/SKILL.md +85 -0
  114. package/skills/content-writing/assets/checklists/fact-check.md +53 -0
  115. package/skills/content-writing/assets/checklists/publish.md +65 -0
  116. package/skills/content-writing/assets/checklists/social.md +40 -0
  117. package/skills/content-writing/assets/templates/article.md +47 -0
  118. package/skills/content-writing/assets/templates/brief.md +44 -0
  119. package/skills/content-writing/assets/templates/doctor-card.md +41 -0
  120. package/skills/content-writing/assets/templates/image-prompts.md +128 -0
  121. package/skills/content-writing/assets/templates/post-telegram.md +36 -0
  122. package/skills/content-writing/assets/templates/post-vk.md +42 -0
  123. package/skills/content-writing/assets/templates/press-release.md +56 -0
  124. package/skills/content-writing/assets/templates/science-pop-note.md +55 -0
  125. package/skills/content-writing/assets/templates/service-card.md +46 -0
  126. package/skills/content-writing/references/anti-ai-style.md +248 -0
  127. package/skills/content-writing/references/editing.md +185 -0
  128. package/skills/content-writing/references/fact-checking.md +205 -0
  129. package/skills/content-writing/references/government-medical.md +246 -0
  130. package/skills/content-writing/references/images.md +212 -0
  131. package/skills/content-writing/references/journalism.md +252 -0
  132. package/skills/content-writing/references/process.md +123 -0
  133. package/skills/content-writing/references/quick-recipes.md +99 -0
  134. package/skills/content-writing/references/rules.md +85 -0
  135. package/skills/content-writing/references/science-popular.md +218 -0
  136. package/skills/content-writing/references/seo-content.md +172 -0
  137. package/skills/content-writing/references/social-media.md +253 -0
  138. package/skills/content-writing/references/sources.md +49 -0
  139. package/skills/content-writing/references/templates.md +291 -0
  140. package/skills/context-economy/SKILL.md +60 -0
  141. package/skills/context-economy/references/long-session.md +35 -0
  142. package/skills/context-economy/references/token-math.md +42 -0
  143. package/skills/context-economy/references/tool-recipes.md +34 -0
  144. package/skills/database-safety/SKILL.md +47 -0
  145. package/skills/debugging-playbook/SKILL.md +42 -0
  146. package/skills/design-critique/SKILL.md +65 -0
  147. package/skills/design-critique/references/a11y.md +25 -0
  148. package/skills/design-critique/references/diagnoses.md +33 -0
  149. package/skills/design-critique/references/quick-tests.md +30 -0
  150. package/skills/design-critique/references/report-template.md +63 -0
  151. package/skills/design-critique/references/rubric-fast.md +73 -0
  152. package/skills/dev-workflow/SKILL.md +48 -0
  153. package/skills/dev-workflow/references/definition-of-done.md +44 -0
  154. package/skills/dev-workflow/references/memory-and-boundaries.md +31 -0
  155. package/skills/dev-workflow/references/stages.md +67 -0
  156. package/skills/git-and-ci/SKILL.md +60 -0
  157. package/skills/pg-upgrade-verify/SKILL.md +67 -0
  158. package/skills/project-deep-dive/SKILL.md +45 -0
  159. package/skills/project-deep-dive/references/recon-commands.md +90 -0
  160. package/skills/project-deep-dive/references/section-templates.md +104 -0
  161. package/skills/project-deep-dive/references/tool-and-process.md +86 -0
  162. package/skills/project-memory-maintenance/SKILL.md +55 -0
  163. package/skills/project-memory-maintenance/references/api-and-checklist.md +99 -0
  164. package/skills/project-memory-maintenance/references/journal-template.md +50 -0
  165. package/skills/project-memory-maintenance/references/section-rules.md +59 -0
  166. package/skills/release-and-deploy/SKILL.md +53 -0
  167. package/skills/requirements-and-planning/SKILL.md +49 -0
  168. package/skills/requirements-and-planning/references/antipatterns-and-checklist.md +20 -0
  169. package/skills/requirements-and-planning/references/process-steps.md +52 -0
  170. package/skills/requirements-and-planning/references/templates-and-estimates.md +29 -0
  171. package/skills/security-review/SKILL.md +40 -0
  172. package/skills/seo/README.md +60 -0
  173. package/skills/seo/SKILL.md +77 -0
  174. package/skills/seo/assets/README.md +66 -0
  175. package/skills/seo/assets/scripts/LICENSE-playwright-core.txt +202 -0
  176. package/skills/seo/assets/scripts/README.md +179 -0
  177. package/skills/seo/assets/scripts/lib/args.mjs +105 -0
  178. package/skills/seo/assets/scripts/lib/browser.mjs +620 -0
  179. package/skills/seo/assets/scripts/lib/zip.mjs +191 -0
  180. package/skills/seo/assets/scripts/seo-audit.mjs +1675 -0
  181. package/skills/seo/assets/snippets/jsonld-breadcrumb.json +30 -0
  182. package/skills/seo/assets/snippets/jsonld-faq.json +39 -0
  183. package/skills/seo/assets/snippets/jsonld-medical-clinic.json +49 -0
  184. package/skills/seo/assets/snippets/jsonld-medical-webpage.json +33 -0
  185. package/skills/seo/assets/snippets/jsonld-organization.json +74 -0
  186. package/skills/seo/assets/snippets/jsonld-physician.json +46 -0
  187. package/skills/seo/assets/snippets/meta.blade.php +61 -0
  188. package/skills/seo/assets/snippets/robots.txt +61 -0
  189. package/skills/seo/assets/snippets/sitemap-urlset-example.xml +72 -0
  190. package/skills/seo/assets/snippets/sitemap.xml +36 -0
  191. package/skills/seo/references/antipatterns.md +66 -0
  192. package/skills/seo/references/audit-checklist.md +319 -0
  193. package/skills/seo/references/content-seo.md +204 -0
  194. package/skills/seo/references/eeat-medical.md +219 -0
  195. package/skills/seo/references/measurement.md +250 -0
  196. package/skills/seo/references/on-page.md +214 -0
  197. package/skills/seo/references/page-templates.md +56 -0
  198. package/skills/seo/references/stack-recipes.md +695 -0
  199. package/skills/seo/references/structured-data.md +487 -0
  200. package/skills/seo/references/technical-seo.md +296 -0
  201. package/skills/service-desk-conventions/SKILL.md +52 -0
  202. package/skills/service-desk-db-safety/SKILL.md +63 -0
  203. package/skills/service-desk-release/SKILL.md +61 -0
  204. package/skills/service-desk-testing/SKILL.md +61 -0
  205. package/skills/service-desk-ui/SKILL.md +52 -0
  206. package/skills/skills-router/SKILL.md +53 -0
  207. package/skills/skills-router/references/applying-skills.md +25 -0
  208. package/skills/skills-router/references/routing-matrix.md +52 -0
  209. package/skills/testing-strategy/SKILL.md +52 -0
  210. package/skills/ui-design-system/SKILL.md +50 -0
  211. package/skills/ui-design-system/references/accessibility-and-responsive.md +50 -0
  212. package/skills/ui-design-system/references/tokens-and-components.md +67 -0
  213. package/skills/ui-design-system/references/visual-qa.md +44 -0
  214. package/skills/web-research/SKILL.md +47 -0
  215. package/skills/web-research/references/answer-and-citations.md +60 -0
  216. package/skills/web-research/references/query-recipes.md +44 -0
  217. package/skills/wow-design/SKILL.md +89 -0
  218. package/skills/wow-design/assets/kit/README.md +195 -0
  219. package/skills/wow-design/assets/kit/assets/css/base.css +158 -0
  220. package/skills/wow-design/assets/kit/assets/css/components.css +1001 -0
  221. package/skills/wow-design/assets/kit/assets/css/fonts.css +73 -0
  222. package/skills/wow-design/assets/kit/assets/css/pages.css +737 -0
  223. package/skills/wow-design/assets/kit/assets/css/tokens.css +218 -0
  224. package/skills/wow-design/assets/kit/assets/fonts/LICENSE-golos-text.txt +93 -0
  225. package/skills/wow-design/assets/kit/assets/fonts/LICENSE-lora.txt +93 -0
  226. package/skills/wow-design/assets/kit/assets/fonts/LICENSE-unbounded.txt +93 -0
  227. package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-400-normal.woff2 +0 -0
  228. package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-500-normal.woff2 +0 -0
  229. package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-600-normal.woff2 +0 -0
  230. package/skills/wow-design/assets/kit/assets/fonts/golos-text-cyrillic-700-normal.woff2 +0 -0
  231. package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-400-normal.woff2 +0 -0
  232. package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-500-normal.woff2 +0 -0
  233. package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-600-normal.woff2 +0 -0
  234. package/skills/wow-design/assets/kit/assets/fonts/golos-text-latin-700-normal.woff2 +0 -0
  235. package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-400-italic.woff2 +0 -0
  236. package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-400-normal.woff2 +0 -0
  237. package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-500-normal.woff2 +0 -0
  238. package/skills/wow-design/assets/kit/assets/fonts/lora-cyrillic-600-normal.woff2 +0 -0
  239. package/skills/wow-design/assets/kit/assets/fonts/lora-latin-400-normal.woff2 +0 -0
  240. package/skills/wow-design/assets/kit/assets/fonts/lora-latin-500-normal.woff2 +0 -0
  241. package/skills/wow-design/assets/kit/assets/fonts/lora-latin-600-normal.woff2 +0 -0
  242. package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-400-normal.woff2 +0 -0
  243. package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-500-normal.woff2 +0 -0
  244. package/skills/wow-design/assets/kit/assets/fonts/unbounded-cyrillic-600-normal.woff2 +0 -0
  245. package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-400-normal.woff2 +0 -0
  246. package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-500-normal.woff2 +0 -0
  247. package/skills/wow-design/assets/kit/assets/fonts/unbounded-latin-600-normal.woff2 +0 -0
  248. package/skills/wow-design/assets/kit/assets/js/kit.js +550 -0
  249. package/skills/wow-design/assets/kit/pages/dashboard.html +539 -0
  250. package/skills/wow-design/assets/kit/pages/hospital.html +430 -0
  251. package/skills/wow-design/assets/kit/showcase.html +1164 -0
  252. package/skills/wow-design/assets/scripts/LICENSE-axe-core.txt +362 -0
  253. package/skills/wow-design/assets/scripts/LICENSE-playwright-core.txt +202 -0
  254. package/skills/wow-design/assets/scripts/README.md +312 -0
  255. package/skills/wow-design/assets/scripts/audit.mjs +1200 -0
  256. package/skills/wow-design/assets/scripts/capture.mjs +337 -0
  257. package/skills/wow-design/assets/scripts/package.json +30 -0
  258. package/skills/wow-design/references/anti-patterns.md +252 -0
  259. package/skills/wow-design/references/critique-rubric.md +262 -0
  260. package/skills/wow-design/references/design-brief-template.md +48 -0
  261. package/skills/wow-design/references/inspiration.md +203 -0
  262. package/skills/wow-design/references/presets.md +99 -0
  263. package/skills/wow-design/references/principles.md +371 -0
  264. package/skills/wow-design/references/quick-presets.md +50 -0
  265. package/skills/wow-design/references/report-template.md +21 -0
  266. package/skills/wow-design/references/stack-recipes.md +96 -0
  267. package/skills/wow-design/references/visual-qa.md +82 -0
  268. package/skills/wow-design/references/wow-levers.md +43 -0
  269. package/tools/skill-cost.mjs +76 -0
@@ -0,0 +1,741 @@
1
+ /**
2
+ * dsh-peak-guard — browser half.
3
+ *
4
+ * A live, dismissible peak-pricing notice in the chat.
5
+ *
6
+ * • 5 minutes before a peak window starts it warns that the 2× price is coming;
7
+ * • during the window it explains the price, shows the window, a live
8
+ * countdown with a depleting ring, and when to come back;
9
+ * • it only informs — it never stops the chat, closes a session or aborts a
10
+ * turn.
11
+ *
12
+ * Real-time: 1s ticks, exact boundary timers (warning start, peak start, peak
13
+ * end) and wake listeners on focus/visibility/pointer.
14
+ *
15
+ * Schedule (UTC, weekdays): 01:00–04:00 and 06:00–10:00; everything else is
16
+ * off-peak with a 50% discount. Weekends have no peak.
17
+ */
18
+
19
+ window.__ModuleLoader__.load({
20
+ id: "dsh-peak-guard",
21
+ factory: (require) => {
22
+ var module = { exports: {} };
23
+ var exports = module.exports;
24
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
+ const React = require("react");
26
+ const h = React.createElement;
27
+ const useEffect = React.useEffect;
28
+ const useState = React.useState;
29
+ const useSyncExternalStore = React.useSyncExternalStore;
30
+
31
+ const NS = "peak-guard";
32
+ const WINDOWS_UTC = [
33
+ { from: 1, to: 4, id: "morning" },
34
+ { from: 6, to: 10, id: "day" },
35
+ ];
36
+ const TICK_MS = 1000;
37
+ const PEAK_LEAD_MS = 5 * 60 * 1000;
38
+ const DISMISS_KEY = "dsh-peak-guard:dismissed-window-end";
39
+ const SOON_DISMISS_KEY = "dsh-peak-guard:dismissed-soon-start";
40
+ const ENABLED_KEY = "dsh-peak-guard:enabled";
41
+ const RING_RADIUS = 26;
42
+ const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;
43
+
44
+ const EN = {
45
+ badgeLabel: "Peak ×2",
46
+ badgeTitle: "Peak pricing is active now: tokens cost 2×. {wait} left in this window.",
47
+ soonBadgeLabel: "Peak in {wait}",
48
+ nowLabel: "Now",
49
+ kicker: "Peak pricing",
50
+ soonKicker: "Peak coming",
51
+ multiplier: "×2",
52
+ title: "Tokens cost double right now",
53
+ soonTitle: "Peak pricing starts in {wait}",
54
+ countdownLabel: "until this peak window ends",
55
+ soonCountdownLabel: "until peak pricing starts",
56
+ unitHour: "h",
57
+ unitMin: "min",
58
+ unitSec: "s",
59
+ windowLabel: "Window",
60
+ soonWindowLabel: "Upcoming window",
61
+ windowRange: "{start}–{end} {zone}",
62
+ reason: "During peak hours every token type costs twice as much for deepseek-flash and deepseek-v4-pro. Outside these hours the off-peak 50% discount applies.",
63
+ soonReason: "Off-peak pricing is still active: the 50% discount holds until the window starts. After that, tokens are billed at 2× for the whole peak window.",
64
+ recommendationTitle: "Best time to come back",
65
+ soonRecommendationTitle: "Before the price doubles",
66
+ recommendation: "Return after {time} — in {wait} the token price drops by half.",
67
+ soonRecommendation: "Finish the current step before {start}, or come back after {end} — the peak window lasts until then.",
68
+ scheduleTitle: "Peak schedule (weekdays)",
69
+ scheduleWeekend: "weekdays · weekends have no peak",
70
+ scheduleChipLabel: "Peak windows",
71
+ scheduleUtc: "UTC: {windows}",
72
+ scheduleLocal: "Your time ({zone}): {windows}",
73
+ holidayNote: "Chinese public holidays are off-peak too; only regular weekdays are shown.",
74
+ dismiss: "Hide for this peak",
75
+ soonDismiss: "Hide until the peak starts",
76
+ details: "Schedule",
77
+ hideDetails: "Collapse",
78
+ waitHours: "{hours} h {minutes} min",
79
+ waitMinutes: "{minutes} min",
80
+ waitSeconds: "{seconds} s",
81
+ };
82
+ const RU = {
83
+ badgeLabel: "Пик ×2",
84
+ badgeTitle: "Сейчас пиковый тариф: токены ×2. До конца окна осталось {wait}.",
85
+ soonBadgeLabel: "Пик через {wait}",
86
+ nowLabel: "Сейчас",
87
+ kicker: "Пиковый тариф",
88
+ soonKicker: "Скоро пик",
89
+ multiplier: "×2",
90
+ title: "Токены сейчас стоят вдвое дороже",
91
+ soonTitle: "Пиковый тариф начнётся через {wait}",
92
+ countdownLabel: "до конца пикового окна",
93
+ soonCountdownLabel: "до начала пика",
94
+ unitHour: "ч",
95
+ unitMin: "мин",
96
+ unitSec: "с",
97
+ windowLabel: "Окно",
98
+ soonWindowLabel: "Окно начнётся",
99
+ windowRange: "{start}–{end} {zone}",
100
+ reason: "В пиковые часы стоимость всех типов токенов для deepseek-flash и deepseek-v4-pro увеличивается вдвое. Вне этих часов действует скидка 50% (off-peak).",
101
+ soonReason: "Сейчас ещё действует off-peak со скидкой 50%. Когда начнётся окно, все типы токенов будут стоить ×2 до самого конца пика.",
102
+ recommendationTitle: "Лучшее время вернуться",
103
+ soonRecommendationTitle: "До подорожания",
104
+ recommendation: "Вернитесь после {time} — через {wait} цена токенов упадёт вдвое.",
105
+ soonRecommendation: "Завершите текущий шаг до {start} — или вернитесь после {end}, когда пик закончится.",
106
+ scheduleTitle: "Расписание пика (будни)",
107
+ scheduleWeekend: "будни · выходные без пика",
108
+ scheduleChipLabel: "Окна пика",
109
+ scheduleUtc: "UTC: {windows}",
110
+ scheduleLocal: "По вашему времени ({zone}): {windows}",
111
+ holidayNote: "Государственные праздники Китая тоже считаются off-peak — выше только обычные будни.",
112
+ dismiss: "Скрыть на этот пик",
113
+ soonDismiss: "Скрыть до начала пика",
114
+ details: "Расписание",
115
+ hideDetails: "Свернуть",
116
+ waitHours: "{hours} ч {minutes} мин",
117
+ waitMinutes: "{minutes} мин",
118
+ waitSeconds: "{seconds} с",
119
+ };
120
+ const ZH = {
121
+ badgeLabel: "高峰 ×2",
122
+ badgeTitle: "当前为高峰时段:Token 价格 ×2。本时段还剩 {wait}。",
123
+ soonBadgeLabel: "高峰还剩 {wait}",
124
+ nowLabel: "现在",
125
+ kicker: "高峰价格",
126
+ soonKicker: "高峰将至",
127
+ multiplier: "×2",
128
+ title: "当前 Token 价格为 2 倍",
129
+ soonTitle: "高峰价格将在 {wait} 后开始",
130
+ countdownLabel: "距本时段结束",
131
+ soonCountdownLabel: "距高峰开始",
132
+ unitHour: "小时",
133
+ unitMin: "分钟",
134
+ unitSec: "秒",
135
+ windowLabel: "时段",
136
+ soonWindowLabel: "即将开始",
137
+ windowRange: "{start}–{end} {zone}",
138
+ reason: "高峰时段内 deepseek-flash 与 deepseek-v4-pro 的全部 Token 价格翻倍;其余时段享受 50% 的错峰折扣。",
139
+ soonReason: "目前仍为错峰价格(50% 折扣)。时段开始后,全部 Token 价格将翻倍,直至本次高峰结束。",
140
+ recommendationTitle: "建议返回时间",
141
+ soonRecommendationTitle: "涨价之前",
142
+ recommendation: "请在 {time}({wait} 后)再回来 — Token 价格将减半。",
143
+ soonRecommendation: "请在 {start} 前完成当前步骤,或在 {end} 之后再回来(届时高峰结束)。",
144
+ scheduleTitle: "高峰时段(工作日)",
145
+ scheduleWeekend: "工作日 · 周末无高峰",
146
+ scheduleChipLabel: "高峰时段",
147
+ scheduleUtc: "UTC:{windows}",
148
+ scheduleLocal: "本地时间({zone}):{windows}",
149
+ holidayNote: "中国法定节假日同样按错峰计费;上表仅适用于普通工作日。",
150
+ dismiss: "本次高峰不再提示",
151
+ soonDismiss: "高峰开始前不再提示",
152
+ details: "时间表",
153
+ hideDetails: "收起",
154
+ waitHours: "{hours} 小时 {minutes} 分钟",
155
+ waitMinutes: "{minutes} 分钟",
156
+ waitSeconds: "{seconds} 秒",
157
+ };
158
+
159
+ const inject = ["slots", "locale"];
160
+
161
+ function pad(value) {
162
+ return String(value).padStart(2, "0");
163
+ }
164
+
165
+ function formatTemplate(template, params) {
166
+ return String(template).replace(/\{(\w+)\}/g, (match, name) =>
167
+ params && Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : match,
168
+ );
169
+ }
170
+
171
+ /** Render "{start}…{end}…" with React nodes inside the translated sentence. */
172
+ function renderTemplate(template, values) {
173
+ const pattern = /\{(\w+)\}/g;
174
+ const parts = [];
175
+ let lastIndex = 0;
176
+ let match;
177
+ let index = 0;
178
+ while ((match = pattern.exec(template)) !== null) {
179
+ if (match.index > lastIndex) parts.push(template.slice(lastIndex, match.index));
180
+ const value = values?.[match[1]];
181
+ parts.push(React.isValidElement(value) ? React.cloneElement(value, { key: `v-${index}` }) : value);
182
+ lastIndex = match.index + match[0].length;
183
+ index += 1;
184
+ }
185
+ if (lastIndex < template.length) parts.push(template.slice(lastIndex));
186
+ return parts;
187
+ }
188
+
189
+ function timeZoneLabel(t) {
190
+ try {
191
+ const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
192
+ if (zone === "Europe/Moscow") return t("waitSeconds") === "{seconds} с" ? "МСК" : "MSK";
193
+ return zone;
194
+ } catch {
195
+ return "local";
196
+ }
197
+ }
198
+
199
+ function formatClock(date) {
200
+ try {
201
+ return new Intl.DateTimeFormat(undefined, { hour: "2-digit", minute: "2-digit", hour12: false }).format(date);
202
+ } catch {
203
+ return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
204
+ }
205
+ }
206
+
207
+ function formatRange(start, end) {
208
+ return `${formatClock(start)}–${formatClock(end)}`;
209
+ }
210
+
211
+ /** Peak window containing `date`, or null. Weekdays are evaluated in UTC. */
212
+ function peakWindowAt(date) {
213
+ const day = date.getUTCDay();
214
+ if (day === 0 || day === 6) return null;
215
+ const minutes = date.getUTCHours() * 60 + date.getUTCMinutes() + date.getUTCSeconds() / 60;
216
+ for (const window of WINDOWS_UTC) {
217
+ if (minutes >= window.from * 60 && minutes < window.to * 60) {
218
+ const year = date.getUTCFullYear();
219
+ const month = date.getUTCMonth();
220
+ const dayOfMonth = date.getUTCDate();
221
+ return {
222
+ id: window.id,
223
+ start: Date.UTC(year, month, dayOfMonth, window.from, 0, 0),
224
+ end: Date.UTC(year, month, dayOfMonth, window.to, 0, 0),
225
+ };
226
+ }
227
+ }
228
+ return null;
229
+ }
230
+
231
+ /** Next peak window start strictly after `date`. */
232
+ function nextPeakWindow(date) {
233
+ const probe = new Date(date.getTime());
234
+ for (let index = 0; index < 8; index += 1) {
235
+ const day = probe.getUTCDay();
236
+ if (day !== 0 && day !== 6) {
237
+ for (const window of WINDOWS_UTC) {
238
+ const start = Date.UTC(probe.getUTCFullYear(), probe.getUTCMonth(), probe.getUTCDate(), window.from, 0, 0);
239
+ if (start > date.getTime()) {
240
+ return { start, end: Date.UTC(probe.getUTCFullYear(), probe.getUTCMonth(), probe.getUTCDate(), window.to, 0, 0) };
241
+ }
242
+ }
243
+ }
244
+ probe.setUTCDate(probe.getUTCDate() + 1);
245
+ probe.setUTCHours(0, 0, 0, 0);
246
+ }
247
+ return null;
248
+ }
249
+
250
+ function waitParts(ms) {
251
+ const totalMinutes = Math.max(1, Math.ceil(ms / 60000));
252
+ const hours = Math.floor(totalMinutes / 60);
253
+ const minutes = totalMinutes % 60;
254
+ return { hours, minutes, totalMinutes, ms };
255
+ }
256
+
257
+ function waitLabel(parts, t) {
258
+ if (parts.ms !== undefined && parts.ms < 60000) return formatTemplate(t("waitSeconds"), { seconds: Math.max(1, Math.ceil(parts.ms / 1000)) });
259
+ if (parts.hours > 0) return formatTemplate(t("waitHours"), { hours: parts.hours, minutes: parts.minutes });
260
+ return formatTemplate(t("waitMinutes"), { minutes: parts.minutes });
261
+ }
262
+
263
+ function scheduleStrings() {
264
+ const local = WINDOWS_UTC.map((entry) =>
265
+ formatRange(new Date(Date.UTC(2000, 0, 3, entry.from)), new Date(Date.UTC(2000, 0, 3, entry.to))),
266
+ );
267
+ const utc = WINDOWS_UTC.map((entry) => `${pad(entry.from)}:00–${pad(entry.to)}:00`);
268
+ return { local, utc, localText: local.join(", "), utcText: utc.join(", ") };
269
+ }
270
+
271
+ function buildState(now, t) {
272
+ const active = peakWindowAt(now);
273
+ const schedule = scheduleStrings();
274
+ const zone = timeZoneLabel(t);
275
+ const base = {
276
+ now,
277
+ zone,
278
+ leadMs: PEAK_LEAD_MS,
279
+ scheduleUtc: schedule.utcText,
280
+ scheduleLocal: schedule.localText,
281
+ scheduleLocalParts: schedule.local,
282
+ scheduleUtcParts: schedule.utc,
283
+ };
284
+ if (active !== null) {
285
+ const total = active.end - active.start;
286
+ const elapsed = now.getTime() - active.start;
287
+ const progress = total > 0 ? Math.min(1, Math.max(0, elapsed / total)) : 1;
288
+ const parts = waitParts(active.end - now.getTime());
289
+ return {
290
+ ...base,
291
+ mode: "peak",
292
+ inPeak: true,
293
+ soon: false,
294
+ windowStart: active.start,
295
+ windowEnd: active.end,
296
+ remainingMs: active.end - now.getTime(),
297
+ progress,
298
+ ringOffset: RING_CIRCUMFERENCE * progress,
299
+ wait: waitLabel(parts, t),
300
+ waitParts: parts,
301
+ startLabel: formatClock(new Date(active.start)),
302
+ endLabel: formatClock(new Date(active.end)),
303
+ nowLabel: formatClock(now),
304
+ };
305
+ }
306
+ const next = nextPeakWindow(now);
307
+ const startsInMs = next === null ? null : next.start - now.getTime();
308
+ if (next !== null && startsInMs !== null && startsInMs > 0 && startsInMs <= PEAK_LEAD_MS) {
309
+ const progress = Math.min(1, Math.max(0, 1 - startsInMs / PEAK_LEAD_MS));
310
+ const parts = waitParts(startsInMs);
311
+ return {
312
+ ...base,
313
+ mode: "soon",
314
+ inPeak: false,
315
+ soon: true,
316
+ windowStart: next.start,
317
+ windowEnd: next.end,
318
+ nextStart: next.start,
319
+ nextEnd: next.end,
320
+ startsInMs,
321
+ progress,
322
+ ringOffset: RING_CIRCUMFERENCE * progress,
323
+ wait: waitLabel(parts, t),
324
+ waitParts: parts,
325
+ startLabel: formatClock(new Date(next.start)),
326
+ endLabel: formatClock(new Date(next.end)),
327
+ nowLabel: formatClock(now),
328
+ };
329
+ }
330
+ return {
331
+ ...base,
332
+ mode: "off",
333
+ inPeak: false,
334
+ soon: false,
335
+ nextStart: next?.start ?? null,
336
+ nextEnd: next?.end ?? null,
337
+ startsInMs,
338
+ };
339
+ }
340
+
341
+ function createClockStore() {
342
+ let forcedNow = null;
343
+ let now = new Date();
344
+ const listeners = new Set();
345
+ const emit = () => {
346
+ for (const listener of [...listeners]) {
347
+ try {
348
+ listener();
349
+ } catch (error) {
350
+ console.error("[peak-guard] listener failed", error);
351
+ }
352
+ }
353
+ };
354
+ return {
355
+ subscribe(listener) {
356
+ listeners.add(listener);
357
+ return () => listeners.delete(listener);
358
+ },
359
+ getNow() {
360
+ return forcedNow ?? now;
361
+ },
362
+ tick() {
363
+ now = new Date();
364
+ emit();
365
+ },
366
+ force(iso) {
367
+ forcedNow = iso === null || iso === undefined ? null : new Date(iso);
368
+ emit();
369
+ },
370
+ getVersion() {
371
+ return now.getTime() + (forcedNow?.getTime?.() ?? 0);
372
+ },
373
+ };
374
+ }
375
+
376
+ function usePeakState(store, t) {
377
+ const version = useSyncExternalStore(store.subscribe, store.getVersion, store.getVersion);
378
+ return React.useMemo(() => buildState(store.getNow(), t), [version, t]);
379
+ }
380
+
381
+ function apply(ctx) {
382
+ const t = ctx.locale.bind(NS);
383
+ const store = createClockStore();
384
+
385
+ ctx.effect(() => ctx.locale.register(NS, { en: EN, zh: ZH }), "peak-guard: dictionaries");
386
+ ctx.effect(() => {
387
+ try {
388
+ return ctx.locale.register(NS, "ru", RU);
389
+ } catch {
390
+ return () => {};
391
+ }
392
+ }, "peak-guard: ru dictionary");
393
+
394
+ // Real-time clock: every second refreshes the countdown; a boundary timer
395
+ // fires exactly at the 5-minute warning start, the peak start and the peak
396
+ // end; focus/visibility/pointer wake it after background throttling.
397
+ ctx.effect(() => {
398
+ let boundaryTimer = null;
399
+ const scheduleBoundary = () => {
400
+ if (boundaryTimer !== null) clearTimeout(boundaryTimer);
401
+ const now = store.getNow();
402
+ const active = peakWindowAt(now);
403
+ const next = nextPeakWindow(now);
404
+ let boundary = null;
405
+ if (active !== null) {
406
+ boundary = active.end;
407
+ } else if (next !== null) {
408
+ const warningAt = next.start - PEAK_LEAD_MS;
409
+ boundary = warningAt > now.getTime() ? warningAt : next.start;
410
+ }
411
+ if (boundary === null) return;
412
+ const delay = Math.max(200, boundary - now.getTime() + 150);
413
+ boundaryTimer = setTimeout(() => {
414
+ boundaryTimer = null;
415
+ store.tick();
416
+ scheduleBoundary();
417
+ }, Math.min(delay, 3600000));
418
+ };
419
+ const tickNow = () => {
420
+ store.tick();
421
+ scheduleBoundary();
422
+ };
423
+ const timer = setInterval(tickNow, TICK_MS);
424
+ const onWake = () => {
425
+ if (!document.hidden) tickNow();
426
+ };
427
+ document.addEventListener("visibilitychange", onWake);
428
+ window.addEventListener("focus", onWake);
429
+ window.addEventListener("pointerdown", onWake);
430
+ scheduleBoundary();
431
+ return () => {
432
+ clearInterval(timer);
433
+ if (boundaryTimer !== null) clearTimeout(boundaryTimer);
434
+ document.removeEventListener("visibilitychange", onWake);
435
+ window.removeEventListener("focus", onWake);
436
+ window.removeEventListener("pointerdown", onWake);
437
+ };
438
+ }, "peak-guard: realtime clock");
439
+
440
+ ctx.effect(() => {
441
+ const style = document.createElement("style");
442
+ style.id = "dsh-peak-guard-style";
443
+ style.textContent = `
444
+ .dpg-card{--dpg-red:var(--dsw-alias-state-error-primary,#e5484d);--dpg-red-deep:#b4232b;--dpg-ink:var(--dsw-alias-label-primary,#111);--dpg-muted:var(--dsw-alias-label-secondary,#5b6472);position:relative;display:flex;flex-direction:column;gap:14px;margin-top:10px;padding:16px 18px 15px;border-radius:16px;border:1px solid color-mix(in srgb,var(--dpg-red) 26%,transparent);background:
445
+ radial-gradient(120% 160% at 100% 0%,color-mix(in srgb,var(--dpg-red) 14%,transparent) 0%,transparent 52%),
446
+ linear-gradient(155deg,color-mix(in srgb,var(--dpg-red) 7%,var(--dsw-alias-bg-layer-1,#fff)) 0%,var(--dsw-alias-bg-layer-1,#fff) 58%),
447
+ var(--dsw-alias-bg-layer-1,#fff);box-shadow:inset 0 1px 0 color-mix(in srgb,#fff 55%,transparent),0 20px 44px -30px color-mix(in srgb,var(--dpg-red) 70%,transparent);overflow:hidden;animation:dpg-in .34s cubic-bezier(.16,1,.3,1)}
448
+ .dpg-card[data-mode="soon"]{--dpg-red:var(--dsw-alias-state-warn-primary,#d97706);--dpg-red-deep:#98540a}
449
+ .dpg-card::before{content:"";position:absolute;left:10px;top:10px;width:13px;height:13px;border-left:1px solid color-mix(in srgb,var(--dpg-red) 55%,transparent);border-top:1px solid color-mix(in srgb,var(--dpg-red) 55%,transparent);pointer-events:none}
450
+ .dpg-card::after{content:"";position:absolute;right:10px;bottom:10px;width:13px;height:13px;border-right:1px solid color-mix(in srgb,var(--dpg-red) 55%,transparent);border-bottom:1px solid color-mix(in srgb,var(--dpg-red) 55%,transparent);pointer-events:none}
451
+ .dpg-top{display:flex;align-items:flex-start;gap:14px}
452
+ .dpg-seal{position:relative;flex:none;width:62px;height:62px;display:flex;align-items:center;justify-content:center}
453
+ .dpg-ring{position:absolute;inset:0;width:62px;height:62px;transform:rotate(-90deg)}
454
+ .dpg-ring-track{fill:none;stroke:color-mix(in srgb,var(--dpg-red) 18%,transparent);stroke-width:2.5}
455
+ .dpg-ring-fill{fill:none;stroke:var(--dpg-red);stroke-width:2.5;stroke-linecap:round;stroke-dasharray:${RING_CIRCUMFERENCE.toFixed(2)};transition:stroke-dashoffset .8s linear,stroke .3s ease}
456
+ .dpg-seal-icon{position:relative;display:flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:50%;color:#fff;background:linear-gradient(150deg,var(--dpg-red) 0%,color-mix(in srgb,var(--dpg-red) 55%,#4a070c) 100%);box-shadow:0 6px 16px -8px color-mix(in srgb,var(--dpg-red) 90%,transparent),inset 0 1px 0 rgba(255,255,255,.28)}
457
+ .dpg-card[data-mode="soon"] .dpg-seal-icon{background:linear-gradient(150deg,var(--dpg-red) 0%,color-mix(in srgb,var(--dpg-red) 55%,#3d2400) 100%)}
458
+ .dpg-seal-pulse{position:absolute;left:50%;top:50%;width:38px;height:38px;margin:-19px 0 0 -19px;border-radius:50%;border:1px solid color-mix(in srgb,var(--dpg-red) 45%,transparent);animation:dpg-ping 2.6s cubic-bezier(.2,.6,.3,1) infinite;pointer-events:none}
459
+ .dpg-copy{display:flex;flex-direction:column;gap:5px;min-width:0;flex:1}
460
+ .dpg-kicker{display:flex;align-items:center;gap:8px;font-size:10.5px;font-weight:750;letter-spacing:.16em;text-transform:uppercase;color:var(--dpg-red-deep)}
461
+ .dpg-dot{width:6px;height:6px;border-radius:50%;background:var(--dpg-red);box-shadow:0 0 0 3px color-mix(in srgb,var(--dpg-red) 16%,transparent);animation:dpg-pulse 2.4s ease-in-out infinite}
462
+ .dpg-mult{display:inline-flex;align-items:center;justify-content:center;height:17px;padding:0 6px;border-radius:5px;border:1px solid color-mix(in srgb,var(--dpg-red) 45%,transparent);background:color-mix(in srgb,var(--dpg-red) 10%,transparent);color:var(--dpg-red-deep);font-size:10px;font-weight:800;letter-spacing:.04em}
463
+ .dpg-title{font-size:15.5px;line-height:21px;font-weight:680;letter-spacing:-.01em;color:var(--dpg-ink)}
464
+ .dpg-reason{font-size:11.5px;line-height:16px;color:var(--dpg-muted);max-width:62ch}
465
+ .dpg-x{position:absolute;right:11px;top:11px;border:none;background:transparent;color:var(--dpg-muted);cursor:pointer;border-radius:8px;width:26px;height:26px;display:flex;align-items:center;justify-content:center;font-size:16px;line-height:1;z-index:2}
466
+ .dpg-x:hover{background:color-mix(in srgb,var(--dpg-red) 10%,transparent);color:var(--dpg-red-deep)}
467
+ .dpg-x:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2f7df6);outline-offset:1px}
468
+ .dpg-mid{display:flex;align-items:flex-end;gap:18px}
469
+ .dpg-count{display:flex;flex-direction:column;gap:1px;flex:none;min-width:118px}
470
+ .dpg-count-num{display:flex;align-items:baseline;gap:4px;color:var(--dpg-ink);font-variant-numeric:tabular-nums;letter-spacing:-.035em;line-height:1}
471
+ .dpg-count-num b{font-size:34px;font-weight:760;color:var(--dpg-red-deep)}
472
+ .dpg-count-num span{font-size:12px;font-weight:650;letter-spacing:.02em;color:var(--dpg-muted)}
473
+ .dpg-count-label{font-size:10px;font-weight:650;letter-spacing:.1em;text-transform:uppercase;color:var(--dpg-muted);margin-top:5px}
474
+ .dpg-meter{display:flex;flex-direction:column;gap:5px;flex:1;min-width:0;padding-bottom:5px}
475
+ .dpg-meter-head{display:flex;justify-content:space-between;align-items:baseline;gap:10px;font-size:10.5px;line-height:14px;color:var(--dpg-muted);font-variant-numeric:tabular-nums}
476
+ .dpg-meter-head b{color:var(--dpg-ink);font-weight:650}
477
+ .dpg-track{position:relative;height:8px;border-radius:3px;background:color-mix(in srgb,var(--dpg-red) 12%,transparent);overflow:hidden}
478
+ .dpg-track::after{content:"";position:absolute;inset:0;background:repeating-linear-gradient(90deg,transparent 0 13px,color-mix(in srgb,var(--dsw-alias-bg-layer-1,#fff) 88%,transparent) 13px 14px);opacity:.9;pointer-events:none}
479
+ .dpg-fill{height:100%;border-radius:3px;background:linear-gradient(90deg,color-mix(in srgb,var(--dpg-red) 72%,#ffb3b6) 0%,var(--dpg-red) 55%,var(--dpg-red-deep) 100%);transition:width .9s linear;box-shadow:0 0 12px -2px color-mix(in srgb,var(--dpg-red) 60%,transparent)}
480
+ .dpg-rec{display:flex;align-items:flex-start;gap:11px;padding:10px 12px;border-radius:11px;border:1px solid color-mix(in srgb,var(--dpg-red) 22%,transparent);background:linear-gradient(120deg,color-mix(in srgb,var(--dpg-red) 9%,transparent),transparent 70%)}
481
+ .dpg-rec-mark{flex:none;width:22px;height:22px;border-radius:7px;display:flex;align-items:center;justify-content:center;color:#fff;background:var(--dpg-red);font-size:12px;font-weight:800;box-shadow:0 4px 10px -5px color-mix(in srgb,var(--dpg-red) 90%,transparent)}
482
+ .dpg-rec-body{display:flex;flex-direction:column;gap:2px;min-width:0}
483
+ .dpg-rec-label{font-size:9.5px;font-weight:750;letter-spacing:.14em;text-transform:uppercase;color:var(--dpg-red-deep)}
484
+ .dpg-rec-text{font-size:12.5px;line-height:17px;color:var(--dpg-ink);font-weight:560}
485
+ .dpg-rec-text b{color:var(--dpg-red-deep);font-weight:760;font-variant-numeric:tabular-nums}
486
+ .dpg-tail{display:flex;align-items:flex-end;justify-content:space-between;gap:14px;flex-wrap:wrap}
487
+ .dpg-schedule{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-width:0}
488
+ .dpg-schedule-label{font-size:9.5px;font-weight:750;letter-spacing:.13em;text-transform:uppercase;color:var(--dpg-muted);margin-right:2px}
489
+ .dpg-chip{display:inline-flex;align-items:center;height:22px;padding:0 8px;border-radius:6px;border:1px solid color-mix(in srgb,var(--dpg-red) 24%,transparent);background:color-mix(in srgb,var(--dpg-red) 7%,transparent);color:var(--dpg-ink);font-size:10.5px;font-weight:650;font-variant-numeric:tabular-nums;white-space:nowrap}
490
+ .dpg-schedule-note{font-size:10px;line-height:14px;color:var(--dpg-muted)}
491
+ .dpg-actions{display:flex;gap:6px;flex:none}
492
+ .dpg-btn{border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.16));background:transparent;color:var(--dpg-ink);font:inherit;font-size:11px;font-weight:650;line-height:15px;padding:6px 11px;border-radius:8px;cursor:pointer;white-space:nowrap;transition:background .18s ease,border-color .18s ease}
493
+ .dpg-btn:hover{background:color-mix(in srgb,var(--dpg-red) 8%,transparent);border-color:color-mix(in srgb,var(--dpg-red) 38%,transparent)}
494
+ .dpg-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2f7df6);outline-offset:1px}
495
+ .dpg-btn[data-emphasis="true"]{border-color:transparent;background:var(--dpg-red-deep);color:#fff}
496
+ .dpg-btn[data-emphasis="true"]:hover{background:color-mix(in srgb,var(--dpg-red-deep) 88%,#000)}
497
+ .dpg-details{display:grid;grid-template-columns:1fr 1fr;gap:6px 18px;padding-top:2px;font-size:10.5px;line-height:15px;color:var(--dpg-muted)}
498
+ .dpg-details b{color:var(--dpg-ink);font-weight:650;font-variant-numeric:tabular-nums}
499
+ .dpg-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:999px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary,#e5484d) 42%,transparent);background:linear-gradient(120deg,color-mix(in srgb,var(--dsw-alias-state-error-primary,#e5484d) 12%,transparent),color-mix(in srgb,var(--dsw-alias-state-error-primary,#e5484d) 5%,transparent));color:var(--dsw-alias-state-error-primary,#c62828);font-size:11px;font-weight:680;line-height:14px;font-variant-numeric:tabular-nums;white-space:nowrap;cursor:default}
500
+ .dpg-badge[data-mode="soon"]{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#d97706) 45%,transparent);background:linear-gradient(120deg,color-mix(in srgb,var(--dsw-alias-state-warn-primary,#d97706) 12%,transparent),color-mix(in srgb,var(--dsw-alias-state-warn-primary,#d97706) 5%,transparent));color:var(--dsw-alias-state-warn-primary,#b45309)}
501
+ .dpg-badge .dpg-dot{width:5px;height:5px;box-shadow:none}
502
+ .dpg-badge-wait{opacity:.85}
503
+ @keyframes dpg-pulse{0%,100%{opacity:1}50%{opacity:.4}}
504
+ @keyframes dpg-ping{0%{transform:scale(.92);opacity:.7}70%,100%{transform:scale(1.5);opacity:0}}
505
+ @keyframes dpg-in{from{opacity:0;transform:translateY(7px)}to{opacity:1;transform:translateY(0)}}
506
+ @media (prefers-color-scheme:dark){
507
+ .dpg-card{--dpg-red-deep:#ff9b9f;--dpg-red:var(--dsw-alias-state-error-primary,#ff7a80);box-shadow:inset 0 1px 0 rgba(255,255,255,.06),0 24px 50px -34px rgba(0,0,0,.9)}
508
+ .dpg-card[data-mode="soon"]{--dpg-red-deep:#ffd08a;--dpg-red:var(--dsw-alias-state-warn-primary,#f5b45b)}
509
+ .dpg-seal-icon{color:#2a0709;background:linear-gradient(150deg,var(--dpg-red) 0%,color-mix(in srgb,var(--dpg-red) 55%,#7d1218) 100%)}
510
+ .dpg-card[data-mode="soon"] .dpg-seal-icon{color:#2a1500;background:linear-gradient(150deg,var(--dpg-red) 0%,color-mix(in srgb,var(--dpg-red) 55%,#4a2c00) 100%)}
511
+ .dpg-rec-mark{color:#2a0709}
512
+ .dpg-count-num b{color:var(--dpg-red)}
513
+ .dpg-kicker,.dpg-rec-label,.dpg-mult{color:var(--dpg-red)}
514
+ .dpg-btn[data-emphasis="true"]{background:var(--dpg-red);color:#2a0709}
515
+ }
516
+ @media (max-width:1100px){.dpg-badge-wait{display:none}}
517
+ @media (max-width:900px){.dpg-badge{display:none}.dpg-mid{align-items:stretch;flex-direction:column;gap:8px}.dpg-count{min-width:0;flex-direction:row;align-items:baseline;gap:8px}.dpg-count-label{margin-top:0}.dpg-meter{padding-bottom:0}.dpg-meter-head{flex-wrap:wrap;row-gap:2px}.dpg-tail{align-items:stretch;flex-direction:column}.dpg-actions{display:flex}.dpg-actions .dpg-btn{flex:1;text-align:center}.dpg-seal{width:52px;height:52px}.dpg-ring{width:52px;height:52px}.dpg-title{font-size:14.5px;line-height:20px}}
518
+ @media (max-width:520px){.dpg-card{position:fixed;left:10px;right:10px;bottom:calc(12px + env(safe-area-inset-bottom,0px));z-index:1200;max-height:78vh;overflow:auto;margin:0;padding:14px 15px 13px;gap:12px}.dpg-seal{display:none}.dpg-details{grid-template-columns:1fr}}
519
+ @media (prefers-reduced-motion:reduce){.dpg-card{animation:none}.dpg-dot,.dpg-seal-pulse{animation:none}.dpg-fill,.dpg-ring-fill{transition:none}}
520
+ `;
521
+ document.head.appendChild(style);
522
+ return () => style.remove();
523
+ }, "peak-guard: styles");
524
+
525
+ function IconBolt() {
526
+ return h("svg", { viewBox: "0 0 20 20", width: 19, height: 19, "aria-hidden": "true", focusable: "false" },
527
+ h("path", { d: "M11.2 2.6 5.8 11h4.1l-1.1 6.4 5.4-8.4H10l1.2-6.4Z", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinejoin: "round", strokeLinecap: "round" }),
528
+ );
529
+ }
530
+
531
+ function HeaderBadge() {
532
+ const state = usePeakState(store, t);
533
+ if (state.inPeak) {
534
+ return h("span", {
535
+ className: "dpg-badge",
536
+ title: formatTemplate(t("badgeTitle"), { wait: state.wait }),
537
+ "aria-label": formatTemplate(t("badgeTitle"), { wait: state.wait }),
538
+ }, h("span", { className: "dpg-dot", "aria-hidden": "true" }), t("badgeLabel"), h("span", { className: "dpg-badge-wait" }, `· ${state.wait}`));
539
+ }
540
+ if (state.soon) {
541
+ const label = formatTemplate(t("soonBadgeLabel"), { wait: state.wait });
542
+ return h("span", { className: "dpg-badge", "data-mode": "soon", title: label, "aria-label": label },
543
+ h("span", { className: "dpg-dot", "aria-hidden": "true" }), label);
544
+ }
545
+ return null;
546
+ }
547
+
548
+ function Countdown({ state }) {
549
+ const parts = state.waitParts;
550
+ if (parts.hours > 0) {
551
+ return h("div", { className: "dpg-count-num" },
552
+ h("b", null, parts.hours), h("span", null, t("unitHour")),
553
+ h("b", null, parts.minutes), h("span", null, t("unitMin")),
554
+ );
555
+ }
556
+ if (parts.totalMinutes >= 1) {
557
+ return h("div", { className: "dpg-count-num" },
558
+ h("b", null, parts.minutes), h("span", null, t("unitMin")),
559
+ );
560
+ }
561
+ return h("div", { className: "dpg-count-num" },
562
+ h("b", null, Math.max(1, Math.ceil(parts.ms / 1000))), h("span", null, t("unitSec")),
563
+ );
564
+ }
565
+
566
+ function PeakCard() {
567
+ const state = usePeakState(store, t);
568
+ const [dismissedEnd, setDismissedEnd] = useState(() => {
569
+ try {
570
+ return Number(localStorage.getItem(DISMISS_KEY) || "0");
571
+ } catch {
572
+ return 0;
573
+ }
574
+ });
575
+ const [dismissedSoon, setDismissedSoon] = useState(() => {
576
+ try {
577
+ return Number(localStorage.getItem(SOON_DISMISS_KEY) || "0");
578
+ } catch {
579
+ return 0;
580
+ }
581
+ });
582
+ const [showDetails, setShowDetails] = useState(false);
583
+ const [enabled, setEnabled] = useState(() => {
584
+ try {
585
+ return localStorage.getItem(ENABLED_KEY) !== "0";
586
+ } catch {
587
+ return true;
588
+ }
589
+ });
590
+ useEffect(() => {
591
+ const onStorage = (event) => {
592
+ if (event.key === ENABLED_KEY) setEnabled(event.newValue !== "0");
593
+ };
594
+ window.addEventListener("storage", onStorage);
595
+ return () => window.removeEventListener("storage", onStorage);
596
+ }, []);
597
+ if (!enabled) return null;
598
+ const isSoon = state.mode === "soon";
599
+ if (state.mode !== "peak" && state.mode !== "soon") return null;
600
+ if (state.mode === "peak" && dismissedEnd === state.windowEnd) return null;
601
+ if (isSoon && dismissedSoon === state.windowStart) return null;
602
+ const dismiss = () => {
603
+ if (state.mode === "peak") {
604
+ setDismissedEnd(state.windowEnd);
605
+ try {
606
+ localStorage.setItem(DISMISS_KEY, String(state.windowEnd));
607
+ } catch {
608
+ /* private mode */
609
+ }
610
+ return;
611
+ }
612
+ setDismissedSoon(state.windowStart);
613
+ try {
614
+ localStorage.setItem(SOON_DISMISS_KEY, String(state.windowStart));
615
+ } catch {
616
+ /* private mode */
617
+ }
618
+ };
619
+ const progressPercent = Math.round((state.progress ?? 0) * 100);
620
+ return h("div", {
621
+ className: "dpg-card",
622
+ "data-mode": isSoon ? "soon" : "peak",
623
+ role: "status",
624
+ "aria-live": "polite",
625
+ "aria-label": isSoon ? `${t("soonKicker")}: ${formatTemplate(t("soonTitle"), { wait: state.wait })}` : `${t("kicker")}: ${t("title")}`,
626
+ },
627
+ h("button", { type: "button", className: "dpg-x", onClick: dismiss, title: isSoon ? t("soonDismiss") : t("dismiss"), "aria-label": isSoon ? t("soonDismiss") : t("dismiss") }, "×"),
628
+ h("div", { className: "dpg-top" },
629
+ h("div", { className: "dpg-seal", "aria-hidden": "true" },
630
+ h("svg", { className: "dpg-ring", viewBox: "0 0 64 64" },
631
+ h("circle", { className: "dpg-ring-track", cx: 32, cy: 32, r: RING_RADIUS }),
632
+ h("circle", { className: "dpg-ring-fill", cx: 32, cy: 32, r: RING_RADIUS, style: { strokeDashoffset: state.ringOffset } }),
633
+ ),
634
+ h("span", { className: "dpg-seal-pulse" }),
635
+ h("span", { className: "dpg-seal-icon" }, h(IconBolt)),
636
+ ),
637
+ h("div", { className: "dpg-copy" },
638
+ h("div", { className: "dpg-kicker" },
639
+ h("span", { className: "dpg-dot", "aria-hidden": "true" }),
640
+ isSoon ? t("soonKicker") : t("kicker"),
641
+ h("span", { className: "dpg-mult" }, t("multiplier")),
642
+ ),
643
+ h("div", { className: "dpg-title" }, isSoon ? formatTemplate(t("soonTitle"), { wait: state.wait }) : t("title")),
644
+ h("div", { className: "dpg-reason" }, isSoon ? t("soonReason") : t("reason")),
645
+ ),
646
+ ),
647
+ h("div", { className: "dpg-mid" },
648
+ h("div", { className: "dpg-count" },
649
+ h(Countdown, { state }),
650
+ h("div", { className: "dpg-count-label" }, isSoon ? t("soonCountdownLabel") : t("countdownLabel")),
651
+ ),
652
+ h("div", { className: "dpg-meter" },
653
+ h("div", { className: "dpg-meter-head" },
654
+ h("span", null, t("windowLabel"), " ", h("b", null, formatTemplate(t("windowRange"), { start: state.startLabel, end: state.endLabel, zone: state.zone }))),
655
+ h("span", null, `${t("nowLabel")} ${state.nowLabel}`, isSoon ? null : h("b", { style: { marginLeft: 6 } }, `${progressPercent}%`)),
656
+ ),
657
+ h("div", { className: "dpg-track", role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": progressPercent, "aria-label": isSoon ? t("soonCountdownLabel") : t("countdownLabel") },
658
+ h("div", { className: "dpg-fill", style: { width: `${progressPercent}%` } }),
659
+ ),
660
+ ),
661
+ ),
662
+ h("div", { className: "dpg-rec" },
663
+ h("span", { className: "dpg-rec-mark", "aria-hidden": "true" }, "→"),
664
+ h("div", { className: "dpg-rec-body" },
665
+ h("span", { className: "dpg-rec-label" }, isSoon ? t("soonRecommendationTitle") : t("recommendationTitle")),
666
+ h("span", { className: "dpg-rec-text" }, isSoon
667
+ ? renderTemplate(t("soonRecommendation"), { start: h("b", null, state.startLabel), end: h("b", null, state.endLabel) })
668
+ : renderTemplate(t("recommendation"), { time: h("b", null, state.endLabel), wait: h("b", null, state.wait) })),
669
+ ),
670
+ ),
671
+ h("div", { className: "dpg-tail" },
672
+ h("div", { className: "dpg-schedule" },
673
+ h("span", { className: "dpg-schedule-label" }, t("scheduleChipLabel")),
674
+ state.scheduleLocalParts.map((part) => h("span", { className: "dpg-chip", key: part }, part)),
675
+ h("span", { className: "dpg-schedule-note" }, t("scheduleWeekend")),
676
+ ),
677
+ h("div", { className: "dpg-actions" },
678
+ h("button", { type: "button", className: "dpg-btn", onClick: () => setShowDetails((value) => !value), "aria-expanded": showDetails ? "true" : "false" }, showDetails ? t("hideDetails") : t("details")),
679
+ h("button", { type: "button", className: "dpg-btn", "data-emphasis": "true", onClick: dismiss }, isSoon ? t("soonDismiss") : t("dismiss")),
680
+ ),
681
+ ),
682
+ showDetails
683
+ ? h("div", { className: "dpg-details" },
684
+ h("span", null, t("scheduleUtc"), " ", h("b", null, state.scheduleUtc)),
685
+ h("span", null, formatTemplate(t("scheduleLocal"), { zone: state.zone }), " ", h("b", null, state.scheduleLocal)),
686
+ h("span", null, t("holidayNote")),
687
+ h("span", null, t("scheduleWeekend")),
688
+ )
689
+ : null,
690
+ );
691
+ }
692
+
693
+ ctx.effect(() => ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register({
694
+ name: "conversation.composer.dock",
695
+ id: "peak-guard",
696
+ order: 5,
697
+ locale: NS,
698
+ }, PeakCard)), "peak-guard: composer dock card");
699
+
700
+ ctx.effect(() => ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
701
+ name: "conversation.session.header.utilities",
702
+ id: "peak-guard",
703
+ order: 40,
704
+ label: () => t("badgeLabel"),
705
+ locale: NS,
706
+ }, HeaderBadge)), "peak-guard: header badge");
707
+
708
+ try {
709
+ window.__DSH_PEAK_GUARD__ = {
710
+ state: () => buildState(store.getNow(), t),
711
+ preview: (iso) => store.force(iso),
712
+ clearPreview: () => store.force(null),
713
+ leadMinutes: PEAK_LEAD_MS / 60000,
714
+ setEnabled: (value) => {
715
+ try {
716
+ localStorage.setItem(ENABLED_KEY, value ? "1" : "0");
717
+ } catch {
718
+ /* private mode */
719
+ }
720
+ window.dispatchEvent(new StorageEvent("storage", { key: ENABLED_KEY, newValue: value ? "1" : "0" }));
721
+ },
722
+ };
723
+ ctx.effect(() => () => {
724
+ try {
725
+ delete window.__DSH_PEAK_GUARD__;
726
+ } catch {
727
+ /* hardened page */
728
+ }
729
+ }, "peak-guard: debug hook");
730
+ } catch {
731
+ /* hardened page */
732
+ }
733
+
734
+ console.info("[peak-guard] ready");
735
+ }
736
+
737
+ exports.apply = apply;
738
+ exports.inject = inject;
739
+ return module.exports;
740
+ },
741
+ });