@heycys/gallery 0.1.2 → 0.2.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 (292) hide show
  1. package/THIRD-PARTY-NOTICES.md +2 -2
  2. package/browser/editor/components/align.mjs +58 -0
  3. package/browser/editor/components/code/index.mjs +1 -1
  4. package/browser/editor/components/embed/page.mjs +1 -1
  5. package/browser/editor/components/file/page.mjs +4 -4
  6. package/browser/editor/components/heading/index.mjs +7 -6
  7. package/browser/editor/components/icons.mjs +3 -0
  8. package/browser/editor/components/image/index.mjs +2 -0
  9. package/browser/editor/components/image/page.mjs +172 -90
  10. package/browser/editor/components/installed/index.mjs +6 -2
  11. package/browser/editor/components/installed/page.mjs +80 -13
  12. package/browser/editor/components/media/icons.mjs +13 -8
  13. package/browser/editor/components/media/page.mjs +5 -3
  14. package/browser/editor/components/media/toolbar.mjs +12 -3
  15. package/browser/editor/components/paragraph/index.mjs +4 -3
  16. package/browser/editor/components/registry.mjs +17 -5
  17. package/browser/editor/components/table/index.mjs +4 -2
  18. package/browser/editor/page/after/index.mjs +288 -0
  19. package/browser/editor/page/align.mjs +91 -0
  20. package/browser/editor/page/beside.mjs +5 -6
  21. package/browser/editor/page/block-menu/index.mjs +6 -3
  22. package/browser/editor/page/blocks.mjs +3 -1
  23. package/browser/editor/page/board/hint.mjs +64 -27
  24. package/browser/editor/page/board/index.mjs +160 -71
  25. package/browser/editor/page/board/motion.mjs +177 -0
  26. package/browser/editor/page/board/settings.mjs +4 -3
  27. package/browser/editor/page/clipboard/index.mjs +10 -3
  28. package/browser/editor/page/clipboard/own/blocks.mjs +3 -4
  29. package/browser/editor/page/clipboard/own/describe.mjs +6 -2
  30. package/browser/editor/page/commit.mjs +2 -2
  31. package/browser/editor/page/core.mjs +3 -1
  32. package/browser/editor/page/custom-page.mjs +15 -11
  33. package/browser/editor/page/editor.mjs +31 -14
  34. package/browser/editor/page/history.mjs +19 -4
  35. package/browser/editor/page/insert/index.mjs +20 -22
  36. package/browser/editor/page/menu.mjs +15 -7
  37. package/browser/editor/page/page-objects.mjs +3 -7
  38. package/browser/editor/page/present-links.mjs +5 -4
  39. package/browser/editor/page/present-shell.mjs +63 -0
  40. package/browser/editor/page/selection/index.mjs +10 -2
  41. package/browser/editor/page/table/binding.mjs +87 -0
  42. package/browser/editor/page/table/icons.mjs +0 -2
  43. package/browser/editor/page/table/index.mjs +10 -14
  44. package/browser/editor/page/table/views.mjs +5 -52
  45. package/browser/editor/page/table/widths.mjs +82 -10
  46. package/browser/editor/page/typing/index.mjs +44 -12
  47. package/browser/editor/page/typing/leave.mjs +19 -5
  48. package/browser/shared/block-packs.mjs +23 -8
  49. package/browser/shared/component-catalog.mjs +15 -8
  50. package/browser/shared/component-source.mjs +3 -1
  51. package/browser/shared/components/heading.mjs +2 -1
  52. package/browser/shared/components/installed.mjs +1 -1
  53. package/browser/shared/components/page.mjs +2 -0
  54. package/browser/shared/components/paragraph.mjs +8 -1
  55. package/browser/shared/components/table.mjs +1 -0
  56. package/browser/shared/emoji/line-icons.mjs +37 -0
  57. package/browser/shared/http.mjs +9 -0
  58. package/browser/shared/i18n/en.mjs +40 -17
  59. package/browser/shared/outline.mjs +30 -2
  60. package/browser/shared/pack-manifest.mjs +12 -1
  61. package/browser/shared/pages.mjs +169 -57
  62. package/browser/shared/pinyin.mjs +92 -0
  63. package/browser/shared/present-step.mjs +17 -0
  64. package/browser/shared/sidebar-head.mjs +70 -0
  65. package/browser/shared/web-links.mjs +5 -0
  66. package/dist/chunk-2OHANJ6Q.mjs +2 -0
  67. package/dist/{chunk-QOUEFUCH.mjs → chunk-3MBRL2PM.mjs} +1 -1
  68. package/dist/{chunk-NWR2LT5B.mjs → chunk-4OKWZY5E.mjs} +1 -1
  69. package/dist/{chunk-RMECZA2K.mjs → chunk-5K44HLTZ.mjs} +1 -1
  70. package/dist/{chunk-NFMCP2HI.mjs → chunk-5XA2ZBAE.mjs} +2 -2
  71. package/dist/{chunk-OUUP3WKT.mjs → chunk-67N2QJYT.mjs} +1 -1
  72. package/dist/chunk-6KBYRA2C.mjs +2 -0
  73. package/dist/{chunk-SX4LJYT6.mjs → chunk-6N3PX4XX.mjs} +1 -1
  74. package/dist/{chunk-RFDZXFJF.mjs → chunk-7JDX4E65.mjs} +1 -1
  75. package/dist/chunk-7M2BQQGZ.mjs +2 -0
  76. package/dist/{chunk-ZUNVAXLH.mjs → chunk-7OG26PGF.mjs} +1 -1
  77. package/dist/{chunk-B4P2CM2J.mjs → chunk-AHN3NFKJ.mjs} +1 -1
  78. package/dist/chunk-ALZ6ZENZ.mjs +3 -0
  79. package/dist/{chunk-UGWYKHBL.mjs → chunk-AR5D57ZD.mjs} +2 -2
  80. package/dist/{chunk-W3BGBJQW.mjs → chunk-BFJGHSQN.mjs} +1 -1
  81. package/dist/{chunk-HLYV7RIH.mjs → chunk-CMA72MAG.mjs} +1 -1
  82. package/dist/{chunk-D4CKRD3V.mjs → chunk-DAZL73VK.mjs} +2 -2
  83. package/dist/{chunk-6VTU6KEO.mjs → chunk-DJISHPUL.mjs} +1 -1
  84. package/dist/{chunk-BPL64HGA.mjs → chunk-EAIFITAV.mjs} +1 -1
  85. package/dist/chunk-EP25HDAJ.mjs +2 -0
  86. package/dist/{chunk-6EZ7FNIK.mjs → chunk-F4ZA6JRZ.mjs} +1 -1
  87. package/dist/{chunk-3KJHLHUO.mjs → chunk-F6HVDZR4.mjs} +1 -1
  88. package/dist/{chunk-3NJACR4W.mjs → chunk-FDMAXMQA.mjs} +1 -1
  89. package/dist/chunk-FETSREUN.mjs +5 -0
  90. package/dist/{chunk-RKPWLGZ3.mjs → chunk-GHOYKMQ3.mjs} +1 -1
  91. package/dist/chunk-HDWTZ763.mjs +12 -0
  92. package/dist/{chunk-VTKNEYT3.mjs → chunk-HPOI4ZX7.mjs} +1 -1
  93. package/dist/{chunk-OW5NWPOJ.mjs → chunk-HXG7EOPJ.mjs} +1 -1
  94. package/dist/{chunk-V6RIYQZZ.mjs → chunk-ITOH5EGO.mjs} +1 -1
  95. package/dist/{chunk-ZWPMCK7P.mjs → chunk-IV64FFI4.mjs} +1 -1
  96. package/dist/chunk-J43Z6LSS.mjs +2 -0
  97. package/dist/chunk-J5PUIV3Y.mjs +4 -0
  98. package/dist/{chunk-KTKHRC6H.mjs → chunk-JA7G53PC.mjs} +1 -1
  99. package/dist/{chunk-NWFBE2YA.mjs → chunk-JLTZF4LA.mjs} +1 -1
  100. package/dist/{chunk-GY3FAUVM.mjs → chunk-JZT7I2OJ.mjs} +1 -1
  101. package/dist/{chunk-QKXMPUXC.mjs → chunk-K5XATEOO.mjs} +1 -1
  102. package/dist/chunk-L2SUIS5S.mjs +2 -0
  103. package/dist/{chunk-TEZAWDSQ.mjs → chunk-LM4XFQHZ.mjs} +1 -1
  104. package/dist/{chunk-5ZJA7CXT.mjs → chunk-MN7BZL4R.mjs} +1 -1
  105. package/dist/chunk-MTYYHM2G.mjs +15 -0
  106. package/dist/{chunk-HMYTM3GT.mjs → chunk-MXUBKDRO.mjs} +1 -1
  107. package/dist/{chunk-MHW7ZKOU.mjs → chunk-OEXWRJME.mjs} +1 -1
  108. package/dist/{chunk-CRJEG7MT.mjs → chunk-OFHXGWOX.mjs} +1 -1
  109. package/dist/chunk-ORID3AE7.mjs +166 -0
  110. package/dist/{chunk-32ZUEFZ7.mjs → chunk-PGDE24N5.mjs} +1 -1
  111. package/dist/{chunk-UNOYVIYR.mjs → chunk-POTA2FV4.mjs} +1 -1
  112. package/dist/{chunk-N7AZZLGT.mjs → chunk-QE5RG76S.mjs} +18 -17
  113. package/dist/{chunk-N76HWBA3.mjs → chunk-R2ZDLJYT.mjs} +1 -1
  114. package/dist/chunk-SWUTTZCN.mjs +2 -0
  115. package/dist/{chunk-POQSE4C2.mjs → chunk-TR7TWGHB.mjs} +1 -1
  116. package/dist/chunk-U5XL7D6A.mjs +3 -0
  117. package/dist/{chunk-PVER7LSC.mjs → chunk-U7NZI6N4.mjs} +2 -2
  118. package/dist/{chunk-Q47PLKIJ.mjs → chunk-UGWMMEJ5.mjs} +1 -1
  119. package/dist/chunk-UJQJKNGK.mjs +2 -0
  120. package/dist/chunk-UYVCQK3H.mjs +22 -0
  121. package/dist/{chunk-FJYN7RTH.mjs → chunk-VFX2KNHP.mjs} +1 -1
  122. package/dist/{chunk-UBBRTJ2R.mjs → chunk-VS7JIGJW.mjs} +1 -1
  123. package/dist/{chunk-NVRNLK25.mjs → chunk-W7665ZEC.mjs} +2 -2
  124. package/dist/{chunk-NG375ILQ.mjs → chunk-WQDQWCX5.mjs} +1 -1
  125. package/dist/chunk-YLPSMDIS.mjs +5 -0
  126. package/dist/{chunk-VXACTLO3.mjs → chunk-YNAWBAYN.mjs} +1 -1
  127. package/dist/{chunk-ZB4H7ZN4.mjs → chunk-YROX2O22.mjs} +1 -1
  128. package/dist/{chunk-YQGT4AB2.mjs → chunk-ZDLPUOAG.mjs} +1 -1
  129. package/dist/chunk-ZQS6H473.mjs +16 -0
  130. package/dist/{chunk-OJKMINIU.mjs → chunk-ZWWC4KLF.mjs} +15 -15
  131. package/dist/cli.mjs +1 -1
  132. package/dist/server.mjs +1 -1
  133. package/dist/worker.mjs +1 -1
  134. package/kit/board.css +5 -5
  135. package/kit/code.js +15 -15
  136. package/kit/report.css +120 -41
  137. package/kit/runtime.js +45 -7
  138. package/kit/tables.js +2 -0
  139. package/kit/ui.js +2 -0
  140. package/package.json +1 -1
  141. package/packs/gallery/chart-view/kit.js +3 -3
  142. package/packs/gallery/chart-view/writing.md +1 -1
  143. package/packs/gallery/countdown/component.json +1 -1
  144. package/packs/gallery/countdown/kit.js +89 -29
  145. package/packs/gallery/countdown/preview.svg +1 -1
  146. package/packs/gallery/countdown/style.css +88 -100
  147. package/packs/gallery/countdown/writing.md +2 -2
  148. package/packs/gallery/habit/component.json +3 -3
  149. package/packs/gallery/habit/kit.js +114 -75
  150. package/packs/gallery/habit/preview.svg +1 -1
  151. package/packs/gallery/habit/style.css +133 -205
  152. package/packs/gallery/habit/writing.md +2 -2
  153. package/packs/gallery/heatmap/component.json +2 -2
  154. package/packs/gallery/heatmap/kit.js +335 -173
  155. package/packs/gallery/heatmap/preview.svg +1 -1
  156. package/packs/gallery/heatmap/style.css +158 -170
  157. package/packs/gallery/heatmap/writing.md +2 -2
  158. package/packs/gallery/kanban/kit.js +3 -3
  159. package/packs/gallery/kanban/writing.md +1 -1
  160. package/packs/gallery/metric/kit.js +3 -3
  161. package/packs/gallery/metric/writing.md +1 -1
  162. package/packs/gallery/note/component.json +3 -3
  163. package/packs/gallery/note/kit.js +40 -13
  164. package/packs/gallery/note/preview.svg +1 -1
  165. package/packs/gallery/note/style.css +51 -30
  166. package/packs/gallery/note/writing.md +2 -2
  167. package/packs/gallery/recent/component.json +2 -2
  168. package/packs/gallery/recent/kit.js +141 -86
  169. package/packs/gallery/recent/preview.svg +1 -1
  170. package/packs/gallery/recent/style.css +53 -126
  171. package/packs/gallery/recent/writing.md +2 -2
  172. package/packs/gallery/static/component.json +2 -2
  173. package/packs/gallery/static/preview.svg +1 -1
  174. package/packs/gallery/static/style.css +99 -66
  175. package/packs/gallery/static/writing.md +2 -2
  176. package/packs/gallery/stats/component.json +3 -3
  177. package/packs/gallery/stats/kit.js +253 -72
  178. package/packs/gallery/stats/preview.svg +1 -1
  179. package/packs/gallery/stats/style.css +64 -142
  180. package/packs/gallery/stats/writing.md +2 -2
  181. package/packs/gallery/today/component.json +2 -2
  182. package/packs/gallery/today/kit.js +62 -27
  183. package/packs/gallery/today/preview.svg +1 -1
  184. package/packs/gallery/today/style.css +101 -72
  185. package/packs/gallery/today/writing.md +2 -2
  186. package/packs/gallery/todos/component.json +1 -1
  187. package/packs/gallery/todos/kit.js +218 -71
  188. package/packs/gallery/todos/preview.svg +1 -1
  189. package/packs/gallery/todos/style.css +170 -223
  190. package/packs/gallery/todos/writing.md +1 -1
  191. package/packs/gallery/weather/component.json +2 -2
  192. package/packs/gallery/weather/kit.js +165 -90
  193. package/packs/gallery/weather/preview.svg +1 -1
  194. package/packs/gallery/weather/style.css +100 -218
  195. package/packs/gallery/weather/writing.md +1 -1
  196. package/packs/gallery-soft/countdown/component.json +18 -0
  197. package/packs/gallery-soft/countdown/example.html +3 -0
  198. package/packs/gallery-soft/countdown/kit.js +206 -0
  199. package/packs/gallery-soft/countdown/preview.svg +1 -0
  200. package/packs/gallery-soft/countdown/style.css +211 -0
  201. package/packs/gallery-soft/countdown/template.html +3 -0
  202. package/packs/gallery-soft/countdown/writing.md +24 -0
  203. package/packs/gallery-soft/habit/component.json +14 -0
  204. package/packs/gallery-soft/habit/example.html +3 -0
  205. package/packs/gallery-soft/habit/kit.js +366 -0
  206. package/packs/gallery-soft/habit/preview.svg +1 -0
  207. package/packs/gallery-soft/habit/style.css +251 -0
  208. package/packs/gallery-soft/habit/template.html +3 -0
  209. package/packs/gallery-soft/habit/writing.md +23 -0
  210. package/packs/gallery-soft/heatmap/component.json +18 -0
  211. package/packs/gallery-soft/heatmap/example.html +3 -0
  212. package/packs/gallery-soft/heatmap/kit.js +498 -0
  213. package/packs/gallery-soft/heatmap/preview.svg +1 -0
  214. package/packs/gallery-soft/heatmap/style.css +181 -0
  215. package/packs/gallery-soft/heatmap/template.html +3 -0
  216. package/packs/gallery-soft/heatmap/writing.md +23 -0
  217. package/packs/gallery-soft/note/component.json +14 -0
  218. package/packs/gallery-soft/note/example.html +3 -0
  219. package/packs/gallery-soft/note/kit.js +162 -0
  220. package/packs/gallery-soft/note/preview.svg +1 -0
  221. package/packs/gallery-soft/note/style.css +148 -0
  222. package/packs/gallery-soft/note/template.html +3 -0
  223. package/packs/gallery-soft/note/writing.md +20 -0
  224. package/packs/gallery-soft/recent/component.json +13 -0
  225. package/packs/gallery-soft/recent/example.html +1 -0
  226. package/packs/gallery-soft/recent/kit.js +335 -0
  227. package/packs/gallery-soft/recent/preview.svg +1 -0
  228. package/packs/gallery-soft/recent/style.css +160 -0
  229. package/packs/gallery-soft/recent/template.html +1 -0
  230. package/packs/gallery-soft/recent/writing.md +17 -0
  231. package/packs/gallery-soft/static/component.json +13 -0
  232. package/packs/gallery-soft/static/example.html +12 -0
  233. package/packs/gallery-soft/static/preview.svg +1 -0
  234. package/packs/gallery-soft/static/style.css +186 -0
  235. package/packs/gallery-soft/static/template.html +1 -0
  236. package/packs/gallery-soft/static/writing.md +20 -0
  237. package/packs/gallery-soft/stats/component.json +13 -0
  238. package/packs/gallery-soft/stats/example.html +1 -0
  239. package/packs/gallery-soft/stats/kit.js +341 -0
  240. package/packs/gallery-soft/stats/preview.svg +1 -0
  241. package/packs/gallery-soft/stats/style.css +119 -0
  242. package/packs/gallery-soft/stats/template.html +1 -0
  243. package/packs/gallery-soft/stats/writing.md +13 -0
  244. package/packs/gallery-soft/today/component.json +12 -0
  245. package/packs/gallery-soft/today/example.html +1 -0
  246. package/packs/gallery-soft/today/kit.js +222 -0
  247. package/packs/gallery-soft/today/preview.svg +1 -0
  248. package/packs/gallery-soft/today/style.css +178 -0
  249. package/packs/gallery-soft/today/template.html +1 -0
  250. package/packs/gallery-soft/today/writing.md +16 -0
  251. package/packs/gallery-soft/todos/component.json +19 -0
  252. package/packs/gallery-soft/todos/example.html +3 -0
  253. package/packs/gallery-soft/todos/kit.js +527 -0
  254. package/packs/gallery-soft/todos/preview.svg +1 -0
  255. package/packs/gallery-soft/todos/style.css +212 -0
  256. package/packs/gallery-soft/todos/template.html +3 -0
  257. package/packs/gallery-soft/todos/writing.md +32 -0
  258. package/packs/gallery-soft/weather/component.json +19 -0
  259. package/packs/gallery-soft/weather/example.html +3 -0
  260. package/packs/gallery-soft/weather/kit.js +411 -0
  261. package/packs/gallery-soft/weather/preview.svg +1 -0
  262. package/packs/gallery-soft/weather/style.css +185 -0
  263. package/packs/gallery-soft/weather/template.html +3 -0
  264. package/packs/gallery-soft/weather/writing.md +28 -0
  265. package/release.json +9 -9
  266. package/skill/SKILL.md +12 -12
  267. package/skill/references/building-blocks.md +4 -4
  268. package/skill/references/pages-and-data.md +13 -9
  269. package/skill/references/writing-html.md +2 -0
  270. package/web/assets/index-BM_K-Auu.css +1 -0
  271. package/web/assets/index-DRslOBiv.js +15 -0
  272. package/web/index.html +2 -2
  273. package/dist/chunk-2NRBY6AJ.mjs +0 -2
  274. package/dist/chunk-35NQ4PLQ.mjs +0 -2
  275. package/dist/chunk-4AV456RQ.mjs +0 -2
  276. package/dist/chunk-5XYHDBJ5.mjs +0 -2
  277. package/dist/chunk-6A53MZYZ.mjs +0 -2
  278. package/dist/chunk-6EXH3ORZ.mjs +0 -166
  279. package/dist/chunk-BVEFGE4B.mjs +0 -2
  280. package/dist/chunk-D4EURAX4.mjs +0 -4
  281. package/dist/chunk-DQCE7TJY.mjs +0 -5
  282. package/dist/chunk-FWNRPMCJ.mjs +0 -12
  283. package/dist/chunk-FWW4CSAZ.mjs +0 -2
  284. package/dist/chunk-JVHHHGEA.mjs +0 -3
  285. package/dist/chunk-PZDLJJTJ.mjs +0 -3
  286. package/dist/chunk-SXHBYZST.mjs +0 -16
  287. package/dist/chunk-UFNYFFKO.mjs +0 -20
  288. package/dist/chunk-UKSHDZZ6.mjs +0 -2
  289. package/dist/chunk-VJPFVNNW.mjs +0 -15
  290. package/dist/chunk-VS3ZMVGX.mjs +0 -5
  291. package/web/assets/index-BK2e_07w.js +0 -13
  292. package/web/assets/index-CWFI83mF.css +0 -1
@@ -0,0 +1,498 @@
1
+ // gallery-soft/heatmap (编辑热力图, 柔光风格): Gallery 风格's gallery/heatmap in the soft look, the same record.
2
+ // The workspace's record of edits by day, as GitHub draws a year of contributions: a column a week (Monday at
3
+ // the top, as a Chinese calendar starts it), a square a day, darker for a day of more, read through
4
+ // context.workspace.query("activity") (its manifest's needs hold "workspace": the server's record,
5
+ // .gallery/state/activity.json, a reader's commits and the Agent's writes, by day). Its one setting, `words`
6
+ // (按字数显示, the card's form: the grip at its top right › 设置…, kept in its data part `settings`, which the
7
+ // board writes and this block only reads), draws the words a day added (its net change, a day that took more
8
+ // away than it added drawn as none) instead of how many edits it had. It asks for every day 53 weeks draw (the
9
+ // record keeps as many), so that a day drawn apart is one before the record started; it reads again whenever
10
+ // the workspace says its record changed (the `data` signal, "activity") and as a day begins (the kit's clock),
11
+ // and draws again as its card changes size (`resize`, `layout`, and its own ResizeObserver on the squares'
12
+ // room) or its settings change (`source`). Where the workspace cannot be read (an exported file, where it has
13
+ // no server; a server that does not answer) it says so on the cards' empty face. The words are the page's
14
+ // language.
15
+ //
16
+ // The squares are the hero: each at least 20px (22px on a card wider than 6 cells), 5px apart, as many weeks
17
+ // as the room beside the weekdays' initials holds, the last today's, the weeks spread so that they reach the
18
+ // card's right edge (`fitOf`); a square a level of five, by where its day stands among the record's days of
19
+ // any edit (`levelsOf`: none, then the quarters a third, two thirds and nine tenths of them reach); a day
20
+ // before the record began a faint dot; today ringed in the accent; a month's name over the week its first day
21
+ // falls in. The pointer on a square says its day, its count and how much of it was the reader's and how much
22
+ // the Agent's. A card wider than 6 cells has a column of figures beside the squares (`figuresOf`): the edits
23
+ // (or words) of the weeks drawn, large; the days with any of the days recorded, and today's; the average a
24
+ // day and the day of the most; and how the edits were shared between you and the Agent, a bar of two. Less to
25
+ // more (少 … 多) is the head's quiet meta there; a card 6 cells wide says the count and the weeks there instead,
26
+ // less to more under the squares. Laid out by its card's size (`context.size`; data-w, data-h, data-pad).
27
+
28
+ const WORDS = {
29
+ zh: {
30
+ title: "编辑热力图",
31
+ since: (/** @type {string} */ date) => `从 ${date} 开始记录`,
32
+ span: (/** @type {number} */ weeks) => `近 ${weeks} 周`,
33
+ after: (/** @type {string} */ date) => `${date}以来`,
34
+ edited: "编辑",
35
+ written: "写作",
36
+ editsUnit: "次",
37
+ wordsUnit: "字",
38
+ days: "天",
39
+ activeLabel: "有编辑的天",
40
+ todayLabel: "今天",
41
+ averageLabel: "日均",
42
+ mostLabel: (/** @type {string} */ date) => `最多 · ${date}`,
43
+ you: "你",
44
+ agent: "Agent",
45
+ none: "还没有编辑",
46
+ noneHow: (/** @type {string} */ date) => `从 ${date} 开始记录。你保存的修改、Agent 写进来的内容,都记在当天的格子里。`,
47
+ tip: (/** @type {number} */ edits) => `${edits} 次编辑`,
48
+ tipWords: (/** @type {string} */ words) => `${words} 字`,
49
+ tipWho: (/** @type {number} */ reader, /** @type {number} */ agent) => `你 ${reader} · Agent ${agent}`,
50
+ before: "还没开始记录",
51
+ said: (/** @type {string} */ day, /** @type {string} */ what) => `${day}:${what}`,
52
+ date: (/** @type {Date} */ date) => `${date.getMonth() + 1}月${date.getDate()}日`,
53
+ weekday: (/** @type {Date} */ date) => `${date.getMonth() + 1}月${date.getDate()}日 ${["周日", "周一", "周二", "周三", "周四", "周五", "周六"][date.getDay()]}`,
54
+ month: (/** @type {number} */ month) => `${month + 1}月`,
55
+ rows: ["一", "", "三", "", "五", "", "日"],
56
+ less: "少",
57
+ more: "多",
58
+ offline: "在 Gallery 里打开时显示",
59
+ offlineHow: "导出的文件读不到工作区的编辑记录",
60
+ failed: "读不到工作区的编辑记录",
61
+ locale: "zh-CN",
62
+ },
63
+ en: {
64
+ title: "Editing heatmap",
65
+ since: (/** @type {string} */ date) => `Recording since ${date}`,
66
+ span: (/** @type {number} */ weeks) => `${weeks} weeks`,
67
+ after: (/** @type {string} */ date) => `Since ${date}`,
68
+ edited: ": edits",
69
+ written: ": words",
70
+ editsUnit: "edits",
71
+ wordsUnit: "words",
72
+ days: "days",
73
+ activeLabel: "Days with edits",
74
+ todayLabel: "Today",
75
+ averageLabel: "A day",
76
+ mostLabel: (/** @type {string} */ date) => `Most · ${date}`,
77
+ you: "You",
78
+ agent: "Agent",
79
+ none: "No edits yet",
80
+ noneHow: (/** @type {string} */ date) => `Recording since ${date}. What you save and what your Agent writes go in the square of their day.`,
81
+ tip: (/** @type {number} */ edits) => `${edits} ${edits === 1 ? "edit" : "edits"}`,
82
+ tipWords: (/** @type {string} */ words) => `${words} words`,
83
+ tipWho: (/** @type {number} */ reader, /** @type {number} */ agent) => `You ${reader} · Agent ${agent}`,
84
+ before: "not recorded yet",
85
+ said: (/** @type {string} */ day, /** @type {string} */ what) => `${day}: ${what}`,
86
+ date: (/** @type {Date} */ date) => `${["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][date.getMonth()]} ${date.getDate()}`,
87
+ weekday: (/** @type {Date} */ date) => `${["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][date.getDay()]}, ${["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][date.getMonth()]} ${date.getDate()}`,
88
+ month: (/** @type {number} */ month) => ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][month],
89
+ rows: ["Mon", "", "Wed", "", "Fri", "", "Sun"],
90
+ less: "Less",
91
+ more: "More",
92
+ offline: "Shown when opened in Gallery",
93
+ offlineHow: "An exported file can't read the workspace's record of edits",
94
+ failed: "The workspace's record of edits could not be read",
95
+ locale: "en",
96
+ },
97
+ };
98
+
99
+ /** The language the page speaks: the UI's where Gallery says it (data-ge-language), else its lang's. */
100
+ function languageOf(/** @type {HTMLElement} */ root) {
101
+ const ui = root.getAttribute("data-ge-language");
102
+ return (ui !== null ? ui === "en" : /^en(?:-|$)/i.test(root.lang)) ? "en" : "zh";
103
+ }
104
+
105
+ /** @typedef {{ date: string, edits: number, words: number, reader: { edits: number, words: number }, agent: { edits: number, words: number } }} Day */
106
+
107
+ const MOST_WEEKS = 53;
108
+ /** The days of the record it reads: as many as 53 weeks draw at the most (on a Sunday, its first square 370 days back). */
109
+ const DAYS = MOST_WEEKS * 7;
110
+ const two = (/** @type {number} */ value) => String(value).padStart(2, "0");
111
+ /** A local day as the record writes it, YYYY-MM-DD. @param {Date} date */
112
+ const dayOf = (date) => `${date.getFullYear()}-${two(date.getMonth() + 1)}-${two(date.getDate())}`;
113
+ /** The day `count` days after `date` (before, negative), at noon. @param {Date} date @param {number} count */
114
+ const after = (date, count) => new Date(date.getFullYear(), date.getMonth(), date.getDate() + count, 12);
115
+
116
+ /**
117
+ * How the squares fit a room of `width` x `height` px (the weekdays' column and the months' row left out),
118
+ * a card `wide` cells wide: { cell, gap, weeks, pitch }. A square is 22px (20px on a card 6 cells wide or
119
+ * less), less where seven of them and the gaps would not fit the height, 8px at the least; the gap 5px, 3px
120
+ * where that leaves a square under 12px (a card two cells high: the squares alone); as many weeks as fit
121
+ * across, at most 53, at least 1; `pitch` the step from one week to the next, the room shared out so that
122
+ * the last week reaches the room's right edge.
123
+ * @param {number} width
124
+ * @param {number} height
125
+ * @param {number} wide
126
+ */
127
+ function fitOf(width, height, wide) {
128
+ const most = wide <= 6 ? 20 : 22;
129
+ const gap = Math.floor((height - 6 * 5) / 7) < 12 ? 3 : 5;
130
+ const cell = Math.max(8, Math.min(most, Math.floor((height - 6 * gap) / 7)));
131
+ const weeks = Math.max(1, Math.min(MOST_WEEKS, Math.floor((width + gap) / (cell + gap))));
132
+ return { cell, gap, weeks, pitch: weeks > 1 ? (width + gap) / weeks : cell + gap };
133
+ }
134
+
135
+ /**
136
+ * The weeks drawn, the last the one of `today` (a Date), `weeks` of them: [[{ date, day, future }…7]…],
137
+ * each a column, Monday first; `future` a day after today, which is not drawn.
138
+ * @param {Date} today
139
+ * @param {number} weeks
140
+ */
141
+ function weeksOf(today, weeks) {
142
+ const monday = after(today, -((today.getDay() + 6) % 7));
143
+ const columns = [];
144
+ for (let week = weeks - 1; week >= 0; week--) {
145
+ const start = after(monday, -7 * week);
146
+ columns.push(Array.from({ length: 7 }, (_, row) => {
147
+ const date = after(start, row);
148
+ return { date, day: dayOf(date), future: dayOf(date) > dayOf(today) };
149
+ }));
150
+ }
151
+ return columns;
152
+ }
153
+
154
+ /**
155
+ * Where a square's levels change, of `values` (each day's count): the counts a third, two thirds and nine
156
+ * tenths of the days of any reach, so that a level says where a day stands among the days of the record.
157
+ * @param {number[]} values
158
+ */
159
+ function levelsOf(values) {
160
+ const some = values.filter((value) => value > 0).sort((a, b) => a - b);
161
+ const at = (/** @type {number} */ share) => (some.length ? some[Math.min(some.length - 1, Math.floor(share * some.length))] : 0);
162
+ return [at(0.34), at(0.66), at(0.9)];
163
+ }
164
+
165
+ /**
166
+ * The level of a day's square, 0 (none) to 4 (the most), of `value` (its edits, or the words it added,
167
+ * none for a day that took more away) against `levels` (levelsOf's).
168
+ * @param {number} value
169
+ * @param {number[]} levels
170
+ */
171
+ function levelOf(value, levels) {
172
+ if (!(value > 0)) return 0;
173
+ return value <= levels[0] ? 1 : value <= levels[1] ? 2 : value <= levels[2] ? 3 : 4;
174
+ }
175
+
176
+ /**
177
+ * What the figures beside the squares say of the record `days` (workspace.query("activity")'s data) over
178
+ * the days drawn, from `first` (YYYY-MM-DD, the first square) to `today`, counting `words` (the setting) or
179
+ * edits, in `language`: the label (近 14 周编辑, or 11月10日以来编辑 where the record began after the first
180
+ * square), the total, how many days had any edit of how many were recorded, today's, the average a day and
181
+ * the day of the most (null for none), and the share of the edits that were the reader's (0 to 100, null
182
+ * for none); `since`, the day the record began (null for no record), and `empty`, nothing counted at all.
183
+ * @param {Array<{ date: string, edits: number, words: number, reader?: { edits: number }, agent?: { edits: number } }>} days
184
+ * @param {boolean} words
185
+ * @param {string} first
186
+ * @param {string} today
187
+ * @param {number} weeks
188
+ * @param {"zh" | "en"} language
189
+ */
190
+ function figuresOf(days, words, first, today, weeks, language) {
191
+ const said = WORDS[language];
192
+ const since = days.map((day) => day.date).sort()[0] ?? null;
193
+ const drawn = days.filter((day) => day.date >= first && day.date <= today);
194
+ const value = (/** @type {{ edits: number, words: number }} */ day) => (words ? Math.max(0, day.words) : day.edits);
195
+ const total = drawn.reduce((sum, day) => sum + value(day), 0);
196
+ const edits = drawn.reduce((sum, day) => sum + day.edits, 0);
197
+ const reader = drawn.reduce((sum, day) => sum + (day.reader?.edits ?? 0), 0);
198
+ const agent = drawn.reduce((sum, day) => sum + (day.agent?.edits ?? 0), 0);
199
+ let most = null;
200
+ for (const day of drawn) if (value(day) > 0 && (!most || value(day) > value(most))) most = day;
201
+ const sinceDate = since ? new Date(`${since}T12:00:00`) : null;
202
+ const period = since && since > first && sinceDate ? said.after(said.date(sinceDate)) : said.span(weeks);
203
+ return {
204
+ label: `${period}${words ? said.written : said.edited}`,
205
+ total,
206
+ active: drawn.filter((day) => day.edits > 0).length,
207
+ recorded: drawn.length,
208
+ today: value(drawn.find((day) => day.date === today) ?? { edits: 0, words: 0 }),
209
+ average: drawn.length ? Math.round(total / drawn.length) : 0,
210
+ most: most ? { date: most.date, value: value(most) } : null,
211
+ share: reader + agent > 0 ? Math.round((100 * reader) / (reader + agent)) : null,
212
+ since,
213
+ empty: edits === 0,
214
+ };
215
+ }
216
+
217
+ /** A count as the card writes it, grouped (1,407), short past 99,999 (12.3万, 123.5K). @param {number} value @param {"zh" | "en"} language */
218
+ const countOf = (value, language) => new Intl.NumberFormat(WORDS[language].locale, Math.abs(value) >= 100_000 ? { notation: "compact", maximumFractionDigits: 1 } : { maximumFractionDigits: 0 }).format(value);
219
+
220
+ /**
221
+ * The size the face is laid out by: its card's cells (`context.size`), or `fallback` (the manifest's first
222
+ * size) where no card is; and the frame's padding for it, as the soft cards pad theirs: "xs" on a card one
223
+ * cell high, "s" on a small one (2 × 2 and less), "l" on one 3 cells high or more, else "m".
224
+ * @param {DocComponentSize | null | undefined} size
225
+ * @param {[number, number]} fallback
226
+ */
227
+ function sizeOf(size, fallback) {
228
+ const w = size?.w ?? fallback[0];
229
+ const h = size?.h ?? fallback[1];
230
+ return { w, h, pad: h === 1 ? "xs" : w <= 2 && h <= 2 ? "s" : h >= 3 ? "l" : "m" };
231
+ }
232
+
233
+ /**
234
+ * An element of the face, of `kind` (its class).
235
+ * @template {keyof HTMLElementTagNameMap} K
236
+ * @param {K} tag
237
+ * @param {string} kind
238
+ * @param {string} [text]
239
+ */
240
+ function made(tag, kind, text) {
241
+ const node = document.createElement(tag);
242
+ node.className = `gallery-soft-heatmap-${kind}`;
243
+ if (text !== undefined) node.textContent = text;
244
+ return node;
245
+ }
246
+
247
+ const HEAT = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3.8" y="3.8" width="6.4" height="6.4" rx="1.6"/><rect x="13.8" y="3.8" width="6.4" height="6.4" rx="1.6"/><rect x="3.8" y="13.8" width="6.4" height="6.4" rx="1.6"/><rect x="13.8" y="13.8" width="6.4" height="6.4" rx="1.6" fill="currentColor"/></svg>';
248
+
249
+ DocKit.component({
250
+ id: "gallery-soft/heatmap",
251
+ on: ["data", "source", "resize", "layout", "clock"],
252
+ mount(host, context) {
253
+ const language = languageOf(document.documentElement);
254
+ const words = WORDS[language];
255
+ const face = made("div", "face");
256
+ const head = made("div", "head");
257
+ const chip = made("span", "chip");
258
+ chip.setAttribute("aria-hidden", "true");
259
+ chip.innerHTML = HEAT;
260
+ const meta = made("span", "meta");
261
+ head.append(chip, made("span", "title", words.title), meta);
262
+ const body = made("div", "body");
263
+ const aside = made("div", "aside");
264
+ const grid = made("div", "grid");
265
+ const room = made("div", "room");
266
+ room.append(grid);
267
+ body.append(aside, room);
268
+ const legend = made("div", "legend");
269
+ legend.setAttribute("aria-hidden", "true");
270
+ legend.append(made("span", "less", words.less), ...[0, 1, 2, 3, 4].map((level) => {
271
+ const square = made("i", "key");
272
+ square.dataset.level = String(level);
273
+ return square;
274
+ }), made("span", "more", words.more));
275
+ const empty = made("div", "empty");
276
+ // The tip stands over the card, in the page's top layer (a popover; where a browser has none, fixed to the
277
+ // window), never inside the card, whose overflow a tip near its edge would widen into a scrollbar or cut.
278
+ const tip = made("div", "tip");
279
+ const popover = typeof tip.showPopover === "function";
280
+ if (popover) tip.popover = "manual";
281
+ else tip.hidden = true;
282
+ tip.setAttribute("role", "tooltip");
283
+ // As wide as the corner a board's grip takes of the content's top right (--soft-grip-room, which CSS alone
284
+ // resolves), measured where the months' row is the face's first (the squares alone).
285
+ const gripRoom = made("i", "grip-room");
286
+ gripRoom.setAttribute("aria-hidden", "true");
287
+ face.append(head, body, empty, gripRoom);
288
+ host.append(face);
289
+ const workspace = context.workspace;
290
+ if (!workspace) return void context.fail(new Error("its manifest's needs hold \"workspace\": context.workspace is not there"));
291
+
292
+ /** @type {Day[] | null} */
293
+ let days = null;
294
+ let failure = "";
295
+ let drawn = "";
296
+ /** @type {Map<HTMLElement, { date: Date, entry: Day | null, value: number, recorded: boolean }>} */
297
+ const squares = new Map();
298
+ const draw = () => {
299
+ const settings = context.readJSON("settings");
300
+ if (settings === undefined) return; // no JSON: the instance has failed, and Gallery shows what it holds
301
+ const byWords = settings !== null && typeof settings === "object" && settings.words === true;
302
+ const { w, h, pad } = sizeOf(context.size, [8, 4]);
303
+ const narrow = w <= 6;
304
+ // Two cells high (8 × 2): the squares alone, no head, figures or legend (the user's word of 2026-11-09).
305
+ const alone = h <= 2;
306
+ face.toggleAttribute("data-alone", alone);
307
+ Object.assign(face.dataset, { w: String(w), h: String(h), pad });
308
+ face.toggleAttribute("data-narrow", narrow);
309
+ face.toggleAttribute("data-unread", Boolean(failure));
310
+ if (failure) {
311
+ meta.replaceChildren();
312
+ const disc = made("span", "disc");
313
+ disc.innerHTML = HEAT;
314
+ empty.replaceChildren(disc, made("p", "line", failure === "offline" ? words.offline : words.failed), ...(failure === "offline" ? [made("p", "how", words.offlineHow)] : []));
315
+ drawn = "";
316
+ return;
317
+ }
318
+ empty.replaceChildren();
319
+ const today = new Date();
320
+ // The room the squares have: the room's, the months' row and the weekdays' column taken off.
321
+ const width = room.clientWidth - 22;
322
+ const height = room.clientHeight - 18;
323
+ const { cell, gap, weeks, pitch } = fitOf(width, height, w);
324
+ const columns = weeksOf(today, weeks);
325
+ const figures = figuresOf(days ?? [], byWords, columns[0][0].day, dayOf(today), weeks, language);
326
+ const sinceSaid = figures.since ? words.since(words.date(new Date(`${figures.since}T12:00:00`))) : "";
327
+ // The head's quiet meta: less to more beside the figures, the count and the weeks on a narrow card, since when while nothing is counted.
328
+ if (days !== null && figures.empty) meta.replaceChildren(sinceSaid);
329
+ else if (narrow) meta.replaceChildren(made("b", "count", countOf(figures.total, language)), ` ${byWords ? words.wordsUnit : words.editsUnit}`, made("span", "dot"), words.span(weeks));
330
+ else meta.replaceChildren(legend);
331
+ if (narrow) {
332
+ if (legend.parentElement !== room) room.append(legend);
333
+ } else if (legend.parentElement === room) legend.remove();
334
+ // The figures beside the squares, on a card wide enough.
335
+ aside.replaceChildren();
336
+ if (!narrow && days !== null) {
337
+ if (figures.empty) aside.append(made("span", "label", words.none), made("p", "note", figures.since ? words.noneHow(words.date(new Date(`${figures.since}T12:00:00`))) : ""));
338
+ else {
339
+ const unit = byWords ? words.wordsUnit : words.editsUnit;
340
+ const total = made("p", "total", countOf(figures.total, language));
341
+ total.append(made("span", "unit", unit));
342
+ const figure = (/** @type {string} */ value, /** @type {string} */ small) => {
343
+ const node = made("span", "value", value);
344
+ node.append(made("small", "small", small));
345
+ return node;
346
+ };
347
+ const table = made("div", "figures");
348
+ table.append(made("span", "label", words.activeLabel), made("span", "label", words.todayLabel), figure(String(figures.active), `/ ${figures.recorded} ${words.days}`), figure(countOf(figures.today, language), unit));
349
+ if (figures.recorded > 6 && figures.most) {
350
+ const most = new Date(`${figures.most.date}T12:00:00`);
351
+ table.append(made("span", "label", words.averageLabel), made("span", "label", words.mostLabel(words.date(most))), figure(countOf(figures.average, language), unit), figure(countOf(figures.most.value, language), unit));
352
+ }
353
+ aside.append(made("span", "label", figures.label), total, table);
354
+ if (figures.share !== null) {
355
+ const split = made("div", "split");
356
+ const bar = made("div", "bar");
357
+ const you = made("i", "you");
358
+ you.style.width = `${figures.share}%`;
359
+ bar.append(you, made("i", "agent"));
360
+ const key = made("div", "key-line");
361
+ const side = (/** @type {string} */ kind, /** @type {string} */ name, /** @type {number} */ share) => {
362
+ const node = made("span", kind);
363
+ node.append(made("i", "swatch"), `${name} `, made("b", "share", `${share}%`));
364
+ return node;
365
+ };
366
+ key.append(side("you-key", words.you, figures.share), side("agent-key", words.agent, 100 - figures.share));
367
+ split.append(bar, key);
368
+ aside.append(split);
369
+ }
370
+ }
371
+ }
372
+ const key = JSON.stringify([cell, gap, weeks, pitch, width, dayOf(today), byWords, days, language]);
373
+ if (key === drawn) return;
374
+ drawn = key;
375
+ const values = (days ?? []).map((day) => (byWords ? Math.max(0, day.words) : day.edits));
376
+ const levels = levelsOf(values);
377
+ const byDate = new Map((days ?? []).map((item) => [item.date, item]));
378
+ squares.clear();
379
+ hide(); // the squares drawn anew: the one it stood over is gone
380
+ /** @type {HTMLElement[]} */
381
+ const nodes = [];
382
+ const top = (/** @type {number} */ row) => 18 + row * (cell + gap);
383
+ columns.forEach((column, index) => {
384
+ for (const [row, { date, day, future }] of column.entries()) {
385
+ if (future) continue;
386
+ const square = made("i", "day");
387
+ square.style.left = `${(22 + index * pitch).toFixed(1)}px`;
388
+ square.style.top = `${top(row)}px`;
389
+ square.style.width = square.style.height = `${cell}px`;
390
+ const recorded = days !== null && figures.since !== null && day >= figures.since;
391
+ const entry = byDate.get(day) ?? null;
392
+ const value = entry ? (byWords ? Math.max(0, entry.words) : entry.edits) : 0;
393
+ if (!recorded) square.classList.add("gallery-soft-heatmap-before");
394
+ else square.dataset.level = String(levelOf(value, levels));
395
+ if (day === dayOf(today)) square.classList.add("gallery-soft-heatmap-today");
396
+ square.setAttribute("aria-label", words.said(words.weekday(date), recorded ? (byWords ? words.tipWords(countOf(value, language)) : words.tip(value)) : words.before));
397
+ squares.set(square, { date, entry, value, recorded });
398
+ nodes.push(square);
399
+ }
400
+ });
401
+ // A month's name over the week its first day falls in (the first week's too where most of it is that
402
+ // month), none closer to the one before than 34px.
403
+ let lastMonth = -1;
404
+ let lastX = -99;
405
+ columns.forEach((column, index) => {
406
+ for (const { date } of column) {
407
+ if (date.getDate() === 1 || (index === 0 && after(date, 21).getMonth() === date.getMonth())) {
408
+ const x = 22 + index * pitch;
409
+ if (date.getMonth() !== lastMonth && x - lastX >= 34) {
410
+ const label = made("span", "month", words.month(date.getMonth()));
411
+ label.style.left = `${x.toFixed(1)}px`;
412
+ nodes.push(label);
413
+ lastX = x;
414
+ }
415
+ lastMonth = date.getMonth();
416
+ break;
417
+ }
418
+ }
419
+ });
420
+ words.rows.forEach((name, row) => {
421
+ if (!name) return;
422
+ const label = made("span", "row", name);
423
+ label.style.top = `${top(row) + (cell - 14) / 2}px`;
424
+ nodes.push(label);
425
+ });
426
+ grid.replaceChildren(...nodes, tip);
427
+ // The squares alone: the months' row is the card's first, and a name that would reach into the grip's corner is left out.
428
+ if (alone) {
429
+ const end = room.getBoundingClientRect().right - gripRoom.offsetWidth;
430
+ for (const label of grid.querySelectorAll(".gallery-soft-heatmap-month")) if (label.getBoundingClientRect().right > end) label.remove();
431
+ }
432
+ };
433
+ // The pointer on a square: its day, its count, and the reader's and the Agent's shares of it, over it (under
434
+ // it where the window has no room above), its middle at the square's but held 8px inside the window.
435
+ const show = (/** @type {Event} */ event) => {
436
+ const square = /** @type {HTMLElement} */ (event.target);
437
+ const said = squares.get(square);
438
+ if (!said || !said.recorded) return hide();
439
+ const settings = context.readJSON("settings");
440
+ const byWords = settings !== null && typeof settings === "object" && settings?.words === true;
441
+ const first = made("b", "tip-count", byWords ? words.tipWords(countOf(said.value, language)) : words.tip(said.value));
442
+ tip.replaceChildren(first, made("span", "tip-day", ` · ${words.weekday(said.date)}`), document.createElement("br"), made("span", "tip-who", words.tipWho(said.entry?.reader?.edits ?? 0, said.entry?.agent?.edits ?? 0)));
443
+ if (popover) {
444
+ if (!tip.matches(":popover-open")) tip.showPopover();
445
+ } else tip.hidden = false;
446
+ const box = square.getBoundingClientRect();
447
+ const width = tip.offsetWidth;
448
+ const height = tip.offsetHeight;
449
+ const left = Math.max(8, Math.min(innerWidth - 8 - width, box.left + box.width / 2 - width / 2));
450
+ const top = box.top - 8 - height < 8 ? box.bottom + 8 : box.top - 8 - height;
451
+ tip.style.left = `${Math.round(left)}px`;
452
+ tip.style.top = `${Math.round(top)}px`;
453
+ for (const other of grid.querySelectorAll(".gallery-soft-heatmap-pointed")) other.classList.remove("gallery-soft-heatmap-pointed");
454
+ square.classList.add("gallery-soft-heatmap-pointed");
455
+ };
456
+ function hide() {
457
+ if (popover) {
458
+ if (tip.matches(":popover-open")) tip.hidePopover();
459
+ } else tip.hidden = true;
460
+ for (const other of grid.querySelectorAll(".gallery-soft-heatmap-pointed")) other.classList.remove("gallery-soft-heatmap-pointed");
461
+ }
462
+ context.listen(grid, "pointerover", show);
463
+ context.listen(grid, "pointerleave", hide);
464
+ // A scroll (the page's, or a card's) moves the square from under it: gone until the pointer is on one again.
465
+ context.listen(document, "scroll", hide, { capture: true, passive: true });
466
+ context.signal?.addEventListener("abort", hide, { once: true });
467
+ let asking = 0; // the last read asked: an older answer that comes after it is dropped
468
+ const read = () => {
469
+ const ticket = ++asking;
470
+ workspace.query("activity", { days: DAYS }).then(({ data }) => {
471
+ if (ticket !== asking || context.signal.aborted) return;
472
+ days = data;
473
+ failure = "";
474
+ draw();
475
+ }, (/** @type {any} */ error) => {
476
+ if (ticket !== asking || context.signal.aborted) return;
477
+ days = null;
478
+ failure = error?.code === "offline" ? "offline" : "failed";
479
+ draw();
480
+ });
481
+ };
482
+ draw();
483
+ read();
484
+ // The room the squares have, its height too (the kit's `resize` hears its width alone): a card given its
485
+ // height after the first draw (a board laid out late) is drawn again as it gets it.
486
+ if (typeof ResizeObserver === "function") {
487
+ const watch = new ResizeObserver(() => draw());
488
+ watch.observe(room);
489
+ context.signal?.addEventListener("abort", () => watch.disconnect(), { once: true });
490
+ }
491
+ return {
492
+ update(reason, detail) {
493
+ if ((reason === "data" && detail === "activity") || (reason === "clock" && detail === "day")) read();
494
+ else if (reason !== "data" && reason !== "clock") draw();
495
+ },
496
+ };
497
+ },
498
+ });
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 120"><defs><linearGradient id="w" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#3e7ac6" stop-opacity=".07"/><stop offset=".52" stop-color="#3e7ac6" stop-opacity="0"/></linearGradient></defs><rect width="240" height="120" rx="8" fill="#f3f3f1"/><rect x="16.5" y="12.5" width="207" height="95" rx="14" fill="#ffffff" stroke="#e8e7e4"/><rect x="17" y="13" width="206" height="94" rx="13.5" fill="url(#w)"/><rect x="30" y="24" width="14" height="14" rx="4.5" fill="#ecf3fb"/><rect x="34" y="30" width="6" height="2" rx="1" fill="#3e7ac6"/><rect x="50" y="28" width="40" height="6" rx="3" fill="#37352f"/><rect x="170" y="28" width="5" height="5" rx="1.5" fill="#ececed"/><rect x="177" y="28" width="5" height="5" rx="1.5" fill="#d6e4f4"/><rect x="184" y="28" width="5" height="5" rx="1.5" fill="#a6c4e8"/><rect x="191" y="28" width="5" height="5" rx="1.5" fill="#6b9bd4"/><rect x="198" y="28" width="5" height="5" rx="1.5" fill="#3e7ac6"/><rect x="30" y="48" width="30" height="4" rx="2" fill="#c4c2bd"/><rect x="30" y="58" width="40" height="14" rx="7" fill="#1f1f1f"/><rect x="30" y="82" width="22" height="4" rx="2" fill="#c4c2bd"/><rect x="30" y="90" width="22" height="5" rx="2.5" fill="#4a4843"/><rect x="30" y="99" width="18" height="3" rx="1.5" fill="#3e7ac6"/><rect x="50" y="99" width="24" height="3" rx="1.5" fill="#afcaea"/><rect x="96" y="44" width="8" height="7" rx="2" fill="#ececed"/><rect x="96" y="52.6" width="8" height="7" rx="2" fill="#6b9bd4"/><rect x="96" y="61.2" width="8" height="7" rx="2" fill="#d6e4f4"/><rect x="96" y="69.8" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="96" y="78.4" width="8" height="7" rx="2" fill="#a6c4e8"/><rect x="96" y="87" width="8" height="7" rx="2" fill="#ececed"/><rect x="96" y="95.6" width="8" height="7" rx="2" fill="#6b9bd4"/><rect x="107" y="44" width="8" height="7" rx="2" fill="#a6c4e8"/><rect x="107" y="52.6" width="8" height="7" rx="2" fill="#d6e4f4"/><rect x="107" y="61.2" width="8" height="7" rx="2" fill="#ececed"/><rect x="107" y="69.8" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="107" y="78.4" width="8" height="7" rx="2" fill="#6b9bd4"/><rect x="107" y="87" width="8" height="7" rx="2" fill="#a6c4e8"/><rect x="107" y="95.6" width="8" height="7" rx="2" fill="#d6e4f4"/><rect x="118" y="44" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="118" y="52.6" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="118" y="61.2" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="118" y="69.8" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="118" y="78.4" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="118" y="87" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="118" y="95.6" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="129" y="44" width="8" height="7" rx="2" fill="#d6e4f4"/><rect x="129" y="52.6" width="8" height="7" rx="2" fill="#a6c4e8"/><rect x="129" y="61.2" width="8" height="7" rx="2" fill="#6b9bd4"/><rect x="129" y="69.8" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="129" y="78.4" width="8" height="7" rx="2" fill="#ececed"/><rect x="129" y="87" width="8" height="7" rx="2" fill="#d6e4f4"/><rect x="129" y="95.6" width="8" height="7" rx="2" fill="#a6c4e8"/><rect x="140" y="44" width="8" height="7" rx="2" fill="#6b9bd4"/><rect x="140" y="52.6" width="8" height="7" rx="2" fill="#ececed"/><rect x="140" y="61.2" width="8" height="7" rx="2" fill="#a6c4e8"/><rect x="140" y="69.8" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="140" y="78.4" width="8" height="7" rx="2" fill="#d6e4f4"/><rect x="140" y="87" width="8" height="7" rx="2" fill="#6b9bd4"/><rect x="140" y="95.6" width="8" height="7" rx="2" fill="#ececed"/><rect x="151" y="44" width="8" height="7" rx="2" fill="#ececed"/><rect x="151" y="52.6" width="8" height="7" rx="2" fill="#6b9bd4"/><rect x="151" y="61.2" width="8" height="7" rx="2" fill="#d6e4f4"/><rect x="151" y="69.8" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="151" y="78.4" width="8" height="7" rx="2" fill="#a6c4e8"/><rect x="151" y="87" width="8" height="7" rx="2" fill="#ececed"/><rect x="151" y="95.6" width="8" height="7" rx="2" fill="#6b9bd4"/><rect x="162" y="44" width="8" height="7" rx="2" fill="#a6c4e8"/><rect x="162" y="52.6" width="8" height="7" rx="2" fill="#d6e4f4"/><rect x="162" y="61.2" width="8" height="7" rx="2" fill="#ececed"/><rect x="162" y="69.8" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="162" y="78.4" width="8" height="7" rx="2" fill="#6b9bd4"/><rect x="162" y="87" width="8" height="7" rx="2" fill="#a6c4e8"/><rect x="162" y="95.6" width="8" height="7" rx="2" fill="#d6e4f4"/><rect x="173" y="44" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="173" y="52.6" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="173" y="61.2" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="173" y="69.8" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="173" y="78.4" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="173" y="87" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="173" y="95.6" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="184" y="44" width="8" height="7" rx="2" fill="#d6e4f4"/><rect x="184" y="52.6" width="8" height="7" rx="2" fill="#a6c4e8"/><rect x="184" y="61.2" width="8" height="7" rx="2" fill="#6b9bd4"/><rect x="184" y="69.8" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="184" y="78.4" width="8" height="7" rx="2" fill="#ececed"/><rect x="184" y="87" width="8" height="7" rx="2" fill="#d6e4f4"/><rect x="184" y="95.6" width="8" height="7" rx="2" fill="#a6c4e8"/><rect x="195" y="44" width="8" height="7" rx="2" fill="#6b9bd4"/><rect x="195" y="52.6" width="8" height="7" rx="2" fill="#ececed"/><rect x="195" y="61.2" width="8" height="7" rx="2" fill="#a6c4e8"/><rect x="195" y="69.8" width="8" height="7" rx="2" fill="#3e7ac6"/><rect x="195" y="78.4" width="8" height="7" rx="2" fill="#d6e4f4"/><rect x="195" y="87" width="8" height="7" rx="2" fill="#6b9bd4"/><rect x="195" y="95.6" width="8" height="7" rx="2" fill="#ececed"/></svg>