@diagrammo/dgmo 0.92.0 → 0.93.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 (166) hide show
  1. package/dist/advanced.d.ts +85 -21
  2. package/dist/advanced.js +455 -422
  3. package/dist/block.d.ts +2 -2
  4. package/dist/block.js +75 -70
  5. package/dist/{chart-meta-BBqLnv5q.d.ts → chart-meta-BbjsRY7o.d.ts} +2 -2
  6. package/dist/chart-meta.d.ts +3 -3
  7. package/dist/chart-meta.js +25 -19
  8. package/dist/{chart-types-CbXlZFLd.d.ts → chart-types-Cppnp31G.d.ts} +173 -4
  9. package/dist/{charts-d3-QUUDVETP.js → charts-d3-ERYYVGP6.js} +7 -6
  10. package/dist/{chunk-HIWHIYLD.js → chunk-2TFXRBCD.js} +19 -19
  11. package/dist/{chunk-F7326NVM.js → chunk-3GCASUWA.js} +58 -25
  12. package/dist/{chunk-ZJAK6C7Y.js → chunk-3PP746YM.js} +6 -1
  13. package/dist/{chunk-WLLO3PZ3.js → chunk-472EOHVW.js} +17 -17
  14. package/dist/{chunk-LMP6Y7QO.js → chunk-4WQX55VX.js} +56 -24
  15. package/dist/{chunk-JLU52ZDT.js → chunk-5CBX66KB.js} +10 -10
  16. package/dist/{chunk-DEFBUZWV.js → chunk-5HAYT43L.js} +70 -54
  17. package/dist/{chunk-S7W75MIM.js → chunk-65GPEV5V.js} +21 -3
  18. package/dist/{chunk-Q6W2K33N.js → chunk-6XLMTAEX.js} +1 -1
  19. package/dist/{chunk-JKST4RTT.js → chunk-7OWK5K5C.js} +6 -6
  20. package/dist/{chunk-73EYNV4O.js → chunk-A5WIAMCT.js} +4 -6
  21. package/dist/{chunk-X5BUX2GK.js → chunk-ADVGV3P7.js} +1 -1
  22. package/dist/{chunk-45QUTYPD.js → chunk-CYDM2XII.js} +17 -17
  23. package/dist/chunk-CYTFO2LF.js +209 -0
  24. package/dist/{chunk-JRWUHLRP.js → chunk-D6PVMUCX.js} +51 -8
  25. package/dist/{chunk-BRSM6Z34.js → chunk-DAHQL57W.js} +14 -14
  26. package/dist/{chunk-RBFJT23E.js → chunk-EQWC76GQ.js} +6 -6
  27. package/dist/{chunk-ADNIO3XY.js → chunk-FIYW7JW4.js} +5 -5
  28. package/dist/{chunk-7ENDOHAA.js → chunk-FMAVHTYB.js} +919 -927
  29. package/dist/{chunk-3PFMGNOM.js → chunk-GJXMG4S3.js} +26 -11
  30. package/dist/chunk-GXCDD6NG.js +24 -0
  31. package/dist/{chunk-RJ2JW3FG.js → chunk-IESUM4N5.js} +2331 -2236
  32. package/dist/{chunk-62BP7AJU.js → chunk-IIV4SMJL.js} +18 -18
  33. package/dist/{chunk-5EBYPETE.js → chunk-J43OCO22.js} +3 -5
  34. package/dist/{chunk-L5NW3PVL.js → chunk-J6AEKIRQ.js} +1 -1
  35. package/dist/{chunk-2BJW7FSF.js → chunk-KBEWVCK3.js} +1 -1
  36. package/dist/{chunk-LRP5FXST.js → chunk-KCHQVED5.js} +18 -17
  37. package/dist/{chunk-Q6ZQX2HX.js → chunk-KCMGIYPO.js} +4 -6
  38. package/dist/{chunk-MDVJYRJT.js → chunk-KPGPK4SK.js} +22 -22
  39. package/dist/{chunk-Q2UFOKPB.js → chunk-KPSSNGAE.js} +22 -22
  40. package/dist/{chunk-V4D2ZQBE.js → chunk-KRPLP5LP.js} +6 -1
  41. package/dist/{chunk-BZX7QCXJ.js → chunk-KRU2LCIP.js} +15 -15
  42. package/dist/{chunk-HXULJZA2.js → chunk-LDDDKWL6.js} +6 -1
  43. package/dist/{chunk-NREU4JFE.js → chunk-LNVB75QU.js} +6 -8
  44. package/dist/{chunk-MX4SZLSV.js → chunk-LUDUDBO6.js} +16 -8
  45. package/dist/{chunk-2YAFDND3.js → chunk-M4NV5PPU.js} +4 -6
  46. package/dist/{chunk-26BBJUBW.js → chunk-M6ZQAFPD.js} +4 -6
  47. package/dist/{chunk-NPWVOFB3.js → chunk-MB2SQF7N.js} +1 -1
  48. package/dist/{chunk-6ZL2XYM7.js → chunk-MLY5ALOV.js} +6 -6
  49. package/dist/chunk-NJJCTEBM.js +416 -0
  50. package/dist/{chunk-OQW4ETJX.js → chunk-NM5V5EKY.js} +6 -1
  51. package/dist/{chunk-ONYO5WJW.js → chunk-NMK7KJZZ.js} +21 -21
  52. package/dist/{chunk-6BNNLVAR.js → chunk-NPDVWFAQ.js} +6 -1
  53. package/dist/{chunk-FTUMRSGT.js → chunk-P5FUWXPW.js} +4 -286
  54. package/dist/chunk-P7TRFL4P.js +54 -0
  55. package/dist/{chunk-A4PNWAUW.js → chunk-PGSGJLUR.js} +6 -1
  56. package/dist/{chunk-DRFGIOZ6.js → chunk-Q7Y6JQ3R.js} +4 -6
  57. package/dist/{chunk-J2E7EUER.js → chunk-QDRCWVV3.js} +17 -17
  58. package/dist/{chunk-MXAP37W6.js → chunk-TNV4F5WA.js} +16 -7
  59. package/dist/{chunk-EBSFSOJC.js → chunk-U7U64UUN.js} +22 -22
  60. package/dist/chunk-UCHZJJIT.js +421 -0
  61. package/dist/{chunk-FSJ3ZA6N.js → chunk-UG7AQQNR.js} +12 -12
  62. package/dist/{chunk-WFA6IULY.js → chunk-URRAWU5D.js} +5 -0
  63. package/dist/{chunk-VOR32OHN.js → chunk-USAGHYRD.js} +13 -15
  64. package/dist/{chunk-QUGC7GWB.js → chunk-UVX3IYXF.js} +15 -15
  65. package/dist/{chunk-B5PEKSRH.js → chunk-VSAKEMDD.js} +6 -1
  66. package/dist/{chunk-YRYKTYMQ.js → chunk-VSOZZYLK.js} +106 -12
  67. package/dist/{chunk-5SSTVFPX.js → chunk-WK5QEYFA.js} +5 -7
  68. package/dist/{chunk-KTGZDMDX.js → chunk-XUTR7GNT.js} +76 -28
  69. package/dist/{chunk-6WVX4KNP.js → chunk-YDZIVDVR.js} +97 -297
  70. package/dist/{chunk-URQIWIKX.js → chunk-YXXIRM4C.js} +3 -3
  71. package/dist/{chunk-H5UEVW6Q.js → chunk-ZD3QCNB7.js} +1 -1
  72. package/dist/{chunk-DUPZ2BBY.js → chunk-ZTLMEBAC.js} +66 -4
  73. package/dist/clock.js +168 -0
  74. package/dist/completion-registry.js +3 -1
  75. package/dist/countdown.js +36 -395
  76. package/dist/{dgmo-router-4UAPPIKN.js → dgmo-router-CZN36TR3.js} +50 -43
  77. package/dist/{dimensions-SCNVYLRF.js → dimensions-7HHKO5BS.js} +3 -3
  78. package/dist/{d3-BThYBTQk.d.ts → export-context-Ceh3wr0E.d.ts} +2 -8
  79. package/dist/{flowchart-parser-NA75YQ77.js → flowchart-parser-4MZPWYK6.js} +8 -3
  80. package/dist/{flowchart-renderer-6MGGX4RC.js → flowchart-renderer-5XDN575E.js} +13 -17
  81. package/dist/index.d.ts +8 -7
  82. package/dist/index.js +80 -74
  83. package/dist/{layout-S3RLVPH4.js → layout-33FHGDEV.js} +10 -12
  84. package/dist/{layout-MBVBAMPA.js → layout-3TSVAGWH.js} +2 -2
  85. package/dist/{layout-WRG4APSJ.js → layout-5GHV7HNM.js} +9 -13
  86. package/dist/layout-FTOGDNUL.js +23 -0
  87. package/dist/{layout-BNLFQHSB.js → layout-QXGPY7KA.js} +7 -9
  88. package/dist/{layout-search-PMYL3VWS.js → layout-search-EMTCDC3C.js} +8 -10
  89. package/dist/{palettes-IN7ZFRJM.js → palettes-4Y7CL2QL.js} +4 -2
  90. package/dist/{parser-3MK56JZC.js → parser-4Y6PODW6.js} +2 -2
  91. package/dist/{parser-2NWH5JUS.js → parser-A3AN76RQ.js} +2 -1
  92. package/dist/{parser-LCJ76UXS.js → parser-BT4NI6JY.js} +5 -1
  93. package/dist/{parser-FXDS4JDK.js → parser-F7NDRPMA.js} +2 -2
  94. package/dist/{parser-BES6LV3G.js → parser-GDLKUBTO.js} +5 -1
  95. package/dist/{parser-SEUTXN6U.js → parser-KJPPUN4D.js} +2 -2
  96. package/dist/{parser-SIKL4PLF.js → parser-KMKVFET2.js} +5 -1
  97. package/dist/{parser-5LW5JDYG.js → parser-NL73FSFI.js} +5 -1
  98. package/dist/{parser-VA7ZQQTA.js → parser-NUU3DVW4.js} +5 -1
  99. package/dist/{parser-L6RWDXQN.js → parser-OGF2CDA6.js} +8 -3
  100. package/dist/{parser-XZUKNZPB.js → parser-R3LPEQIT.js} +7 -1
  101. package/dist/{parser-X23GRTQJ.js → parser-RCIEKN7Y.js} +5 -1
  102. package/dist/{parser-GX5YLRIP.js → parser-VKYUAT4Y.js} +8 -3
  103. package/dist/{parser-QYISOAGR.js → parser-YX72VLDZ.js} +8 -3
  104. package/dist/redraw-MSXU4ZD2.js +73 -0
  105. package/dist/redraw-T324XAF3.js +1698 -0
  106. package/dist/{renderer-5VKPB4LL.js → renderer-37PLQHVB.js} +5 -5
  107. package/dist/{renderer-ISSDGK54.js → renderer-3F47TPZ2.js} +3 -3
  108. package/dist/{renderer-3QAFDXEY.js → renderer-5ITLW7MX.js} +14 -12
  109. package/dist/{renderer-ZZ6MWVB7.js → renderer-6GJWADLO.js} +4 -4
  110. package/dist/{renderer-MM27BSBO.js → renderer-7Y3LR4R2.js} +3 -4
  111. package/dist/{renderer-2FVUPBGV.js → renderer-AM7P4LN7.js} +1 -1
  112. package/dist/{renderer-ZHGHTSNG.js → renderer-BO73LSVT.js} +13 -12
  113. package/dist/{renderer-3IHQI4A4.js → renderer-CNRQFIEI.js} +11 -10
  114. package/dist/{renderer-OAR5VPH4.js → renderer-DAZWHNLI.js} +2 -2
  115. package/dist/{renderer-NDA2FFYB.js → renderer-DD6T3C4J.js} +5 -5
  116. package/dist/{renderer-WIU5BJUK.js → renderer-DDWDCM2K.js} +2 -2
  117. package/dist/{renderer-7ZZG5HR3.js → renderer-EVKWGUGT.js} +8 -9
  118. package/dist/{renderer-3JY6JAQJ.js → renderer-FL7EOSBU.js} +5 -6
  119. package/dist/{renderer-NIE5IW24.js → renderer-GFALLIYT.js} +8 -7
  120. package/dist/{renderer-O6DY46AV.js → renderer-GFGFHYWY.js} +7 -7
  121. package/dist/{renderer-5TZVFQ7K.js → renderer-GOXAP2IE.js} +7 -8
  122. package/dist/{renderer-NHTR7IRR.js → renderer-GPAVVUET.js} +4 -5
  123. package/dist/{renderer-TO2NVJP3.js → renderer-HJ54YXT3.js} +50 -45
  124. package/dist/{renderer-LM4ICK6I.js → renderer-HYGTMBHT.js} +4 -5
  125. package/dist/{renderer-S3YCLJKL.js → renderer-IHCWBFPI.js} +17 -20
  126. package/dist/{renderer-XO57LTU4.js → renderer-ISDKO3DI.js} +5 -5
  127. package/dist/{renderer-LQP3AGEJ.js → renderer-J3QWK6IM.js} +3 -4
  128. package/dist/{renderer-J666LZXJ.js → renderer-J6H4Q3RM.js} +7 -8
  129. package/dist/{renderer-ALWVK3EJ.js → renderer-JJ4QW3SK.js} +6 -6
  130. package/dist/{renderer-DC7C5Q3G.js → renderer-NOYLNVTI.js} +2 -2
  131. package/dist/{renderer-ZGY7P4BM.js → renderer-PBKTAWQV.js} +16 -20
  132. package/dist/{renderer-XGG2UNXT.js → renderer-POBGKIUT.js} +3 -4
  133. package/dist/{renderer-QGIHLZOJ.js → renderer-RDI2RYUP.js} +9 -10
  134. package/dist/{renderer-JEJT3YET.js → renderer-TSCSZ6CL.js} +2 -3
  135. package/dist/{renderer-VPIELEE5.js → renderer-VDBNC7A6.js} +8 -9
  136. package/dist/{renderer-J67LMOD5.js → renderer-VI34SD5V.js} +15 -15
  137. package/dist/{renderer-J5FKF423.js → renderer-W4MUMXJE.js} +7 -4
  138. package/dist/{renderer-RSK6QFWN.js → renderer-XJMVG2WJ.js} +7 -9
  139. package/dist/{state-parser-TIR2ORJY.js → state-parser-NPYTMRXP.js} +8 -3
  140. package/dist/{state-renderer-G6SUXU4J.js → state-renderer-44K2KDX7.js} +15 -19
  141. package/dist/{tag-groups-DsoVpAY-.d.ts → tag-groups-DydewK3D.d.ts} +2 -173
  142. package/dist/{themes-DRNjxX7Q.d.ts → themes-DzIH3Hjv.d.ts} +3 -2
  143. package/docs/ai-integration.md +1 -1
  144. package/docs/language-reference.md +23 -2
  145. package/gallery/fixtures/map-undirected-edges.dgmo +5 -5
  146. package/gallery/fixtures/whiteboard.dgmo +3 -0
  147. package/package.json +4 -6
  148. package/dist/chunk-3Y63RFJL.js +0 -75
  149. package/dist/chunk-5DH3KYFH.js +0 -20
  150. package/dist/chunk-O753JUEZ.js +0 -67
  151. package/dist/chunk-RJNZHFNG.js +0 -51
  152. package/dist/chunk-Z6DXWULM.js +0 -23
  153. package/dist/layout-5USEVQBY.js +0 -25
  154. package/dist/{chunk-YITHYQCL.js → chunk-4FT5KQ7H.js} +7 -7
  155. package/dist/{chunk-GE7ICBX6.js → chunk-4RY5D5RE.js} +4 -4
  156. package/dist/{chunk-DGUGE67L.js → chunk-CDYV46JE.js} +3 -3
  157. package/dist/{chunk-IGKFOHBG.js → chunk-CQYFZOUC.js} +13 -13
  158. package/dist/{chunk-TKAB3FTK.js → chunk-DRQSB72S.js} +11 -11
  159. package/dist/{chunk-ZKP77MB3.js → chunk-FVTYJUXR.js} +5 -5
  160. package/dist/{chunk-MA42PIGJ.js → chunk-H5NVCOEJ.js} +4 -4
  161. package/dist/{chunk-BEA4D7OS.js → chunk-IXHWS2HI.js} +3 -3
  162. package/dist/{chunk-QKY6W4NA.js → chunk-KHL652SX.js} +25 -25
  163. package/dist/{chunk-6OIMUSDX.js → chunk-MUJVNSEW.js} +9 -9
  164. package/dist/{chunk-YO6AQU4A.js → chunk-QJOVZ4TC.js} +12 -12
  165. package/dist/{chunk-SOHZDW6N.js → chunk-UXNA2H2V.js} +3 -3
  166. package/dist/{chunk-NO3UX6AP.js → chunk-V2NQV7VL.js} +4 -4
@@ -0,0 +1,1698 @@
1
+ import {
2
+ DAY_MS,
3
+ applyOrdinalTemplate,
4
+ dayKey,
5
+ dayStart,
6
+ formatCompound,
7
+ formatCount,
8
+ formatDateShort,
9
+ formatFooter,
10
+ formatHuman,
11
+ formatWordsDetail,
12
+ now,
13
+ ordinalFor,
14
+ sameDay,
15
+ splitClockSeconds
16
+ } from "./chunk-UCHZJJIT.js";
17
+
18
+ // src/utils/svg-embed.ts
19
+ function normalizeSvgForEmbed(input, opts = {}) {
20
+ let svg = input;
21
+ if ((opts.background ?? "transparent") === "transparent") {
22
+ svg = stripRootBackground(svg);
23
+ }
24
+ const rootMatch = svg.match(/<svg[^>]*>/);
25
+ const rootTag = rootMatch?.[0] ?? "";
26
+ if (rootTag && !rootTag.includes("viewBox")) {
27
+ const wh = rootTag.match(/width="(\d+)"[^>]*height="(\d+)"/);
28
+ if (wh) {
29
+ svg = svg.replace(/<svg/, `<svg viewBox="0 0 ${wh[1]} ${wh[2]}"`);
30
+ }
31
+ }
32
+ const tight = computeBBox(svg);
33
+ if (tight && tight.width > 0 && tight.height > 0) {
34
+ const pad = 16;
35
+ const x = tight.x - pad;
36
+ const y = tight.y - pad;
37
+ const w = tight.width + pad * 2;
38
+ const h = tight.height + pad * 2;
39
+ const canvas = readViewBox(svg);
40
+ const TOL = 2;
41
+ const MIN_COVERAGE = 0.5;
42
+ const trustworthy = !canvas || x >= canvas.x - TOL && y >= canvas.y - TOL && x + w <= canvas.x + canvas.width + TOL && y + h <= canvas.y + canvas.height + TOL && w >= canvas.width * MIN_COVERAGE && h >= canvas.height * MIN_COVERAGE;
43
+ if (trustworthy) {
44
+ const vb = `${x} ${y} ${w} ${h}`;
45
+ svg = svg.replace(/(<svg[^>]*?)viewBox="[^"]*"/, `$1viewBox="${vb}"`);
46
+ }
47
+ }
48
+ svg = svg.replace(/(<svg[^>]*?) width="[^"]*"/g, "$1");
49
+ svg = svg.replace(/(<svg[^>]*?) height="[^"]*"/g, "$1");
50
+ svg = svg.replace(/<svg\s{2,}/g, "<svg ");
51
+ return svg;
52
+ }
53
+ function stripRootBackground(svg) {
54
+ const rootMatch = svg.match(/<svg\b[^>]*>/);
55
+ if (!rootMatch) return svg;
56
+ const rootTag = rootMatch[0];
57
+ const styleMatch = rootTag.match(/style="([^"]*)"/);
58
+ const style = styleMatch?.[1] ?? "";
59
+ const bgMatch = style.match(/background:\s*([^;]+)/);
60
+ const bgColor = bgMatch ? normalizeColor(bgMatch[1]) : null;
61
+ const canvas = rootCanvas(rootTag, svg);
62
+ let out = svg;
63
+ if (bgMatch) {
64
+ const cleaned = style.replace(/background:\s*[^;]+;?/i, "").replace(/\s{2,}/g, " ").replace(/;\s*$/, "").trim();
65
+ const newRoot = (cleaned ? rootTag.replace(/style="[^"]*"/, `style="${cleaned}"`) : rootTag.replace(/\s*style="[^"]*"/, "")).replace(/<svg\s{2,}/, "<svg ");
66
+ out = out.replace(rootTag, newRoot);
67
+ }
68
+ if (bgColor && canvas) {
69
+ out = out.replace(
70
+ /<rect\b[^>]*?\/?>(?:\s*<\/rect>)?/g,
71
+ (m) => isFullCanvasFill(m, bgColor, canvas) ? "" : m
72
+ );
73
+ }
74
+ return out;
75
+ }
76
+ function isFullCanvasFill(tag, bgColor, canvas) {
77
+ const fm = tag.match(/\bfill="([^"]*)"/);
78
+ if (!fm || normalizeColor(fm[1]) !== bgColor) return false;
79
+ const w = numAttr(tag, "width");
80
+ const h = numAttr(tag, "height");
81
+ if (w === null || h === null) return false;
82
+ const x = numAttr(tag, "x") ?? 0;
83
+ const y = numAttr(tag, "y") ?? 0;
84
+ const T = 1.5;
85
+ return Math.abs(x) <= T && Math.abs(y) <= T && Math.abs(w - canvas.w) <= T && Math.abs(h - canvas.h) <= T;
86
+ }
87
+ function rootCanvas(rootTag, svg) {
88
+ const wm = rootTag.match(/\bwidth="([\d.]+)"/);
89
+ const hm = rootTag.match(/\bheight="([\d.]+)"/);
90
+ if (wm && hm) return { w: parseFloat(wm[1]), h: parseFloat(hm[1]) };
91
+ const vb = readViewBox(svg);
92
+ return vb ? { w: vb.width, h: vb.height } : null;
93
+ }
94
+ function numAttr(tag, name) {
95
+ const m = tag.match(new RegExp(`\\b${name}="([^"]*)"`));
96
+ if (!m) return null;
97
+ const n = parseFloat(m[1]);
98
+ return Number.isFinite(n) ? n : null;
99
+ }
100
+ function normalizeColor(c) {
101
+ const s = c.trim().toLowerCase();
102
+ const hex6 = s.match(/^#([0-9a-f]{6})$/);
103
+ if (hex6) {
104
+ const n = parseInt(hex6[1], 16);
105
+ return `${n >> 16 & 255},${n >> 8 & 255},${n & 255}`;
106
+ }
107
+ const hex3 = s.match(/^#([0-9a-f]{3})$/);
108
+ if (hex3) {
109
+ const [r, g, b] = hex3[1].split("").map((ch) => parseInt(ch + ch, 16));
110
+ return `${r},${g},${b}`;
111
+ }
112
+ const rgb = s.match(/^rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/);
113
+ if (rgb) return `${+rgb[1]},${+rgb[2]},${+rgb[3]}`;
114
+ return null;
115
+ }
116
+ function readViewBox(svg) {
117
+ const m = svg.match(/<svg[^>]*?\bviewBox="([^"]+)"/);
118
+ if (!m) return null;
119
+ const n = m[1].trim().split(/[\s,]+/).map(Number);
120
+ if (n.length !== 4 || n.some((v) => !Number.isFinite(v)) || n[2] <= 0 || n[3] <= 0)
121
+ return null;
122
+ return { x: n[0], y: n[1], width: n[2], height: n[3] };
123
+ }
124
+ function computeBBox(svg) {
125
+ const xs = [];
126
+ const ys = [];
127
+ function push(x, y) {
128
+ if (Number.isFinite(x) && Number.isFinite(y)) {
129
+ xs.push(x);
130
+ ys.push(y);
131
+ }
132
+ }
133
+ function attr(tag, name) {
134
+ const m = tag.match(new RegExp(`\\b${name}="([^"]*)"`));
135
+ if (!m) return null;
136
+ const n = parseFloat(m[1]);
137
+ return Number.isFinite(n) ? n : null;
138
+ }
139
+ for (const m of svg.matchAll(/<rect\b[^>]*?\/?>/g)) {
140
+ const tag = m[0];
141
+ const x = attr(tag, "x");
142
+ const y = attr(tag, "y");
143
+ const w = attr(tag, "width");
144
+ const h = attr(tag, "height");
145
+ if (x !== null && y !== null && w !== null && h !== null) {
146
+ push(x, y);
147
+ push(x + w, y + h);
148
+ }
149
+ }
150
+ for (const m of svg.matchAll(/<line\b[^>]*?\/?>/g)) {
151
+ const tag = m[0];
152
+ const x1 = attr(tag, "x1");
153
+ const y1 = attr(tag, "y1");
154
+ const x2 = attr(tag, "x2");
155
+ const y2 = attr(tag, "y2");
156
+ if (x1 !== null && y1 !== null && x2 !== null && y2 !== null) {
157
+ push(x1, y1);
158
+ push(x2, y2);
159
+ }
160
+ }
161
+ for (const m of svg.matchAll(/<circle\b[^>]*?\/?>/g)) {
162
+ const tag = m[0];
163
+ const cx = attr(tag, "cx");
164
+ const cy = attr(tag, "cy");
165
+ const r = attr(tag, "r");
166
+ if (cx !== null && cy !== null && r !== null) {
167
+ push(cx - r, cy - r);
168
+ push(cx + r, cy + r);
169
+ }
170
+ }
171
+ for (const m of svg.matchAll(/<ellipse\b[^>]*?\/?>/g)) {
172
+ const tag = m[0];
173
+ const cx = attr(tag, "cx");
174
+ const cy = attr(tag, "cy");
175
+ const rx = attr(tag, "rx");
176
+ const ry = attr(tag, "ry");
177
+ if (cx !== null && cy !== null && rx !== null && ry !== null) {
178
+ push(cx - rx, cy - ry);
179
+ push(cx + rx, cy + ry);
180
+ }
181
+ }
182
+ for (const m of svg.matchAll(/<text\b([^>]*?)>([\s\S]*?)<\/text>/g)) {
183
+ const tag = `<text${m[1]}>`;
184
+ const text = m[2].replace(/<[^>]+>/g, "");
185
+ const x = attr(tag, "x");
186
+ const y = attr(tag, "y");
187
+ if (x !== null && y !== null) {
188
+ const fontSize = attr(tag, "font-size") ?? (() => {
189
+ const s = tag.match(/font-size:\s*([\d.]+)/);
190
+ return s ? parseFloat(s[1]) : 14;
191
+ })();
192
+ const w = text.length * fontSize * 0.5;
193
+ const anchor = tag.match(/\btext-anchor="([^"]*)"/)?.[1] ?? "start";
194
+ const left = anchor === "middle" ? x - w / 2 : anchor === "end" ? x - w : x;
195
+ const right = anchor === "middle" ? x + w / 2 : anchor === "end" ? x : x + w;
196
+ push(left, y - fontSize * 0.9);
197
+ push(right, y + fontSize * 0.25);
198
+ }
199
+ }
200
+ for (const m of svg.matchAll(/<path\b[^>]*?>/g)) {
201
+ const tag = m[0];
202
+ if (tag.includes("data-embed-ignore-bbox")) continue;
203
+ const dm = tag.match(/\bd="([^"]+)"/);
204
+ if (!dm) continue;
205
+ const d = dm[1];
206
+ const nums = d.match(/-?\d+(?:\.\d+)?/g);
207
+ if (!nums) continue;
208
+ for (let i = 0; i + 1 < nums.length; i += 2) {
209
+ push(parseFloat(nums[i]), parseFloat(nums[i + 1]));
210
+ }
211
+ }
212
+ for (const m of svg.matchAll(
213
+ /<(?:polygon|polyline)\b[^>]*?\bpoints="([^"]+)"/g
214
+ )) {
215
+ const nums = m[1].match(/-?\d+(?:\.\d+)?/g);
216
+ if (!nums) continue;
217
+ for (let i = 0; i + 1 < nums.length; i += 2) {
218
+ push(parseFloat(nums[i]), parseFloat(nums[i + 1]));
219
+ }
220
+ }
221
+ if (xs.length === 0 || ys.length === 0) return null;
222
+ const minX = Math.min(...xs);
223
+ const maxX = Math.max(...xs);
224
+ const minY = Math.min(...ys);
225
+ const maxY = Math.max(...ys);
226
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
227
+ }
228
+
229
+ // src/countdown/renderer.ts
230
+ import * as d3Selection from "d3-selection";
231
+
232
+ // src/fonts.ts
233
+ var FONT_FAMILY = "Inter, system-ui, Avenir, Helvetica, Arial, sans-serif";
234
+ var MONO_FAMILY = "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace";
235
+
236
+ // src/colors.ts
237
+ var nord = {
238
+ // Polar Night (dark)
239
+ nord0: "#2e3440",
240
+ nord1: "#3b4252",
241
+ nord2: "#434c5e",
242
+ nord3: "#4c566a",
243
+ // Snow Storm (light)
244
+ nord4: "#d8dee9",
245
+ nord5: "#e5e9f0",
246
+ nord6: "#eceff4",
247
+ // Frost (accent blues)
248
+ nord7: "#8fbcbb",
249
+ nord8: "#88c0d0",
250
+ nord9: "#81a1c1",
251
+ nord10: "#5e81ac",
252
+ // Aurora (colors)
253
+ nord11: "#bf616a",
254
+ // red
255
+ nord12: "#d08770",
256
+ // orange
257
+ nord13: "#ebcb8b",
258
+ // yellow
259
+ nord14: "#a3be8c",
260
+ // green
261
+ nord15: "#b48ead"
262
+ // purple
263
+ };
264
+ var colorNames = {
265
+ red: nord.nord11,
266
+ orange: nord.nord12,
267
+ yellow: nord.nord13,
268
+ green: nord.nord14,
269
+ blue: nord.nord10,
270
+ purple: nord.nord15,
271
+ teal: nord.nord7,
272
+ cyan: nord.nord8,
273
+ gray: nord.nord3,
274
+ black: nord.nord0,
275
+ white: nord.nord6
276
+ };
277
+ var RECOGNIZED_COLOR_NAMES = Object.freeze([
278
+ "red",
279
+ "orange",
280
+ "yellow",
281
+ "green",
282
+ "blue",
283
+ "purple",
284
+ "teal",
285
+ "cyan",
286
+ "gray",
287
+ "black",
288
+ "white"
289
+ ]);
290
+ var CATEGORICAL_COLOR_ORDER = Object.freeze([
291
+ "red",
292
+ "green",
293
+ "blue",
294
+ "yellow",
295
+ "teal",
296
+ "purple",
297
+ "orange",
298
+ "cyan"
299
+ ]);
300
+ function isRecognizedColorName(name) {
301
+ return Object.prototype.hasOwnProperty.call(colorNames, name.toLowerCase());
302
+ }
303
+ function resolveColor(color, palette) {
304
+ if (!color) return null;
305
+ if (color.startsWith("#")) return null;
306
+ const lower = color.toLowerCase();
307
+ if (!isRecognizedColorName(lower)) return null;
308
+ if (palette) {
309
+ const named = palette.colors[lower];
310
+ if (named) return named;
311
+ }
312
+ return colorNames[lower] ?? null;
313
+ }
314
+ var INVALID_CSS_COLOR_HEX = Object.freeze({
315
+ pink: "#ffc0cb",
316
+ hotpink: "#ff69b4",
317
+ deeppink: "#ff1493",
318
+ lightpink: "#ffb6c1",
319
+ palevioletred: "#db7093",
320
+ crimson: "#dc143c",
321
+ scarlet: "#ff2400",
322
+ firebrick: "#b22222",
323
+ darkred: "#8b0000",
324
+ maroon: "#800000",
325
+ salmon: "#fa8072",
326
+ lightsalmon: "#ffa07a",
327
+ darksalmon: "#e9967a",
328
+ coral: "#ff7f50",
329
+ lightcoral: "#f08080",
330
+ tomato: "#ff6347",
331
+ orangered: "#ff4500",
332
+ darkorange: "#ff8c00",
333
+ gold: "#ffd700",
334
+ goldenrod: "#daa520",
335
+ darkgoldenrod: "#b8860b",
336
+ khaki: "#f0e68c",
337
+ darkkhaki: "#bdb76b",
338
+ amber: "#ffbf00",
339
+ lavender: "#e6e6fa",
340
+ violet: "#ee82ee",
341
+ magenta: "#ff00ff",
342
+ fuchsia: "#ff00ff",
343
+ orchid: "#da70d6",
344
+ plum: "#dda0dd",
345
+ indigo: "#4b0082",
346
+ navy: "#000080",
347
+ midnightblue: "#191970",
348
+ darkblue: "#00008b",
349
+ mediumblue: "#0000cd",
350
+ royalblue: "#4169e1",
351
+ cornflowerblue: "#6495ed",
352
+ dodgerblue: "#1e90ff",
353
+ deepskyblue: "#00bfff",
354
+ skyblue: "#87ceeb",
355
+ lightskyblue: "#87cefa",
356
+ lightblue: "#add8e6",
357
+ powderblue: "#b0e0e6",
358
+ steelblue: "#4682b4",
359
+ slateblue: "#6a5acd",
360
+ cadetblue: "#5f9ea0",
361
+ turquoise: "#40e0d0",
362
+ aqua: "#00ffff",
363
+ aquamarine: "#7fffd4",
364
+ lime: "#00ff00",
365
+ limegreen: "#32cd32",
366
+ lightgreen: "#90ee90",
367
+ palegreen: "#98fb98",
368
+ seagreen: "#2e8b57",
369
+ mediumseagreen: "#3cb371",
370
+ forestgreen: "#228b22",
371
+ darkgreen: "#006400",
372
+ olive: "#808000",
373
+ olivedrab: "#6b8e23",
374
+ darkolivegreen: "#556b2f",
375
+ chartreuse: "#7fff00",
376
+ lawngreen: "#7cfc00",
377
+ springgreen: "#00ff7f",
378
+ greenyellow: "#adff2f",
379
+ brown: "#a52a2a",
380
+ sienna: "#a0522d",
381
+ chocolate: "#d2691e",
382
+ peru: "#cd853f",
383
+ tan: "#d2b48c",
384
+ beige: "#f5f5dc",
385
+ wheat: "#f5deb3",
386
+ ivory: "#fffff0",
387
+ silver: "#c0c0c0",
388
+ lightgray: "#d3d3d3",
389
+ lightgrey: "#d3d3d3",
390
+ darkgray: "#a9a9a9",
391
+ darkgrey: "#a9a9a9",
392
+ dimgray: "#696969",
393
+ dimgrey: "#696969",
394
+ slategray: "#708090",
395
+ slategrey: "#708090",
396
+ gainsboro: "#dcdcdc",
397
+ grey: "#808080"
398
+ });
399
+ var seriesColors = [
400
+ nord.nord10,
401
+ // blue
402
+ nord.nord14,
403
+ // green
404
+ nord.nord13,
405
+ // yellow
406
+ nord.nord12,
407
+ // orange
408
+ nord.nord15,
409
+ // purple
410
+ nord.nord11,
411
+ // red
412
+ nord.nord7,
413
+ // teal
414
+ nord.nord8
415
+ // light blue
416
+ ];
417
+
418
+ // src/palettes/color-utils.ts
419
+ var MIX_CACHE = /* @__PURE__ */ new Map();
420
+ var MIX_CACHE_MAX = 5e3;
421
+ function mix(a, b, pct) {
422
+ const key = `${a}|${b}|${pct}`;
423
+ const cached = MIX_CACHE.get(key);
424
+ if (cached !== void 0) return cached;
425
+ const parse = (h) => {
426
+ const r = h.replace("#", "");
427
+ const f = r.length === 3 ? [...r].map((c2) => c2 + c2).join("") : r;
428
+ return [
429
+ parseInt(f.substring(0, 2), 16),
430
+ parseInt(f.substring(2, 4), 16),
431
+ parseInt(f.substring(4, 6), 16)
432
+ ];
433
+ };
434
+ const [ar, ag, ab] = parse(a), [br, bg, bb] = parse(b), t = pct / 100;
435
+ const c = (x, y) => Math.round(x * t + y * (1 - t)).toString(16).padStart(2, "0");
436
+ const out = `#${c(ar, br)}${c(ag, bg)}${c(ab, bb)}`;
437
+ if (MIX_CACHE.size >= MIX_CACHE_MAX) MIX_CACHE.clear();
438
+ MIX_CACHE.set(key, out);
439
+ return out;
440
+ }
441
+ var APCA = {
442
+ mainTRC: 2.4,
443
+ sRco: 0.2126729,
444
+ sGco: 0.7151522,
445
+ sBco: 0.072175,
446
+ normBG: 0.56,
447
+ normTXT: 0.57,
448
+ revTXT: 0.62,
449
+ revBG: 0.65,
450
+ blkThrs: 0.022,
451
+ blkClmp: 1.414,
452
+ scale: 1.14,
453
+ loOffset: 0.027,
454
+ deltaYmin: 5e-4,
455
+ loClip: 0.1
456
+ };
457
+ function apcaY(hex) {
458
+ const raw = hex.replace("#", "");
459
+ const full = raw.length === 3 ? [...raw].map((c) => c + c).join("") : raw;
460
+ const [r, g, b] = [0, 2, 4].map(
461
+ (i) => (parseInt(full.substring(i, i + 2), 16) / 255) ** APCA.mainTRC
462
+ );
463
+ return APCA.sRco * r + APCA.sGco * g + APCA.sBco * b;
464
+ }
465
+ function apcaContrast(text, bg) {
466
+ const clamp = (y) => y > APCA.blkThrs ? y : y + (APCA.blkThrs - y) ** APCA.blkClmp;
467
+ const txtY = clamp(apcaY(text));
468
+ const bgY = clamp(apcaY(bg));
469
+ if (Math.abs(bgY - txtY) < APCA.deltaYmin) return 0;
470
+ if (bgY > txtY) {
471
+ const sapc2 = (bgY ** APCA.normBG - txtY ** APCA.normTXT) * APCA.scale;
472
+ return sapc2 < APCA.loClip ? 0 : (sapc2 - APCA.loOffset) * 100;
473
+ }
474
+ const sapc = (bgY ** APCA.revBG - txtY ** APCA.revTXT) * APCA.scale;
475
+ return sapc > -APCA.loClip ? 0 : (sapc + APCA.loOffset) * 100;
476
+ }
477
+ function contrastText(bg, lightText, darkText) {
478
+ return Math.abs(apcaContrast(darkText, bg)) >= Math.abs(apcaContrast(lightText, bg)) ? darkText : lightText;
479
+ }
480
+ function themeBaseBg(palette, isDark) {
481
+ return isDark ? palette.surface : palette.bg;
482
+ }
483
+ function getSeriesColors(palette) {
484
+ const c = palette.colors;
485
+ return CATEGORICAL_COLOR_ORDER.map((name) => c[name]);
486
+ }
487
+
488
+ // src/utils/safe-href.ts
489
+ var ALLOWED_SCHEMES = ["http", "https", "mailto"];
490
+ var SCHEME_RE = /^([a-z][a-z0-9+.-]*):/i;
491
+ var LEADING_TRIM_RE = /^[\x00-\x20]+/;
492
+ var URL_STRIP_RE = /[\x09\x0A\x0D]/g;
493
+ function safeHref(url) {
494
+ if (typeof url !== "string") return null;
495
+ const trimmed = url.replace(URL_STRIP_RE, "").replace(LEADING_TRIM_RE, "");
496
+ if (trimmed.length === 0) return null;
497
+ const match = SCHEME_RE.exec(trimmed);
498
+ if (!match) {
499
+ return url;
500
+ }
501
+ const scheme = match[1].toLowerCase();
502
+ if (ALLOWED_SCHEMES.includes(scheme)) {
503
+ return url;
504
+ }
505
+ return null;
506
+ }
507
+
508
+ // src/utils/inline-markdown.ts
509
+ function parseInlineMarkdown(text) {
510
+ const spans = [];
511
+ const regex = /\*\*(.+?)\*\*|__(.+?)__|\*(.+?)\*|_(.+?)_|`(.+?)`|\[(.+?)\]\((.+?)\)|(https?:\/\/[^\s)>\]]+|www\.[^\s)>\]]+)|([^*_`[]+?(?=https?:\/\/|www\.|$)|[^*_`[]+)/g;
512
+ let match;
513
+ while ((match = regex.exec(text)) !== null) {
514
+ if (match[1])
515
+ spans.push({ text: match[1], bold: true });
516
+ else if (match[2])
517
+ spans.push({ text: match[2], bold: true });
518
+ else if (match[3])
519
+ spans.push({ text: match[3], italic: true });
520
+ else if (match[4])
521
+ spans.push({ text: match[4], italic: true });
522
+ else if (match[5])
523
+ spans.push({ text: match[5], code: true });
524
+ else if (match[6])
525
+ spans.push({
526
+ text: match[6],
527
+ ...match[7] !== void 0 && { href: match[7] }
528
+ });
529
+ else if (match[8]) {
530
+ const url = match[8];
531
+ const href = url.startsWith("www.") ? `https://${url}` : url;
532
+ spans.push({ text: url, href });
533
+ } else if (match[9]) spans.push({ text: match[9] });
534
+ }
535
+ return spans;
536
+ }
537
+ var BARE_URL_MAX_DISPLAY = 35;
538
+ function truncateBareUrl(url) {
539
+ const stripped = url.replace(/^https?:\/\//, "").replace(/^www\./, "");
540
+ if (stripped.length <= BARE_URL_MAX_DISPLAY) return stripped;
541
+ return stripped.slice(0, BARE_URL_MAX_DISPLAY - 1) + "\u2026";
542
+ }
543
+ function stripInlineMarkdown(text) {
544
+ return text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1").replace(
545
+ /https?:\/\/[^\s)>\]]+|www\.[^\s)>\]]+/g,
546
+ (u) => truncateBareUrl(u)
547
+ ).replace(/\*\*|__|[*_`]/g, "");
548
+ }
549
+ function renderInlineText(textEl, text, palette, fontSize) {
550
+ const spans = parseInlineMarkdown(text);
551
+ for (const span of spans) {
552
+ if (span.href) {
553
+ const isBareUrl = span.text === span.href || `https://${span.text}` === span.href;
554
+ const display = isBareUrl ? truncateBareUrl(span.text) : span.text;
555
+ const safe = safeHref(span.href);
556
+ if (safe !== null) {
557
+ const a = textEl.append("a").attr("href", safe);
558
+ a.append("tspan").text(display).attr("fill", palette.primary).style("text-decoration", "underline");
559
+ } else {
560
+ textEl.append("tspan").text(display).attr("fill", palette.primary).style("text-decoration", "underline");
561
+ }
562
+ } else {
563
+ const tspan = textEl.append("tspan").text(span.text);
564
+ if (span.bold) tspan.attr("font-weight", "bold");
565
+ if (span.italic) tspan.attr("font-style", "italic");
566
+ if (span.code) {
567
+ tspan.attr("font-family", MONO_FAMILY);
568
+ if (fontSize) tspan.attr("font-size", fontSize - 1);
569
+ }
570
+ }
571
+ }
572
+ }
573
+
574
+ // src/utils/wrapped-desc.ts
575
+ var BULLET_PREFIX = "\u2022 ";
576
+ var BULLET_INDENT_CHARS = 2;
577
+ var HARD_BREAK = "\\";
578
+ function reflowDescriptionLines(lines) {
579
+ const blocks = [];
580
+ let open = null;
581
+ let gapPending = false;
582
+ let breakPending = false;
583
+ const openBlock = (kind, text) => {
584
+ const block = {
585
+ kind,
586
+ segments: [text],
587
+ gapBefore: gapPending && blocks.length > 0
588
+ };
589
+ blocks.push(block);
590
+ gapPending = false;
591
+ return block;
592
+ };
593
+ for (const raw of lines) {
594
+ const line = raw.trimEnd();
595
+ if (!line) {
596
+ open = null;
597
+ breakPending = false;
598
+ gapPending = blocks.length > 0;
599
+ continue;
600
+ }
601
+ const hard = line.endsWith(HARD_BREAK);
602
+ const text = hard ? line.slice(0, -1).trimEnd() : line;
603
+ if (!text) {
604
+ breakPending = hard;
605
+ continue;
606
+ }
607
+ if (text.startsWith(BULLET_PREFIX)) {
608
+ open = openBlock("bullet", text.slice(BULLET_PREFIX.length));
609
+ } else if (open) {
610
+ if (breakPending) open.segments.push(text);
611
+ else open.segments[open.segments.length - 1] += ` ${text}`;
612
+ } else {
613
+ open = openBlock("plain", text);
614
+ }
615
+ breakPending = hard;
616
+ }
617
+ return blocks;
618
+ }
619
+ function wrapDescriptionLines(lines, charsPerLine, lengthFn = (s) => s.length) {
620
+ const measure = (s) => lengthFn(stripInlineMarkdown(s));
621
+ const result = [];
622
+ for (const block of reflowDescriptionLines(lines)) {
623
+ if (block.gapBefore && result.length > 0)
624
+ result.push({ text: "", kind: "plain" });
625
+ if (block.kind === "plain") {
626
+ for (const segment of block.segments)
627
+ for (const w of wrapPlainLine(segment, charsPerLine, measure))
628
+ result.push({ text: w, kind: "plain" });
629
+ continue;
630
+ }
631
+ const bodyLimit = Math.max(8, charsPerLine - BULLET_INDENT_CHARS);
632
+ let first = true;
633
+ for (const segment of block.segments) {
634
+ for (const w of wrapPlainLine(segment, bodyLimit, measure)) {
635
+ result.push({ text: w, kind: first ? "bullet-first" : "bullet-cont" });
636
+ first = false;
637
+ }
638
+ }
639
+ }
640
+ return result;
641
+ }
642
+ function wrapPlainLine(line, charsPerLine, lengthFn) {
643
+ const words = line.split(/\s+/);
644
+ const result = [];
645
+ let current = "";
646
+ for (const word of words) {
647
+ const test = current ? `${current} ${word}` : word;
648
+ if (lengthFn(test) > charsPerLine && current) {
649
+ result.push(current);
650
+ current = word;
651
+ } else {
652
+ current = test;
653
+ }
654
+ }
655
+ if (current) result.push(current);
656
+ return result;
657
+ }
658
+
659
+ // src/countdown/renderer.ts
660
+ function bakedHero(parsed, now2) {
661
+ const resolved = parsed.resolvedMs;
662
+ if (resolved === null) return "\u2014";
663
+ const tz = parsed.tz;
664
+ const remaining = resolved - now2;
665
+ if (!parsed.rule && remaining <= 0) {
666
+ if (parsed.expired !== null) return parsed.expired;
667
+ const elapsed = -remaining;
668
+ if (elapsed <= 0) return "Now!";
669
+ if (parsed.units === "compound" || parsed.units === "human") {
670
+ const [a, b] = parsed.hasTime ? [resolved, now2] : [dayStart(resolved, tz), dayStart(now2, tz)];
671
+ return `${formatCompound(a, b, 2, tz)} ago`;
672
+ }
673
+ const bu = parsed.units === "full" || parsed.units === "clock" ? "days" : parsed.units;
674
+ return `${formatCount(elapsed, { units: bu, round: parsed.round, fields: parsed.fields })} ago`;
675
+ }
676
+ if (parsed.rule && sameDay(resolved, now2, tz))
677
+ return parsed.onDay ?? "Today!";
678
+ if (parsed.units === "human") {
679
+ const [hn, ht] = parsed.hasTime ? [now2, resolved] : [dayStart(now2, tz), dayStart(resolved, tz)];
680
+ return formatHuman(hn, ht, tz).big;
681
+ }
682
+ if (parsed.units === "compound") return formatCompound(now2, resolved, 2, tz);
683
+ const bakeUnits = parsed.units === "full" || parsed.units === "clock" ? "days" : parsed.units;
684
+ return formatCount(Math.max(0, remaining), {
685
+ units: bakeUnits,
686
+ round: parsed.round,
687
+ fields: parsed.fields
688
+ });
689
+ }
690
+ function stampRecur(sel, parsed) {
691
+ const r = parsed.rule;
692
+ if (!r) return;
693
+ sel.attr("data-dgmo-recur-kind", r.kind);
694
+ if (r.month !== void 0) sel.attr("data-dgmo-recur-month", r.month);
695
+ if (r.day !== void 0) sel.attr("data-dgmo-recur-day", r.day);
696
+ if (r.nth !== void 0) sel.attr("data-dgmo-recur-nth", r.nth);
697
+ if (r.weekday !== void 0) sel.attr("data-dgmo-recur-weekday", r.weekday);
698
+ sel.attr("data-dgmo-recur-hour", r.hour);
699
+ sel.attr("data-dgmo-recur-minute", r.minute);
700
+ sel.attr("data-dgmo-recur-allday", r.allDay ? "1" : "0");
701
+ if (r.intervalN !== void 0)
702
+ sel.attr("data-dgmo-recur-interval-n", r.intervalN);
703
+ if (r.intervalUnit !== void 0)
704
+ sel.attr("data-dgmo-recur-interval-unit", r.intervalUnit);
705
+ if (r.anchorMs !== void 0) sel.attr("data-dgmo-recur-anchor", r.anchorMs);
706
+ if (r.tz !== void 0) sel.attr("data-dgmo-recur-tz", r.tz);
707
+ }
708
+ var MON_ABBR = [
709
+ "Jan",
710
+ "Feb",
711
+ "Mar",
712
+ "Apr",
713
+ "May",
714
+ "Jun",
715
+ "Jul",
716
+ "Aug",
717
+ "Sep",
718
+ "Oct",
719
+ "Nov",
720
+ "Dec"
721
+ ];
722
+ var WD_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
723
+ var DAY_START = (ms) => {
724
+ const d = new Date(ms);
725
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
726
+ };
727
+ var dayDelta = (a, b) => Math.round((DAY_START(b) - DAY_START(a)) / DAY_MS);
728
+ var keyOf = (ms) => {
729
+ const d = new Date(ms);
730
+ return d.getFullYear() * 1e4 + d.getMonth() * 100 + d.getDate();
731
+ };
732
+ function bandColors(palette, accent, muted, faint, isDark, fillMode) {
733
+ const blue = resolveColor("blue", palette) ?? mix(palette.text, palette.bg, 45);
734
+ const collides = accent.toLowerCase() === blue.toLowerCase();
735
+ const now2 = collides ? resolveColor("teal", palette) ?? resolveColor("cyan", palette) ?? mix(palette.text, palette.bg, 55) : blue;
736
+ const mid = mix(accent, now2, 50);
737
+ return {
738
+ bg: palette.bg,
739
+ text: palette.text,
740
+ muted,
741
+ faint,
742
+ rule: mix(palette.text, palette.bg, 14),
743
+ now: now2,
744
+ event: accent,
745
+ midSoft: mix(mid, palette.bg, 24),
746
+ inert: mix(palette.text, palette.bg, 7),
747
+ inertBorder: mix(palette.text, palette.bg, 22),
748
+ fillMode,
749
+ baseBg: themeBaseBg(palette, isDark),
750
+ onLight: palette.textOnFillLight,
751
+ onDark: palette.textOnFillDark
752
+ };
753
+ }
754
+ function roleStyle(C, role) {
755
+ switch (role) {
756
+ case "past":
757
+ return { fill: C.inert, stroke: C.inertBorder, text: C.faint };
758
+ case "future":
759
+ return { fill: C.inert, stroke: C.inertBorder, text: C.muted };
760
+ case "today":
761
+ return chipStyle(C, C.now);
762
+ case "event":
763
+ return chipStyle(C, C.event);
764
+ }
765
+ }
766
+ function chipStyle(C, color) {
767
+ if (C.fillMode === "outline")
768
+ return { fill: C.baseBg, stroke: color, text: color };
769
+ if (C.fillMode === "solid")
770
+ return {
771
+ fill: color,
772
+ stroke: color,
773
+ text: contrastText(color, C.onLight, C.onDark)
774
+ };
775
+ return { fill: color, stroke: color, text: C.bg };
776
+ }
777
+ function gradStyle(C, frac) {
778
+ const t = Math.max(0, Math.min(1, frac));
779
+ const f = mix(C.event, C.now, Math.round(t * 100));
780
+ return chipStyle(C, f);
781
+ }
782
+ function aText(svg, x, y, size, fill, weight, anchor, txt, extra) {
783
+ const t = svg.append("text").attr("x", x).attr("y", y).attr("text-anchor", anchor).attr("font-size", size).attr("font-weight", weight).attr("fill", fill).attr("font-family", FONT_FAMILY).attr("font-variant-numeric", "tabular-nums").text(txt);
784
+ if (extra) for (const k in extra) t.attr(k, extra[k]);
785
+ }
786
+ function aRect(svg, x, y, w, h, rx, fill, stroke, sw) {
787
+ const r = svg.append("rect").attr("x", x).attr("y", y).attr("width", w).attr("height", h).attr("rx", rx).attr("fill", fill);
788
+ if (stroke) r.attr("stroke", stroke).attr("stroke-width", sw ?? 1);
789
+ }
790
+ function aLine(svg, x1, y1, x2, y2, stroke, sw, opacity) {
791
+ const l = svg.append("line").attr("x1", x1).attr("y1", y1).attr("x2", x2).attr("y2", y2).attr("stroke", stroke).attr("stroke-width", sw);
792
+ if (opacity != null) l.attr("opacity", opacity);
793
+ }
794
+ var REPEAT_ICON_PATH = "m17 2 4 4-4 4 M3 11v-1a4 4 0 0 1 4-4h14 M7 22l-4-4 4-4 M21 13v1a4 4 0 0 1-4 4H3";
795
+ function recurGlyph(svg, cx, cy, r, color) {
796
+ const side = r * 2.4;
797
+ const scale = side / 24;
798
+ const swPx = Math.max(1.5, r * 0.34);
799
+ svg.append("path").attr("d", REPEAT_ICON_PATH).attr(
800
+ "transform",
801
+ `translate(${cx - side / 2} ${cy - side / 2}) scale(${scale})`
802
+ ).attr("data-embed-ignore-bbox", "").attr("fill", "none").attr("stroke", color).attr("stroke-width", swPx / scale).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
803
+ }
804
+ function haloRect(svg, x, y, w, h, rx, C) {
805
+ aRect(svg, x - 1, y - 1, w + 2, h + 2, rx + 1, "none", C.bg, 2);
806
+ svg.append("rect").attr("class", "dgmo-countdown-target-ring").attr("x", x - 3).attr("y", y - 3).attr("width", w + 6).attr("height", h + 6).attr("rx", rx + 3).attr("fill", "none").attr("stroke", C.event).attr("stroke-width", 2);
807
+ }
808
+ function pickBand(nowMs, resolvedMs, hasTime) {
809
+ const days = dayDelta(nowMs, resolvedMs);
810
+ if (hasTime && days <= 0) return "clock";
811
+ if (days === 0) return "today";
812
+ const mag = Math.abs(days);
813
+ if (mag > 3 * 365) return "years";
814
+ if (mag > 365) return "year";
815
+ if (mag > 92) return "months";
816
+ if (mag > 7) return "monthgrid";
817
+ return "weekstrip";
818
+ }
819
+ function monthSpan(nowMs, resolvedMs) {
820
+ const n = new Date(nowMs);
821
+ const t = new Date(resolvedMs);
822
+ return Math.abs(
823
+ (t.getFullYear() - n.getFullYear()) * 12 + (t.getMonth() - n.getMonth())
824
+ ) + 1;
825
+ }
826
+ var MONTH_TARGET_PX = 200;
827
+ function monthGridLayout(nowMs, resolvedMs, contentW) {
828
+ const now2 = new Date(nowMs);
829
+ const ev = new Date(resolvedMs);
830
+ const realSpan = monthSpan(nowMs, resolvedMs);
831
+ const shown = Math.max(realSpan, Math.round(contentW / MONTH_TARGET_PX));
832
+ const padBefore = Math.floor((shown - realSpan) / 2);
833
+ const nowMk = now2.getFullYear() * 12 + now2.getMonth();
834
+ const evMk = ev.getFullYear() * 12 + ev.getMonth();
835
+ const loMk = Math.min(nowMk, evMk);
836
+ const hiMk = Math.max(nowMk, evMk);
837
+ const startMk = loMk - padBefore;
838
+ const months = [];
839
+ const dim = [];
840
+ for (let i = 0; i < shown; i++) {
841
+ const d = new Date(Math.floor(startMk / 12), startMk % 12 + i, 1);
842
+ const mk = d.getFullYear() * 12 + d.getMonth();
843
+ months.push(d);
844
+ dim.push(mk < loMk || mk > hiMk);
845
+ }
846
+ return { months, dim };
847
+ }
848
+ function yearsLayout(nowMs, resolvedMs) {
849
+ const y0 = new Date(nowMs).getFullYear();
850
+ const y1 = new Date(resolvedMs).getFullYear();
851
+ const d0 = Math.floor(Math.min(y0, y1) / 10) * 10;
852
+ const d1 = Math.floor(Math.max(y0, y1) / 10) * 10;
853
+ return { d0, d1, rows: (d1 - d0) / 10 + 1 };
854
+ }
855
+ function bandHeightFor(kind, nowMs, resolvedMs, contentW) {
856
+ const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
857
+ switch (kind) {
858
+ case "years": {
859
+ const { rows } = yearsLayout(nowMs, resolvedMs);
860
+ const rowGap = 11;
861
+ const labW = 54;
862
+ const gap = 7;
863
+ const cellW = (contentW - labW - 10 * gap) / 10;
864
+ let cellH = clamp(cellW * 0.5, 26, 40);
865
+ const MAX = 380;
866
+ let total = rows * cellH + (rows - 1) * rowGap;
867
+ if (total > MAX) {
868
+ cellH = Math.max(9, (MAX - (rows - 1) * rowGap) / rows);
869
+ total = rows * cellH + (rows - 1) * rowGap;
870
+ }
871
+ return total;
872
+ }
873
+ case "year": {
874
+ const R = Math.abs(
875
+ new Date(resolvedMs).getFullYear() - new Date(nowMs).getFullYear()
876
+ ) + 1;
877
+ const yearW = 66;
878
+ const gap = 8;
879
+ const cellW = (contentW - yearW - 11 * gap) / 12;
880
+ const cellH = clamp(cellW * 0.6, 28, 44);
881
+ const rowGap = 12;
882
+ return R * cellH + (R - 1) * rowGap;
883
+ }
884
+ case "months": {
885
+ const realSpan = monthSpan(nowMs, resolvedMs);
886
+ const perRow = 6;
887
+ const shown = Math.max(perRow, Math.ceil(realSpan / perRow) * perRow);
888
+ const rows = shown / perRow;
889
+ const cellW = (contentW - 16 * (perRow - 1)) / perRow;
890
+ const cellH = clamp(cellW * 0.5, 58, 84);
891
+ return rows * cellH + (rows - 1) * 16;
892
+ }
893
+ case "monthgrid": {
894
+ const { months } = monthGridLayout(nowMs, resolvedMs, contentW);
895
+ const nM = months.length;
896
+ const mw = (contentW - 20 * (nM - 1)) / nM;
897
+ const cellW = (mw - 20) / 7;
898
+ const cellH = clamp(cellW * 0.8, 15, 22);
899
+ return 26 + 6 * cellH + 8;
900
+ }
901
+ case "weekstrip": {
902
+ const n = Math.max(7, Math.abs(dayDelta(nowMs, resolvedMs)) + 1);
903
+ const cw = (contentW - 10 * (n - 1)) / n;
904
+ return clamp(cw * 1.5, 116, 170);
905
+ }
906
+ case "clock":
907
+ return clamp(contentW * 0.26, 150, 205);
908
+ case "today":
909
+ return clamp(contentW * 0.22, 125, 170);
910
+ }
911
+ }
912
+ function vizYears(svg, g, nowMs, resolvedMs, C) {
913
+ const now2 = new Date(nowMs);
914
+ const ev = new Date(resolvedMs);
915
+ const nowY = now2.getFullYear();
916
+ const evY = ev.getFullYear();
917
+ const { d0, rows } = yearsLayout(nowMs, resolvedMs);
918
+ const labW = 54;
919
+ const gap = 7;
920
+ const rowGap = 11;
921
+ const rowH = (g.height - rowGap * (rows - 1)) / rows;
922
+ const cellH = rowH;
923
+ const cellW = (g.contentW - labW - 10 * gap) / 10;
924
+ const rx = Math.min(6, Math.min(cellW, cellH) * 0.22);
925
+ const showLabel = cellH >= 18 && cellW >= 22;
926
+ const yrFont = Math.min(12, cellW * 0.34, cellH * 0.5);
927
+ for (let r = 0; r < rows; r++) {
928
+ const dec = d0 + r * 10;
929
+ const ry = g.top + r * (rowH + rowGap);
930
+ aText(
931
+ svg,
932
+ g.left + labW - 12,
933
+ ry + rowH / 2,
934
+ Math.min(14, rowH * 0.5),
935
+ C.muted,
936
+ "bold",
937
+ "end",
938
+ `${dec}s`,
939
+ { "dominant-baseline": "central" }
940
+ );
941
+ for (let i = 0; i < 10; i++) {
942
+ const yr = dec + i;
943
+ const before = yr < nowY;
944
+ const isNow = yr === nowY;
945
+ const isEv = yr === evY;
946
+ const between = (yr - nowY) * (yr - evY) < 0;
947
+ const st = isEv ? roleStyle(C, "event") : isNow ? roleStyle(C, "today") : between ? gradStyle(C, (yr - nowY) / (evY - nowY)) : roleStyle(C, before ? "past" : "future");
948
+ const x = g.left + labW + i * (cellW + gap);
949
+ aRect(svg, x, ry, cellW, cellH, rx, st.fill, st.stroke, 1);
950
+ if (isEv) haloRect(svg, x, ry, cellW, cellH, rx, C);
951
+ if (showLabel) {
952
+ aText(
953
+ svg,
954
+ x + cellW / 2,
955
+ ry + cellH / 2,
956
+ yrFont,
957
+ st.text,
958
+ "bold",
959
+ "middle",
960
+ String(yr),
961
+ { "dominant-baseline": "central" }
962
+ );
963
+ }
964
+ }
965
+ }
966
+ }
967
+ function vizYear(svg, g, nowMs, resolvedMs, C) {
968
+ const now2 = new Date(nowMs);
969
+ const ev = new Date(resolvedMs);
970
+ const y0 = Math.min(now2.getFullYear(), ev.getFullYear());
971
+ const y1 = Math.max(now2.getFullYear(), ev.getFullYear());
972
+ const years = [];
973
+ for (let y = y0; y <= y1; y++) years.push(y);
974
+ const R = years.length;
975
+ const yearW = 66;
976
+ const gap = 8;
977
+ const rowGap = 12;
978
+ const rowH = (g.height - rowGap * (R - 1)) / R;
979
+ const cellH = rowH;
980
+ const cellW = (g.contentW - yearW - 11 * gap) / 12;
981
+ const nowY = now2.getFullYear();
982
+ const nowM = now2.getMonth();
983
+ const evY = ev.getFullYear();
984
+ const evM = ev.getMonth();
985
+ const nowOrd = nowY * 12 + nowM;
986
+ const evOrd = evY * 12 + evM;
987
+ years.forEach((yr, r) => {
988
+ const ry = g.top + r * (rowH + rowGap);
989
+ aText(
990
+ svg,
991
+ g.left + yearW - 12,
992
+ ry + rowH / 2,
993
+ Math.min(22, rowH * 0.6),
994
+ C.muted,
995
+ "bold",
996
+ "end",
997
+ String(yr),
998
+ { "dominant-baseline": "central" }
999
+ );
1000
+ for (let m = 0; m < 12; m++) {
1001
+ const mOrd = yr * 12 + m;
1002
+ const before = mOrd < nowOrd;
1003
+ const isNow = yr === nowY && m === nowM;
1004
+ const isEv = yr === evY && m === evM;
1005
+ const between = (mOrd - nowOrd) * (mOrd - evOrd) < 0;
1006
+ const anchor = isEv || isNow;
1007
+ const st = isEv ? roleStyle(C, "event") : isNow ? roleStyle(C, "today") : between ? gradStyle(C, (yr * 12 + m - nowOrd) / (evOrd - nowOrd)) : roleStyle(C, before ? "past" : "future");
1008
+ const x = g.left + yearW + m * (cellW + gap);
1009
+ const rx = Math.min(5, Math.min(cellW, cellH) * 0.22);
1010
+ aRect(svg, x, ry, cellW, cellH, rx, st.fill, st.stroke, 1);
1011
+ if (isEv) haloRect(svg, x, ry, cellW, cellH, rx, C);
1012
+ const cx = x + cellW / 2;
1013
+ const mFont = Math.min(11, cellW * 0.32);
1014
+ if (anchor) {
1015
+ aText(
1016
+ svg,
1017
+ cx,
1018
+ ry + cellH * 0.36,
1019
+ mFont,
1020
+ st.text,
1021
+ "bold",
1022
+ "middle",
1023
+ MON_ABBR[m],
1024
+ { "dominant-baseline": "central" }
1025
+ );
1026
+ const dnum = isEv ? ev.getDate() : now2.getDate();
1027
+ aText(
1028
+ svg,
1029
+ cx,
1030
+ ry + cellH * 0.72,
1031
+ Math.min(14, cellW * 0.42),
1032
+ st.text,
1033
+ "bold",
1034
+ "middle",
1035
+ String(dnum),
1036
+ { "dominant-baseline": "central" }
1037
+ );
1038
+ } else {
1039
+ aText(
1040
+ svg,
1041
+ cx,
1042
+ ry + cellH / 2,
1043
+ mFont,
1044
+ st.text,
1045
+ "bold",
1046
+ "middle",
1047
+ MON_ABBR[m],
1048
+ { "dominant-baseline": "central" }
1049
+ );
1050
+ }
1051
+ }
1052
+ });
1053
+ }
1054
+ function vizMonths(svg, g, nowMs, resolvedMs, C) {
1055
+ const now2 = new Date(nowMs);
1056
+ const ev = new Date(resolvedMs);
1057
+ const realSpan = monthSpan(nowMs, resolvedMs);
1058
+ const perRow = 6;
1059
+ const shown = Math.max(perRow, Math.ceil(realSpan / perRow) * perRow);
1060
+ const padBefore = Math.floor((shown - realSpan) / 2);
1061
+ const rows = shown / perRow;
1062
+ const gap = 16;
1063
+ const rowGap = 16;
1064
+ const labelH = 27;
1065
+ const cellW = (g.contentW - gap * (perRow - 1)) / perRow;
1066
+ const cellH = (g.height - rowGap * (rows - 1)) / rows;
1067
+ const nowMk = now2.getFullYear() * 12 + now2.getMonth();
1068
+ const evMk = ev.getFullYear() * 12 + ev.getMonth();
1069
+ const loMk = Math.min(nowMk, evMk);
1070
+ const hiMk = Math.max(nowMk, evMk);
1071
+ const startMk = loMk - padBefore;
1072
+ for (let i = 0; i < shown; i++) {
1073
+ const d = new Date(Math.floor(startMk / 12), startMk % 12 + i, 1);
1074
+ const mk = d.getFullYear() * 12 + d.getMonth();
1075
+ const isNow = mk === nowMk;
1076
+ const isEv = mk === evMk;
1077
+ const isContext = mk < loMk || mk > hiMk;
1078
+ const anchor = isNow || isEv;
1079
+ const col = i % perRow;
1080
+ const row = Math.floor(i / perRow);
1081
+ const x = g.left + col * (cellW + gap);
1082
+ const y = g.top + row * (cellH + rowGap);
1083
+ const mt = isContext ? svg.append("g").attr("opacity", 0.42) : svg;
1084
+ const st = isEv ? roleStyle(C, "event") : isNow ? roleStyle(C, "today") : (mk - nowMk) * (mk - evMk) < 0 ? gradStyle(C, (mk - nowMk) / (evMk - nowMk)) : roleStyle(C, mk < nowMk ? "past" : "future");
1085
+ const rxM = Math.min(12, cellH * 0.14);
1086
+ aRect(mt, x, y, cellW, cellH, rxM, st.fill, st.stroke, 1.25);
1087
+ if (isEv) haloRect(mt, x, y, cellW, cellH, rxM, C);
1088
+ aText(
1089
+ mt,
1090
+ x + cellW / 2,
1091
+ y + 19,
1092
+ 16,
1093
+ st.text,
1094
+ "bold",
1095
+ "middle",
1096
+ MON_ABBR[d.getMonth()]
1097
+ );
1098
+ aLine(
1099
+ mt,
1100
+ x,
1101
+ y + labelH,
1102
+ x + cellW,
1103
+ y + labelH,
1104
+ st.text,
1105
+ 1,
1106
+ anchor ? 0.5 : 0.3
1107
+ );
1108
+ if (anchor) {
1109
+ const dnum = isEv ? ev.getDate() : now2.getDate();
1110
+ const dFont = Math.min(cellH - labelH - 6, cellW * 0.42, 44);
1111
+ aText(
1112
+ mt,
1113
+ x + cellW / 2,
1114
+ y + labelH + (cellH - labelH) / 2 + 1,
1115
+ dFont,
1116
+ st.text,
1117
+ "bold",
1118
+ "middle",
1119
+ String(dnum),
1120
+ { "dominant-baseline": "central" }
1121
+ );
1122
+ }
1123
+ }
1124
+ }
1125
+ function vizMonthGrid(svg, g, nowMs, resolvedMs, C) {
1126
+ const { months, dim: isContext } = monthGridLayout(
1127
+ nowMs,
1128
+ resolvedMs,
1129
+ g.contentW
1130
+ );
1131
+ const nM = months.length;
1132
+ const gap = 20;
1133
+ const mw = (g.contentW - gap * (nM - 1)) / nM;
1134
+ const padX = 10;
1135
+ const gridLeft = padX;
1136
+ const gridW = mw - 2 * padX;
1137
+ const cols = 7;
1138
+ const cellW = gridW / cols;
1139
+ const top = g.top;
1140
+ const headH = 26;
1141
+ const cellH = (g.height - headH - 8) / 6;
1142
+ const gridH = 6 * cellH;
1143
+ const weekendShade = mix(C.text, C.bg, 8);
1144
+ const todayKey = keyOf(nowMs);
1145
+ const evKey = keyOf(resolvedMs);
1146
+ months.forEach((m, mi) => {
1147
+ const mt = isContext[mi] ? svg.append("g").attr("opacity", 0.42) : svg;
1148
+ const mx = g.left + mi * (mw + gap);
1149
+ aRect(mt, mx, top, mw, g.height, 14, C.inert, C.inertBorder, 1.25);
1150
+ const gy = top + headH;
1151
+ for (const wcol of [0, 6])
1152
+ aRect(
1153
+ mt,
1154
+ mx + gridLeft + wcol * cellW,
1155
+ gy,
1156
+ cellW,
1157
+ gridH,
1158
+ 6,
1159
+ weekendShade
1160
+ );
1161
+ aText(
1162
+ mt,
1163
+ mx + mw / 2,
1164
+ top + 16,
1165
+ 14,
1166
+ C.text,
1167
+ "bold",
1168
+ "middle",
1169
+ `${MON_ABBR[m.getMonth()]} ${m.getFullYear()}`
1170
+ );
1171
+ aLine(mt, mx, top + headH - 6, mx + mw, top + headH - 6, C.rule, 1.25);
1172
+ const first = new Date(m.getFullYear(), m.getMonth(), 1).getDay();
1173
+ const dim = new Date(m.getFullYear(), m.getMonth() + 1, 0).getDate();
1174
+ for (let day = 1; day <= dim; day++) {
1175
+ const pos = first + day - 1;
1176
+ const wk = Math.floor(pos / 7);
1177
+ const dk = keyOf(new Date(m.getFullYear(), m.getMonth(), day).getTime());
1178
+ const wd = pos % 7;
1179
+ const cx = mx + gridLeft + wd * cellW;
1180
+ const cy = gy + wk * cellH;
1181
+ const isEvent = dk === evKey;
1182
+ const isToday = dk === todayKey;
1183
+ const between = (dk - todayKey) * (dk - evKey) < 0;
1184
+ const anchor = isEvent || isToday;
1185
+ const dtMs = new Date(m.getFullYear(), m.getMonth(), day).getTime();
1186
+ const st = isEvent ? roleStyle(C, "event") : isToday ? roleStyle(C, "today") : between ? gradStyle(C, dayDelta(nowMs, dtMs) / dayDelta(nowMs, resolvedMs)) : roleStyle(C, dk < todayKey ? "past" : "future");
1187
+ if (anchor) {
1188
+ const s = Math.min(cellW, cellH) * 1.02;
1189
+ const sx = cx + (cellW - s) / 2;
1190
+ const sy = cy + (cellH - s) / 2;
1191
+ aRect(mt, sx, sy, s, s, 7, st.fill, st.stroke, 2);
1192
+ if (isEvent) haloRect(mt, sx, sy, s, s, 7, C);
1193
+ aText(
1194
+ mt,
1195
+ cx + cellW / 2,
1196
+ cy + cellH / 2,
1197
+ Math.min(16, cellH * 0.62),
1198
+ st.text,
1199
+ "bold",
1200
+ "middle",
1201
+ String(day),
1202
+ { "dominant-baseline": "central" }
1203
+ );
1204
+ } else {
1205
+ const s = Math.min(cellW, cellH) * 0.5;
1206
+ aRect(
1207
+ mt,
1208
+ cx + (cellW - s) / 2,
1209
+ cy + (cellH - s) / 2,
1210
+ s,
1211
+ s,
1212
+ Math.min(3, s * 0.3),
1213
+ st.fill,
1214
+ st.stroke,
1215
+ 1
1216
+ );
1217
+ }
1218
+ }
1219
+ });
1220
+ }
1221
+ function vizWeekStrip(svg, g, nowMs, resolvedMs, C) {
1222
+ const nReal = Math.abs(dayDelta(nowMs, resolvedMs)) + 1;
1223
+ const n = Math.max(7, nReal);
1224
+ const padBefore = Math.floor((n - nReal) / 2);
1225
+ const gap = n <= 7 ? 14 : n <= 12 ? 10 : 7;
1226
+ const cw = (g.contentW - gap * (n - 1)) / n;
1227
+ const cellTop = g.top;
1228
+ const ch = g.height;
1229
+ const showWd = cw >= 30;
1230
+ const showTag = cw >= 44;
1231
+ const nowOrd = DAY_START(nowMs);
1232
+ const evOrd = DAY_START(resolvedMs);
1233
+ const loOrd = Math.min(nowOrd, evOrd);
1234
+ const hiOrd = Math.max(nowOrd, evOrd);
1235
+ const first = new Date(Math.min(nowMs, resolvedMs));
1236
+ for (let i = 0; i < n; i++) {
1237
+ const dt = new Date(
1238
+ first.getFullYear(),
1239
+ first.getMonth(),
1240
+ first.getDate() + i - padBefore
1241
+ );
1242
+ const dtOrd = DAY_START(dt.getTime());
1243
+ const x = g.left + i * (cw + gap);
1244
+ const isToday = dtOrd === nowOrd;
1245
+ const isEvent = dtOrd === evOrd;
1246
+ const isContext = dtOrd < loOrd || dtOrd > hiOrd;
1247
+ const st = isEvent ? roleStyle(C, "event") : isToday ? roleStyle(C, "today") : isContext ? roleStyle(C, dtOrd < nowOrd ? "past" : "future") : gradStyle(C, (dtOrd - nowOrd) / (evOrd - nowOrd));
1248
+ const ct = isContext ? svg.append("g").attr("opacity", 0.5) : svg;
1249
+ const sw = isToday || isEvent ? 2 : 1.25;
1250
+ aRect(ct, x, cellTop, cw, ch, 14, st.fill, st.stroke, sw);
1251
+ if (isEvent) haloRect(ct, x, cellTop, cw, ch, 14, C);
1252
+ if (showWd) {
1253
+ aText(
1254
+ ct,
1255
+ x + cw / 2,
1256
+ cellTop + 18,
1257
+ Math.min(12, cw * 0.3),
1258
+ st.text,
1259
+ "bold",
1260
+ "middle",
1261
+ WD_ABBR[dt.getDay()],
1262
+ { "letter-spacing": "0.04em" }
1263
+ );
1264
+ aLine(
1265
+ ct,
1266
+ x,
1267
+ cellTop + 27,
1268
+ x + cw,
1269
+ cellTop + 27,
1270
+ st.text,
1271
+ 1,
1272
+ isToday || isEvent ? 0.5 : 0.3
1273
+ );
1274
+ }
1275
+ aText(
1276
+ ct,
1277
+ x + cw / 2,
1278
+ cellTop + (showWd ? ch * 0.6 : ch * 0.54),
1279
+ Math.min(44, cw * 0.62),
1280
+ st.text,
1281
+ "bold",
1282
+ "middle",
1283
+ String(dt.getDate())
1284
+ );
1285
+ if (showTag && isToday)
1286
+ aText(
1287
+ ct,
1288
+ x + cw / 2,
1289
+ cellTop + ch - 13,
1290
+ 10,
1291
+ st.text,
1292
+ "bold",
1293
+ "middle",
1294
+ "TODAY",
1295
+ { "letter-spacing": "0.04em" }
1296
+ );
1297
+ }
1298
+ }
1299
+ function vizToday(svg, g, resolvedMs, C) {
1300
+ const ev = new Date(resolvedMs);
1301
+ const cx = g.left + g.contentW / 2;
1302
+ const cy = g.top + g.height / 2;
1303
+ aLine(svg, g.left, cy, g.left + g.contentW, cy, C.rule, 2);
1304
+ aLine(svg, cx, cy, g.left + g.contentW, cy, C.midSoft, 2);
1305
+ const rays = 16;
1306
+ const r0 = g.height * 0.3;
1307
+ const r1 = g.height * 0.44;
1308
+ for (let i = 0; i < rays; i++) {
1309
+ const a = i / rays * Math.PI * 2;
1310
+ svg.append("line").attr("x1", cx + Math.cos(a) * r0).attr("y1", cy + Math.sin(a) * r0).attr("x2", cx + Math.cos(a) * r1).attr("y2", cy + Math.sin(a) * r1).attr("stroke", C.event).attr("stroke-width", 2.5).attr("stroke-linecap", "round").attr("opacity", 0.45);
1311
+ }
1312
+ const w = Math.min(120, g.contentW * 0.16);
1313
+ const h = g.height * 0.52;
1314
+ const st = roleStyle(C, "event");
1315
+ aRect(svg, cx - w / 2, cy - h / 2, w, h, 18, st.fill, st.stroke, 2);
1316
+ aText(
1317
+ svg,
1318
+ cx,
1319
+ cy - h * 0.14,
1320
+ 14,
1321
+ st.text,
1322
+ "bold",
1323
+ "middle",
1324
+ MON_ABBR[ev.getMonth()].toUpperCase(),
1325
+ { "letter-spacing": "0.08em" }
1326
+ );
1327
+ aText(
1328
+ svg,
1329
+ cx,
1330
+ cy + h * 0.22,
1331
+ Math.min(48, h * 0.4),
1332
+ st.text,
1333
+ "bold",
1334
+ "middle",
1335
+ String(ev.getDate())
1336
+ );
1337
+ aText(
1338
+ svg,
1339
+ cx,
1340
+ cy + h / 2 + 26,
1341
+ 13,
1342
+ C.event,
1343
+ "bold",
1344
+ "middle",
1345
+ "THE DAY IS HERE",
1346
+ { "letter-spacing": "0.12em" }
1347
+ );
1348
+ }
1349
+ function polar(cx, cy, r, deg) {
1350
+ const a = (deg - 90) * Math.PI / 180;
1351
+ return [cx + r * Math.cos(a), cy + r * Math.sin(a)];
1352
+ }
1353
+ function arcPath(cx, cy, r, frac) {
1354
+ frac = Math.max(0, Math.min(0.9999, frac));
1355
+ const [x0, y0] = polar(cx, cy, r, 0);
1356
+ const [x1, y1] = polar(cx, cy, r, frac * 360);
1357
+ return `M ${x0} ${y0} A ${r} ${r} 0 ${frac > 0.5 ? 1 : 0} 1 ${x1} ${y1}`;
1358
+ }
1359
+ function vizClock(svg, g, nowMs, targetMs, C) {
1360
+ const ahead = targetMs >= nowMs;
1361
+ const rem = Math.abs(targetMs - nowMs);
1362
+ const h = Math.floor(rem / 36e5);
1363
+ const m = Math.floor(rem % 36e5 / 6e4);
1364
+ const s = Math.floor(rem % 6e4 / 1e3);
1365
+ const R = Math.min(g.contentW / 3 * 0.3, g.height * 0.34);
1366
+ const cy = g.top + g.height * 0.42;
1367
+ const sw = Math.max(8, R * 0.17);
1368
+ const gauges = [
1369
+ { frac: h % 24 / 24, val: h, label: "HOURS", col: C.event, key: "h" },
1370
+ { frac: m / 60, val: m, label: "MINUTES", col: C.event, key: "m" },
1371
+ { frac: s / 60, val: s, label: "SECONDS", col: C.event, key: "s" }
1372
+ ];
1373
+ gauges.forEach((gg, i) => {
1374
+ const cx = g.left + g.contentW * (i + 0.5) / 3;
1375
+ svg.append("circle").attr("cx", cx).attr("cy", cy).attr("r", R).attr("fill", "none").attr("stroke", C.inert).attr("stroke-width", sw);
1376
+ const arc = svg.append("path").attr("d", gg.frac > 1e-3 ? arcPath(cx, cy, R, gg.frac) : "").attr("fill", "none").attr("stroke", gg.col).attr("stroke-width", sw).attr("stroke-linecap", "round").attr("data-dgmo-gauge-arc", gg.key).attr("data-cx", cx).attr("data-cy", cy).attr("data-r", R);
1377
+ void arc;
1378
+ aText(
1379
+ svg,
1380
+ cx,
1381
+ cy + R * 0.22,
1382
+ R * 0.62,
1383
+ C.text,
1384
+ "bold",
1385
+ "middle",
1386
+ pad2(gg.val),
1387
+ {
1388
+ "data-dgmo-gauge-val": gg.key
1389
+ }
1390
+ );
1391
+ aText(svg, cx, cy + R + 22, 12, C.muted, "bold", "middle", gg.label, {
1392
+ "letter-spacing": "0.1em"
1393
+ });
1394
+ });
1395
+ const cxMid = g.left + g.contentW * 0.5;
1396
+ aText(
1397
+ svg,
1398
+ cxMid,
1399
+ cy - R - 14,
1400
+ 13,
1401
+ ahead ? C.event : C.muted,
1402
+ "bold",
1403
+ "middle",
1404
+ ahead ? "TO GO" : "AGO",
1405
+ {
1406
+ "letter-spacing": "0.12em",
1407
+ "data-dgmo-gauge-caption": ""
1408
+ }
1409
+ );
1410
+ }
1411
+ function pad2(n) {
1412
+ return n < 10 ? "0" + n : String(n);
1413
+ }
1414
+ function paintClock(sel, str, secSize, secFill) {
1415
+ const { lead, sec } = splitClockSeconds(str);
1416
+ sel.attr("font-variant-numeric", "tabular-nums").attr("data-dgmo-cd-clock", "").attr("data-dgmo-cd-sec-size", secSize).attr("data-dgmo-cd-sec-fill", secFill).text(null);
1417
+ sel.append("tspan").attr("data-cd-lead", "").text(lead);
1418
+ if (sec !== null)
1419
+ sel.append("tspan").attr("data-cd-sec", "").attr("font-size", secSize).attr("fill", secFill).attr("font-weight", "bold").text(sec);
1420
+ }
1421
+ function drawBand(svg, kind, g, nowMs, resolvedMs, C) {
1422
+ switch (kind) {
1423
+ case "years":
1424
+ return vizYears(svg, g, nowMs, resolvedMs, C);
1425
+ case "year":
1426
+ return vizYear(svg, g, nowMs, resolvedMs, C);
1427
+ case "months":
1428
+ return vizMonths(svg, g, nowMs, resolvedMs, C);
1429
+ case "monthgrid":
1430
+ return vizMonthGrid(svg, g, nowMs, resolvedMs, C);
1431
+ case "weekstrip":
1432
+ return vizWeekStrip(svg, g, nowMs, resolvedMs, C);
1433
+ case "today":
1434
+ return vizToday(svg, g, resolvedMs, C);
1435
+ case "clock":
1436
+ return vizClock(svg, g, nowMs, resolvedMs, C);
1437
+ }
1438
+ }
1439
+ function renderCountdown(container, parsed, palette, isDark, exportDims) {
1440
+ d3Selection.select(container).selectAll(":not([data-d3-tooltip])").remove();
1441
+ const width = exportDims?.width ?? container.clientWidth;
1442
+ const height = exportDims?.height ?? container.clientHeight;
1443
+ if (width <= 0 || height <= 0) return;
1444
+ const now2 = now();
1445
+ const resolved = parsed.resolvedMs;
1446
+ const label = parsed.sinceLabel ?? parsed.title ?? "";
1447
+ const ordinal = parsed.sinceLabel !== null && parsed.rule !== null && resolved !== null ? ordinalFor(resolved, parsed.rule) : null;
1448
+ const expiredFrozen = !parsed.rule && parsed.expired !== null && resolved !== null && resolved - now2 <= 0;
1449
+ const bandKind = resolved === null || parsed.noVisual || expiredFrozen ? null : pickBand(now2, resolved, parsed.hasTime);
1450
+ let hero = bakedHero(parsed, now2);
1451
+ if (bandKind === "clock" && resolved !== null) {
1452
+ const clock = formatCount(Math.abs(resolved - now2), {
1453
+ units: "clock",
1454
+ round: parsed.round,
1455
+ fields: parsed.fields
1456
+ });
1457
+ hero = resolved - now2 < 0 ? `${clock} ago` : clock;
1458
+ }
1459
+ const seriesAccent = getSeriesColors(palette)[0];
1460
+ const defaultAccent = resolveColor("red", palette) ?? seriesAccent;
1461
+ const manualColor = (parsed.color && resolveColor(parsed.color, palette)) ?? parsed.color ?? null;
1462
+ const accent = manualColor ?? defaultAccent;
1463
+ const muted = mix(palette.text, themeBaseBg(palette, isDark), 55);
1464
+ const banked = resolveColor("green", palette) ?? muted;
1465
+ const faint = mix(palette.text, themeBaseBg(palette, isDark), 72);
1466
+ const svg = d3Selection.select(container).append("svg").attr("width", width).attr("preserveAspectRatio", "xMidYMid meet").attr("xmlns", "http://www.w3.org/2000/svg").attr("role", "img").attr("aria-label", `${parsed.title ?? "Countdown"}: ${hero}`).style("font-family", FONT_FAMILY);
1467
+ svg.append("style").text(`
1468
+ @keyframes dgmo-countdown-flash {
1469
+ 0%, 100% { opacity: 1; stroke-width: 2; }
1470
+ 50% { opacity: 0.45; stroke-width: 3; }
1471
+ }
1472
+ .dgmo-countdown-target-ring {
1473
+ animation: dgmo-countdown-flash 1.8s ease-in-out infinite;
1474
+ }
1475
+ @media (prefers-reduced-motion: reduce) {
1476
+ .dgmo-countdown-target-ring { animation: none; }
1477
+ }
1478
+ `);
1479
+ const bgRect = svg.append("rect").attr("width", width).attr("fill", palette.bg);
1480
+ const estWidth = (t, fs) => t.length * fs * 0.58;
1481
+ const fitFont = (t, base, maxW, min) => {
1482
+ const w = estWidth(t, base);
1483
+ return w <= maxW ? base : Math.max(min, Math.floor(base * maxW / w));
1484
+ };
1485
+ const dispLen = (s) => stripInlineMarkdown(s).length;
1486
+ const padX = Math.max(30, Math.round(width * 0.045));
1487
+ const padY = Math.max(26, Math.round(width * 0.03));
1488
+ const contentW = width - 2 * padX;
1489
+ const leftX = padX;
1490
+ const remainingNow = resolved === null ? 0 : Math.max(0, resolved - now2);
1491
+ const dayCount = Math.ceil(remainingNow / DAY_MS);
1492
+ const heroSizeStr = parsed.units === "full" ? `${dayCount > 0 ? `${dayCount}d ` : ""}00:00:00` : parsed.units === "clock" ? formatCount(remainingNow, {
1493
+ units: "clock",
1494
+ round: parsed.round,
1495
+ fields: parsed.fields
1496
+ }) : hero;
1497
+ const heroMaxW = contentW * 0.44;
1498
+ const sizeStr = estWidth(hero, 1) > estWidth(heroSizeStr, 1) ? hero : heroSizeStr;
1499
+ const gapMid = Math.max(28, Math.round(width * 0.03));
1500
+ const stacked = estWidth(sizeStr, 26) > heroMaxW;
1501
+ const heroFont = fitFont(sizeStr, 96, stacked ? contentW : heroMaxW, 26);
1502
+ const heroW = stacked ? contentW : Math.min(heroMaxW, estWidth(sizeStr, heroFont));
1503
+ const leftW = stacked ? contentW : Math.max(contentW * 0.42, contentW - heroW - gapMid);
1504
+ const titleBase = 40;
1505
+ let titleFont = 0;
1506
+ let titleLines = [];
1507
+ if (parsed.title) {
1508
+ const longestWord = parsed.title.split(/\s+/).reduce((a, b) => b.length > a.length ? b : a, "");
1509
+ titleFont = fitFont(longestWord, titleBase, leftW, 22);
1510
+ const cpl = Math.max(6, Math.floor(leftW / (titleFont * 0.58)));
1511
+ titleLines = wrapDescriptionLines([parsed.title], cpl).map((l) => l.text);
1512
+ }
1513
+ const eyebrowText = ordinal !== null ? applyOrdinalTemplate(parsed.sinceLabel ?? `Nth ${label}`, ordinal) : null;
1514
+ const eyebrowFont = 16;
1515
+ const footerText = resolved !== null ? formatFooter(resolved, parsed.hasTime, parsed.tz) : null;
1516
+ const footerFont = 17;
1517
+ const noteFont = 19;
1518
+ const asofFont = 13;
1519
+ const noteCharsPerLine = Math.max(8, Math.floor(leftW / (noteFont * 0.5)));
1520
+ const noteSource = parsed.note ? parsed.note.split("\n").map((l) => l.replace(/^[-*]\s+/, "\u2022 ")) : [];
1521
+ const noteLines = noteSource.length ? wrapDescriptionLines(noteSource, noteCharsPerLine, dispLen) : [];
1522
+ const bulletPx = noteFont * 0.95;
1523
+ let y = padY;
1524
+ const drawText = (text, yTop, fs, fill, weight) => svg.append("text").attr("x", leftX).attr("y", yTop + fs).attr("text-anchor", "start").attr("dominant-baseline", "alphabetic").attr("fill", fill).attr("font-family", FONT_FAMILY).attr("font-size", fs).attr("font-weight", weight).text(text);
1525
+ if (parsed.title) {
1526
+ titleLines.forEach((tl, i) => {
1527
+ const t = drawText(tl, y, titleFont, palette.text, "bold");
1528
+ if (i === 0) t.attr("data-line-number", parsed.titleLineNumber);
1529
+ y += i < titleLines.length - 1 ? Math.round(titleFont * 1.12) : titleFont;
1530
+ });
1531
+ y += Math.round(titleFont * 0.45);
1532
+ const heroLeft = width - padX - heroW;
1533
+ const ruleRight = stacked ? width - padX : Math.max(leftX + 40, heroLeft - 20);
1534
+ svg.append("line").attr("x1", leftX).attr("x2", ruleRight).attr("y1", y).attr("y2", y).attr("stroke", mix(palette.text, palette.bg, 14)).attr("stroke-width", 1.25);
1535
+ y += Math.round(titleFont * 0.5);
1536
+ }
1537
+ if (eyebrowText) {
1538
+ drawText(eyebrowText, y, eyebrowFont, banked, "bold").attr(
1539
+ "data-dgmo-countdown-eyebrow",
1540
+ ""
1541
+ );
1542
+ y += eyebrowFont + 8;
1543
+ }
1544
+ if (footerText) {
1545
+ const fx = parsed.rule ? leftX + 22 : leftX;
1546
+ if (parsed.rule)
1547
+ recurGlyph(svg, leftX + 8, y + footerFont * 0.52, 6.5, muted);
1548
+ svg.append("text").attr("x", fx).attr("y", y + footerFont).attr("text-anchor", "start").attr("dominant-baseline", "alphabetic").attr("fill", muted).attr("font-family", FONT_FAMILY).attr("font-size", footerFont).attr("font-weight", "normal").attr("data-dgmo-countdown-footer", "").text(footerText);
1549
+ y += footerFont + 8;
1550
+ }
1551
+ if (noteLines.length) {
1552
+ y += 4;
1553
+ for (const nl of noteLines) {
1554
+ const bx = nl.kind === "plain" ? leftX : leftX + bulletPx;
1555
+ if (nl.kind === "bullet-first") {
1556
+ svg.append("text").attr("x", leftX).attr("y", y + noteFont).attr("fill", muted).attr("font-family", FONT_FAMILY).attr("font-size", noteFont).text("\u2022");
1557
+ }
1558
+ const t = svg.append("text").attr("x", bx).attr("y", y + noteFont).attr("fill", palette.text).attr("font-family", FONT_FAMILY).attr("font-size", noteFont).attr("font-weight", "normal");
1559
+ renderInlineText(t, nl.text, palette, noteFont);
1560
+ y += Math.round(noteFont * 1.35);
1561
+ }
1562
+ y += 2;
1563
+ }
1564
+ drawText(`as of ${formatDateShort(now2)}`, y, asofFont, faint, "normal").attr(
1565
+ "data-dgmo-countdown-asof",
1566
+ ""
1567
+ );
1568
+ y += asofFont;
1569
+ const leftBottom = y;
1570
+ const detailFont = 18;
1571
+ const remainingSub = resolved !== null ? resolved - now2 : 0;
1572
+ const subText = resolved === null || remainingSub <= 0 ? null : parsed.units === "words" ? formatWordsDetail(remainingSub) : parsed.units === "human" && bandKind !== "clock" ? parsed.hasTime ? formatCount(remainingSub, {
1573
+ units: "clock",
1574
+ round: parsed.round,
1575
+ fields: parsed.fields
1576
+ }) : formatHuman(
1577
+ dayStart(now2, parsed.tz),
1578
+ dayStart(resolved, parsed.tz),
1579
+ parsed.tz
1580
+ ).sub || null : null;
1581
+ const heroCapTop = stacked ? leftBottom + Math.round(heroFont * 0.2) : padY + 0.28 * titleFont;
1582
+ const heroBaseline = heroCapTop + 0.72 * heroFont;
1583
+ const heroBlockBottom = heroBaseline + (subText ? heroFont * 0.28 + detailFont : 0);
1584
+ const contentBottom = Math.max(leftBottom, heroBlockBottom);
1585
+ const hasBand = bandKind !== null;
1586
+ const topSection = contentBottom;
1587
+ const bandTop = topSection + 26;
1588
+ const BAND_H = hasBand && resolved !== null ? Math.round(bandHeightFor(bandKind, now2, resolved, width - 2 * padX)) : 0;
1589
+ const bannerH = hasBand ? bandTop + BAND_H + padY : Math.max(
1590
+ contentBottom + padY,
1591
+ heroFont * 1.25 + 2 * padY,
1592
+ Math.round(width * 0.28)
1593
+ );
1594
+ svg.attr("height", bannerH).attr("viewBox", `0 0 ${width} ${bannerH}`);
1595
+ bgRect.attr("height", bannerH);
1596
+ const bandC = bandColors(
1597
+ palette,
1598
+ accent,
1599
+ muted,
1600
+ faint,
1601
+ isDark,
1602
+ parsed.fillMode
1603
+ );
1604
+ if (hasBand && resolved !== null) {
1605
+ drawBand(
1606
+ svg,
1607
+ bandKind,
1608
+ { left: leftX, top: bandTop, contentW: width - 2 * padX, height: BAND_H },
1609
+ now2,
1610
+ resolved,
1611
+ bandC
1612
+ );
1613
+ }
1614
+ const value = svg.append("text").attr("class", "countdown-value").attr("x", stacked ? leftX : width - padX).attr("y", heroBaseline).attr("text-anchor", stacked ? "start" : "end").attr("dominant-baseline", "alphabetic").attr("fill", accent).attr("font-family", FONT_FAMILY).attr("font-size", heroFont).attr("font-weight", "bold").attr("data-dgmo-countdown-units", parsed.units).attr("data-dgmo-countdown-round", parsed.round).attr("data-dgmo-countdown-fields", parsed.fields.join(",")).attr("data-dgmo-countdown-expired", parsed.expired).attr("aria-label", `${parsed.title ?? "Countdown"}: ${hero}`);
1615
+ paintClock(value, hero, Math.round(heroFont * 0.55), bandC.now);
1616
+ if (parsed.rule && resolved !== null) {
1617
+ value.attr("data-dgmo-countdown", new Date(resolved).toISOString());
1618
+ } else if (parsed.target) {
1619
+ value.attr("data-dgmo-countdown", parsed.target);
1620
+ }
1621
+ if (parsed.title) value.attr("data-dgmo-countdown-title", parsed.title);
1622
+ if (parsed.onDay) value.attr("data-dgmo-countdown-onday", parsed.onDay);
1623
+ if (parsed.hasTime) value.attr("data-dgmo-countdown-hastime", "1");
1624
+ if (parsed.tz) value.attr("data-dgmo-countdown-tz", parsed.tz);
1625
+ if (parsed.sinceLabel !== null) {
1626
+ value.attr("data-dgmo-countdown-since-label", parsed.sinceLabel);
1627
+ }
1628
+ stampRecur(value, parsed);
1629
+ if (bandKind !== null && resolved !== null) {
1630
+ value.attr("data-dgmo-countdown-band-day", dayKey(now2, parsed.tz)).attr("data-dgmo-countdown-band-on", dayKey(resolved, parsed.tz));
1631
+ svg.attr(
1632
+ "data-dgmo-countdown-redraw",
1633
+ JSON.stringify({ parsed, palette, isDark, width, height })
1634
+ );
1635
+ }
1636
+ if (subText) {
1637
+ const detailSel = svg.append("text").attr("x", stacked ? leftX : width - padX).attr("y", heroBaseline + heroFont * 0.28 + detailFont).attr("text-anchor", stacked ? "start" : "end").attr("dominant-baseline", "alphabetic").attr("fill", muted).attr("font-family", FONT_FAMILY).attr("font-size", detailFont).attr("font-weight", "normal").attr("data-dgmo-countdown-detail", "");
1638
+ paintClock(detailSel, subText, Math.round(detailFont * 0.66), bandC.now);
1639
+ }
1640
+ }
1641
+
1642
+ // src/countdown/redraw.ts
1643
+ function redrawCountdown(svg, resolvedMs) {
1644
+ const raw = svg.getAttribute("data-dgmo-countdown-redraw");
1645
+ if (!raw) return;
1646
+ let p;
1647
+ try {
1648
+ p = JSON.parse(raw);
1649
+ } catch {
1650
+ return;
1651
+ }
1652
+ const doc = svg.ownerDocument;
1653
+ const container = doc.createElement("div");
1654
+ renderCountdown(container, { ...p.parsed, resolvedMs }, p.palette, p.isDark, {
1655
+ width: p.width,
1656
+ height: p.height
1657
+ });
1658
+ let fresh = container.querySelector("svg");
1659
+ if (!fresh) return;
1660
+ if (!svg.hasAttribute("width")) {
1661
+ const bg = p.palette.bg.trim().toLowerCase();
1662
+ const keptBackground = /background/i.test(svg.getAttribute("style") ?? "") || Array.from(svg.children).some(
1663
+ (c) => c.tagName.toLowerCase() === "rect" && (c.getAttribute("fill") ?? "").trim().toLowerCase() === bg
1664
+ );
1665
+ fresh.style.background = p.palette.bg;
1666
+ const holder = doc.createElement("div");
1667
+ holder.innerHTML = normalizeSvgForEmbed(fresh.outerHTML, {
1668
+ background: keptBackground ? "opaque" : "transparent"
1669
+ });
1670
+ fresh = holder.querySelector("svg");
1671
+ if (!fresh) return;
1672
+ }
1673
+ Array.from(svg.childNodes).forEach((c) => {
1674
+ if (!(c.nodeType === 1 && c.tagName.toLowerCase() === "desc")) {
1675
+ svg.removeChild(c);
1676
+ }
1677
+ });
1678
+ Array.from(fresh.childNodes).forEach((c) => {
1679
+ const isDesc = c.nodeType === 1 && c.tagName.toLowerCase() === "desc";
1680
+ if (!isDesc) svg.appendChild(doc.importNode(c, true));
1681
+ });
1682
+ for (const name of ["viewBox", "width", "height", "aria-label"]) {
1683
+ const v = fresh.getAttribute(name);
1684
+ if (v !== null) svg.setAttribute(name, v);
1685
+ }
1686
+ const payload = fresh.getAttribute("data-dgmo-countdown-redraw");
1687
+ if (payload !== null) svg.setAttribute("data-dgmo-countdown-redraw", payload);
1688
+ else svg.removeAttribute("data-dgmo-countdown-redraw");
1689
+ const view = doc.defaultView;
1690
+ if (view && typeof view.CustomEvent === "function") {
1691
+ svg.dispatchEvent(
1692
+ new view.CustomEvent("dgmo:countdown-redrawn", { bubbles: true })
1693
+ );
1694
+ }
1695
+ }
1696
+ export {
1697
+ redrawCountdown
1698
+ };