@diagrammo/dgmo 0.92.1 → 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.
- package/dist/advanced.d.ts +77 -20
- package/dist/advanced.js +453 -420
- package/dist/block.d.ts +2 -2
- package/dist/block.js +75 -70
- package/dist/{chart-meta-BBqLnv5q.d.ts → chart-meta-BbjsRY7o.d.ts} +2 -2
- package/dist/chart-meta.d.ts +3 -3
- package/dist/chart-meta.js +25 -19
- package/dist/{chart-types-CbXlZFLd.d.ts → chart-types-Cppnp31G.d.ts} +173 -4
- package/dist/{charts-d3-QUUDVETP.js → charts-d3-ERYYVGP6.js} +7 -6
- package/dist/{chunk-HIWHIYLD.js → chunk-2TFXRBCD.js} +19 -19
- package/dist/{chunk-O5WUZGPV.js → chunk-3GCASUWA.js} +17 -17
- package/dist/{chunk-ZJAK6C7Y.js → chunk-3PP746YM.js} +6 -1
- package/dist/{chunk-WLLO3PZ3.js → chunk-472EOHVW.js} +17 -17
- package/dist/{chunk-JLU52ZDT.js → chunk-5CBX66KB.js} +10 -10
- package/dist/{chunk-DEFBUZWV.js → chunk-5HAYT43L.js} +70 -54
- package/dist/{chunk-S7W75MIM.js → chunk-65GPEV5V.js} +21 -3
- package/dist/{chunk-Q6W2K33N.js → chunk-6XLMTAEX.js} +1 -1
- package/dist/{chunk-JKST4RTT.js → chunk-7OWK5K5C.js} +6 -6
- package/dist/{chunk-73EYNV4O.js → chunk-A5WIAMCT.js} +4 -6
- package/dist/{chunk-X5BUX2GK.js → chunk-ADVGV3P7.js} +1 -1
- package/dist/{chunk-45QUTYPD.js → chunk-CYDM2XII.js} +17 -17
- package/dist/chunk-CYTFO2LF.js +209 -0
- package/dist/{chunk-JRWUHLRP.js → chunk-D6PVMUCX.js} +51 -8
- package/dist/{chunk-BRSM6Z34.js → chunk-DAHQL57W.js} +14 -14
- package/dist/{chunk-RBFJT23E.js → chunk-EQWC76GQ.js} +6 -6
- package/dist/{chunk-ADNIO3XY.js → chunk-FIYW7JW4.js} +5 -5
- package/dist/{chunk-7ENDOHAA.js → chunk-FMAVHTYB.js} +919 -927
- package/dist/{chunk-3PFMGNOM.js → chunk-GJXMG4S3.js} +26 -11
- package/dist/chunk-GXCDD6NG.js +24 -0
- package/dist/{chunk-Y2V5AEVG.js → chunk-IESUM4N5.js} +2331 -2236
- package/dist/{chunk-QQPNCXAO.js → chunk-IIV4SMJL.js} +18 -18
- package/dist/{chunk-5EBYPETE.js → chunk-J43OCO22.js} +3 -5
- package/dist/{chunk-L5NW3PVL.js → chunk-J6AEKIRQ.js} +1 -1
- package/dist/{chunk-2BJW7FSF.js → chunk-KBEWVCK3.js} +1 -1
- package/dist/{chunk-LRP5FXST.js → chunk-KCHQVED5.js} +18 -17
- package/dist/{chunk-Q6ZQX2HX.js → chunk-KCMGIYPO.js} +4 -6
- package/dist/{chunk-MDVJYRJT.js → chunk-KPGPK4SK.js} +22 -22
- package/dist/{chunk-Q2UFOKPB.js → chunk-KPSSNGAE.js} +22 -22
- package/dist/{chunk-V4D2ZQBE.js → chunk-KRPLP5LP.js} +6 -1
- package/dist/{chunk-BZX7QCXJ.js → chunk-KRU2LCIP.js} +15 -15
- package/dist/{chunk-HXULJZA2.js → chunk-LDDDKWL6.js} +6 -1
- package/dist/{chunk-NREU4JFE.js → chunk-LNVB75QU.js} +6 -8
- package/dist/{chunk-MX4SZLSV.js → chunk-LUDUDBO6.js} +16 -8
- package/dist/{chunk-2YAFDND3.js → chunk-M4NV5PPU.js} +4 -6
- package/dist/{chunk-26BBJUBW.js → chunk-M6ZQAFPD.js} +4 -6
- package/dist/{chunk-NPWVOFB3.js → chunk-MB2SQF7N.js} +1 -1
- package/dist/{chunk-6ZL2XYM7.js → chunk-MLY5ALOV.js} +6 -6
- package/dist/chunk-NJJCTEBM.js +416 -0
- package/dist/{chunk-OQW4ETJX.js → chunk-NM5V5EKY.js} +6 -1
- package/dist/{chunk-ONYO5WJW.js → chunk-NMK7KJZZ.js} +21 -21
- package/dist/{chunk-6BNNLVAR.js → chunk-NPDVWFAQ.js} +6 -1
- package/dist/{chunk-FTUMRSGT.js → chunk-P5FUWXPW.js} +4 -286
- package/dist/chunk-P7TRFL4P.js +54 -0
- package/dist/{chunk-A4PNWAUW.js → chunk-PGSGJLUR.js} +6 -1
- package/dist/{chunk-DRFGIOZ6.js → chunk-Q7Y6JQ3R.js} +4 -6
- package/dist/{chunk-J2E7EUER.js → chunk-QDRCWVV3.js} +17 -17
- package/dist/{chunk-MXAP37W6.js → chunk-TNV4F5WA.js} +16 -7
- package/dist/{chunk-EBSFSOJC.js → chunk-U7U64UUN.js} +22 -22
- package/dist/chunk-UCHZJJIT.js +421 -0
- package/dist/{chunk-FSJ3ZA6N.js → chunk-UG7AQQNR.js} +12 -12
- package/dist/{chunk-WFA6IULY.js → chunk-URRAWU5D.js} +5 -0
- package/dist/{chunk-VOR32OHN.js → chunk-USAGHYRD.js} +13 -15
- package/dist/{chunk-QUGC7GWB.js → chunk-UVX3IYXF.js} +15 -15
- package/dist/{chunk-B5PEKSRH.js → chunk-VSAKEMDD.js} +6 -1
- package/dist/{chunk-5SSTVFPX.js → chunk-WK5QEYFA.js} +5 -7
- package/dist/{chunk-KTGZDMDX.js → chunk-XUTR7GNT.js} +76 -28
- package/dist/{chunk-6WVX4KNP.js → chunk-YDZIVDVR.js} +97 -297
- package/dist/{chunk-URQIWIKX.js → chunk-YXXIRM4C.js} +3 -3
- package/dist/{chunk-H5UEVW6Q.js → chunk-ZD3QCNB7.js} +1 -1
- package/dist/{chunk-DUPZ2BBY.js → chunk-ZTLMEBAC.js} +66 -4
- package/dist/clock.js +168 -0
- package/dist/completion-registry.js +3 -1
- package/dist/countdown.js +36 -395
- package/dist/{dgmo-router-4UAPPIKN.js → dgmo-router-CZN36TR3.js} +50 -43
- package/dist/{dimensions-SCNVYLRF.js → dimensions-7HHKO5BS.js} +3 -3
- package/dist/{d3-BThYBTQk.d.ts → export-context-Ceh3wr0E.d.ts} +2 -8
- package/dist/{flowchart-parser-NA75YQ77.js → flowchart-parser-4MZPWYK6.js} +8 -3
- package/dist/{flowchart-renderer-6MGGX4RC.js → flowchart-renderer-5XDN575E.js} +13 -17
- package/dist/index.d.ts +8 -7
- package/dist/index.js +80 -74
- package/dist/{layout-S3RLVPH4.js → layout-33FHGDEV.js} +10 -12
- package/dist/{layout-MBVBAMPA.js → layout-3TSVAGWH.js} +2 -2
- package/dist/{layout-WRG4APSJ.js → layout-5GHV7HNM.js} +9 -13
- package/dist/layout-FTOGDNUL.js +23 -0
- package/dist/{layout-BNLFQHSB.js → layout-QXGPY7KA.js} +7 -9
- package/dist/{layout-search-PMYL3VWS.js → layout-search-EMTCDC3C.js} +8 -10
- package/dist/{palettes-IN7ZFRJM.js → palettes-4Y7CL2QL.js} +4 -2
- package/dist/{parser-3MK56JZC.js → parser-4Y6PODW6.js} +2 -2
- package/dist/{parser-2NWH5JUS.js → parser-A3AN76RQ.js} +2 -1
- package/dist/{parser-LCJ76UXS.js → parser-BT4NI6JY.js} +5 -1
- package/dist/{parser-FXDS4JDK.js → parser-F7NDRPMA.js} +2 -2
- package/dist/{parser-BES6LV3G.js → parser-GDLKUBTO.js} +5 -1
- package/dist/{parser-SEUTXN6U.js → parser-KJPPUN4D.js} +2 -2
- package/dist/{parser-SIKL4PLF.js → parser-KMKVFET2.js} +5 -1
- package/dist/{parser-5LW5JDYG.js → parser-NL73FSFI.js} +5 -1
- package/dist/{parser-VA7ZQQTA.js → parser-NUU3DVW4.js} +5 -1
- package/dist/{parser-L6RWDXQN.js → parser-OGF2CDA6.js} +8 -3
- package/dist/{parser-XZUKNZPB.js → parser-R3LPEQIT.js} +7 -1
- package/dist/{parser-X23GRTQJ.js → parser-RCIEKN7Y.js} +5 -1
- package/dist/{parser-GX5YLRIP.js → parser-VKYUAT4Y.js} +8 -3
- package/dist/{parser-QYISOAGR.js → parser-YX72VLDZ.js} +8 -3
- package/dist/redraw-MSXU4ZD2.js +73 -0
- package/dist/redraw-T324XAF3.js +1698 -0
- package/dist/{renderer-5VKPB4LL.js → renderer-37PLQHVB.js} +5 -5
- package/dist/{renderer-ISSDGK54.js → renderer-3F47TPZ2.js} +3 -3
- package/dist/{renderer-3QAFDXEY.js → renderer-5ITLW7MX.js} +14 -12
- package/dist/{renderer-ZZ6MWVB7.js → renderer-6GJWADLO.js} +4 -4
- package/dist/{renderer-MM27BSBO.js → renderer-7Y3LR4R2.js} +3 -4
- package/dist/{renderer-2FVUPBGV.js → renderer-AM7P4LN7.js} +1 -1
- package/dist/{renderer-ZHGHTSNG.js → renderer-BO73LSVT.js} +13 -12
- package/dist/{renderer-3IHQI4A4.js → renderer-CNRQFIEI.js} +11 -10
- package/dist/{renderer-OAR5VPH4.js → renderer-DAZWHNLI.js} +2 -2
- package/dist/{renderer-NDA2FFYB.js → renderer-DD6T3C4J.js} +5 -5
- package/dist/{renderer-WIU5BJUK.js → renderer-DDWDCM2K.js} +2 -2
- package/dist/{renderer-7ZZG5HR3.js → renderer-EVKWGUGT.js} +8 -9
- package/dist/{renderer-3JY6JAQJ.js → renderer-FL7EOSBU.js} +5 -6
- package/dist/{renderer-RUSFL7WU.js → renderer-GFALLIYT.js} +7 -6
- package/dist/{renderer-O6DY46AV.js → renderer-GFGFHYWY.js} +7 -7
- package/dist/{renderer-5TZVFQ7K.js → renderer-GOXAP2IE.js} +7 -8
- package/dist/{renderer-NHTR7IRR.js → renderer-GPAVVUET.js} +4 -5
- package/dist/{renderer-TO2NVJP3.js → renderer-HJ54YXT3.js} +50 -45
- package/dist/{renderer-LM4ICK6I.js → renderer-HYGTMBHT.js} +4 -5
- package/dist/{renderer-S3YCLJKL.js → renderer-IHCWBFPI.js} +17 -20
- package/dist/{renderer-XO57LTU4.js → renderer-ISDKO3DI.js} +5 -5
- package/dist/{renderer-LQP3AGEJ.js → renderer-J3QWK6IM.js} +3 -4
- package/dist/{renderer-J666LZXJ.js → renderer-J6H4Q3RM.js} +7 -8
- package/dist/{renderer-ALWVK3EJ.js → renderer-JJ4QW3SK.js} +6 -6
- package/dist/{renderer-DC7C5Q3G.js → renderer-NOYLNVTI.js} +2 -2
- package/dist/{renderer-ZGY7P4BM.js → renderer-PBKTAWQV.js} +16 -20
- package/dist/{renderer-XGG2UNXT.js → renderer-POBGKIUT.js} +3 -4
- package/dist/{renderer-QGIHLZOJ.js → renderer-RDI2RYUP.js} +9 -10
- package/dist/{renderer-JEJT3YET.js → renderer-TSCSZ6CL.js} +2 -3
- package/dist/{renderer-VPIELEE5.js → renderer-VDBNC7A6.js} +8 -9
- package/dist/{renderer-DVNMCYHZ.js → renderer-VI34SD5V.js} +14 -14
- package/dist/{renderer-J5FKF423.js → renderer-W4MUMXJE.js} +7 -4
- package/dist/{renderer-RSK6QFWN.js → renderer-XJMVG2WJ.js} +7 -9
- package/dist/{state-parser-TIR2ORJY.js → state-parser-NPYTMRXP.js} +8 -3
- package/dist/{state-renderer-G6SUXU4J.js → state-renderer-44K2KDX7.js} +15 -19
- package/dist/{tag-groups-DsoVpAY-.d.ts → tag-groups-DydewK3D.d.ts} +2 -173
- package/dist/{themes-DRNjxX7Q.d.ts → themes-DzIH3Hjv.d.ts} +3 -2
- package/docs/ai-integration.md +1 -1
- package/docs/language-reference.md +23 -2
- package/gallery/fixtures/whiteboard.dgmo +3 -0
- package/package.json +2 -6
- package/dist/chunk-3Y63RFJL.js +0 -75
- package/dist/chunk-5DH3KYFH.js +0 -20
- package/dist/chunk-O753JUEZ.js +0 -67
- package/dist/chunk-RJNZHFNG.js +0 -51
- package/dist/chunk-Z6DXWULM.js +0 -23
- package/dist/layout-5USEVQBY.js +0 -25
- package/dist/{chunk-YITHYQCL.js → chunk-4FT5KQ7H.js} +7 -7
- package/dist/{chunk-GE7ICBX6.js → chunk-4RY5D5RE.js} +4 -4
- package/dist/{chunk-4QLGGQNO.js → chunk-4WQX55VX.js} +14 -14
- package/dist/{chunk-DGUGE67L.js → chunk-CDYV46JE.js} +3 -3
- package/dist/{chunk-IGKFOHBG.js → chunk-CQYFZOUC.js} +13 -13
- package/dist/{chunk-TKAB3FTK.js → chunk-DRQSB72S.js} +11 -11
- package/dist/{chunk-ZKP77MB3.js → chunk-FVTYJUXR.js} +5 -5
- package/dist/{chunk-MA42PIGJ.js → chunk-H5NVCOEJ.js} +4 -4
- package/dist/{chunk-BEA4D7OS.js → chunk-IXHWS2HI.js} +3 -3
- package/dist/{chunk-QKY6W4NA.js → chunk-KHL652SX.js} +25 -25
- package/dist/{chunk-6OIMUSDX.js → chunk-MUJVNSEW.js} +9 -9
- package/dist/{chunk-YO6AQU4A.js → chunk-QJOVZ4TC.js} +12 -12
- package/dist/{chunk-SOHZDW6N.js → chunk-UXNA2H2V.js} +3 -3
- 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
|
+
};
|