@diagrammo/dgmo 0.93.0 → 0.95.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 (122) hide show
  1. package/dist/advanced.d.ts +98 -21
  2. package/dist/advanced.js +79 -61
  3. package/dist/block.d.ts +6 -0
  4. package/dist/block.js +23 -20
  5. package/dist/chart-meta.js +9 -9
  6. package/dist/{charts-d3-ERYYVGP6.js → charts-d3-SZV2SFTM.js} +2 -2
  7. package/dist/{chunk-J6AEKIRQ.js → chunk-2I4UQG7W.js} +1 -1
  8. package/dist/{chunk-LDDDKWL6.js → chunk-2J2CWWWK.js} +1 -1
  9. package/dist/{chunk-QDRCWVV3.js → chunk-4EX4OKGZ.js} +71 -8
  10. package/dist/{chunk-A5WIAMCT.js → chunk-4UVCTLRH.js} +1 -1
  11. package/dist/{chunk-KCMGIYPO.js → chunk-5XBYLYYI.js} +1 -1
  12. package/dist/{chunk-XUTR7GNT.js → chunk-62GLKE6Q.js} +68 -29
  13. package/dist/{chunk-D6PVMUCX.js → chunk-67N26CER.js} +4 -4
  14. package/dist/{chunk-IIV4SMJL.js → chunk-6EFCOES2.js} +370 -285
  15. package/dist/{chunk-TNV4F5WA.js → chunk-7IALGIOI.js} +2 -2
  16. package/dist/{chunk-MB2SQF7N.js → chunk-7WEYXYLC.js} +1 -1
  17. package/dist/{chunk-4RY5D5RE.js → chunk-AM42TLSZ.js} +98 -93
  18. package/dist/{chunk-J43OCO22.js → chunk-BCV2VJTM.js} +2 -2
  19. package/dist/{chunk-7OWK5K5C.js → chunk-BH2SF4TT.js} +2 -2
  20. package/dist/{chunk-UVX3IYXF.js → chunk-BTP2BMWN.js} +4 -4
  21. package/dist/{chunk-MUJVNSEW.js → chunk-C6G36F5S.js} +1 -1
  22. package/dist/{chunk-ZTLMEBAC.js → chunk-E7F76M3U.js} +208 -26
  23. package/dist/{chunk-AUS7V54P.js → chunk-ERT7XDQW.js} +1 -1
  24. package/dist/{chunk-WK5QEYFA.js → chunk-EXHXTMVR.js} +3 -3
  25. package/dist/{chunk-YXXIRM4C.js → chunk-FWVE373X.js} +2 -2
  26. package/dist/{chunk-EQWC76GQ.js → chunk-GWQRTA5W.js} +2 -2
  27. package/dist/{chunk-GJXMG4S3.js → chunk-HHVIE45A.js} +1 -1
  28. package/dist/{chunk-4FT5KQ7H.js → chunk-IGJEKO2P.js} +1 -1
  29. package/dist/{chunk-KRU2LCIP.js → chunk-IHN6WDEY.js} +1 -1
  30. package/dist/{chunk-QT3KDDXK.js → chunk-IN7XI6NO.js} +0 -7
  31. package/dist/{chunk-NMK7KJZZ.js → chunk-IQN2NGEU.js} +4 -4
  32. package/dist/{chunk-M6ZQAFPD.js → chunk-IY62DXT2.js} +1 -1
  33. package/dist/{chunk-4WQX55VX.js → chunk-IZHGT5HD.js} +31 -5
  34. package/dist/{chunk-CYDM2XII.js → chunk-JJ7RGTAU.js} +56 -7
  35. package/dist/{chunk-NJJCTEBM.js → chunk-KLBTJEIK.js} +1 -1
  36. package/dist/{chunk-KHL652SX.js → chunk-KSZB7TIY.js} +1 -1
  37. package/dist/{chunk-KPSSNGAE.js → chunk-L5S32VZJ.js} +4 -4
  38. package/dist/{chunk-472EOHVW.js → chunk-LAXMUSGW.js} +31 -5
  39. package/dist/{chunk-M4NV5PPU.js → chunk-LJGE43SG.js} +1 -1
  40. package/dist/{chunk-VSOZZYLK.js → chunk-NERQSSYD.js} +20 -9
  41. package/dist/{chunk-UG7AQQNR.js → chunk-OSSTOSXU.js} +2 -2
  42. package/dist/{chunk-YDZIVDVR.js → chunk-P5XA627W.js} +7 -7
  43. package/dist/{chunk-DRQSB72S.js → chunk-PGBTUGDB.js} +2 -2
  44. package/dist/{chunk-65GPEV5V.js → chunk-QKPOIJBT.js} +2 -2
  45. package/dist/{chunk-QJOVZ4TC.js → chunk-QPQWY5RU.js} +1 -1
  46. package/dist/{chunk-P7TRFL4P.js → chunk-R46N5KR4.js} +2 -0
  47. package/dist/{chunk-LNVB75QU.js → chunk-RBVFNUAL.js} +1 -1
  48. package/dist/{chunk-DAHQL57W.js → chunk-RITTLIDJ.js} +1 -1
  49. package/dist/{chunk-2TFXRBCD.js → chunk-RQTDUZUH.js} +3 -3
  50. package/dist/{chunk-Q7Y6JQ3R.js → chunk-S4SJ4LLY.js} +1 -1
  51. package/dist/{chunk-LUDUDBO6.js → chunk-S5Y3OO3V.js} +2 -2
  52. package/dist/{chunk-3GCASUWA.js → chunk-TASRGDYP.js} +1 -1
  53. package/dist/{chunk-5HAYT43L.js → chunk-U6VS3T2M.js} +19 -3
  54. package/dist/{chunk-42UJ23H3.js → chunk-UIEKW4SG.js} +1 -0
  55. package/dist/{chunk-IESUM4N5.js → chunk-USE6HIOZ.js} +95 -62
  56. package/dist/{chunk-FIYW7JW4.js → chunk-USZLHYEQ.js} +1 -1
  57. package/dist/{chunk-U7U64UUN.js → chunk-V5D6GDNK.js} +4 -4
  58. package/dist/{chunk-ADVGV3P7.js → chunk-VB4VMB2I.js} +7 -5
  59. package/dist/{chunk-CQYFZOUC.js → chunk-VBMRZCMQ.js} +1 -1
  60. package/dist/{chunk-USAGHYRD.js → chunk-VF7IKIRS.js} +2 -2
  61. package/dist/{chunk-EJS3K44P.js → chunk-VGAMBOOC.js} +5 -3
  62. package/dist/{chunk-P5FUWXPW.js → chunk-VTESGA5F.js} +7 -5
  63. package/dist/chunk-WTTIODDV.js +9 -0
  64. package/dist/{chunk-5CBX66KB.js → chunk-XJQ6Z3FO.js} +2 -2
  65. package/dist/{chunk-KPGPK4SK.js → chunk-XOMIYMQ3.js} +6 -6
  66. package/dist/{chunk-KCHQVED5.js → chunk-YQMIKX54.js} +43 -7
  67. package/dist/completion-registry.js +3 -0
  68. package/dist/copy-image.d.ts +34 -0
  69. package/dist/copy-image.js +96 -0
  70. package/dist/countdown.js +1 -1
  71. package/dist/{dgmo-router-CZN36TR3.js → dgmo-router-6YLKM6WU.js} +13 -13
  72. package/dist/{dimensions-7HHKO5BS.js → dimensions-DS6FWLRY.js} +2 -2
  73. package/dist/{flowchart-parser-4MZPWYK6.js → flowchart-parser-J3VWIFWB.js} +5 -5
  74. package/dist/{flowchart-renderer-5XDN575E.js → flowchart-renderer-FECH5KK5.js} +14 -12
  75. package/dist/index.js +19 -19
  76. package/dist/layout-5SZ5A64I.js +24 -0
  77. package/dist/{layout-5GHV7HNM.js → layout-EWXHEXNS.js} +11 -10
  78. package/dist/{layout-QXGPY7KA.js → layout-TIBRMWHL.js} +7 -6
  79. package/dist/layout-VDCJOZSH.js +11 -0
  80. package/dist/layout-YBMT74MG.js +24 -0
  81. package/dist/{layout-search-EMTCDC3C.js → layout-search-AWPJWBKT.js} +9 -8
  82. package/dist/{parser-R3LPEQIT.js → parser-AR2GON37.js} +2 -2
  83. package/dist/{parser-YX72VLDZ.js → parser-BTSTKY5G.js} +5 -5
  84. package/dist/{parser-OGF2CDA6.js → parser-MA7L3XND.js} +5 -5
  85. package/dist/{parser-VKYUAT4Y.js → parser-ZRIZFDZY.js} +5 -5
  86. package/dist/{redraw-T324XAF3.js → redraw-AHW7EUUF.js} +2 -2
  87. package/dist/{redraw-MSXU4ZD2.js → redraw-STZ4WJAI.js} +3 -3
  88. package/dist/{renderer-HYGTMBHT.js → renderer-2LYX6NHN.js} +3 -3
  89. package/dist/{renderer-XJMVG2WJ.js → renderer-2QPHDBZY.js} +5 -5
  90. package/dist/{renderer-GPAVVUET.js → renderer-3NLDX7DQ.js} +3 -3
  91. package/dist/{renderer-IHCWBFPI.js → renderer-4VGO27EE.js} +14 -13
  92. package/dist/{renderer-JJ4QW3SK.js → renderer-6BDK47V6.js} +1 -1
  93. package/dist/{renderer-FL7EOSBU.js → renderer-7OGGV327.js} +3 -3
  94. package/dist/{renderer-5ITLW7MX.js → renderer-ACQQ2JXA.js} +6 -4
  95. package/dist/{renderer-37PLQHVB.js → renderer-BXD5KVSR.js} +2 -2
  96. package/dist/{renderer-TSCSZ6CL.js → renderer-HX6ICPDX.js} +2 -2
  97. package/dist/{renderer-BO73LSVT.js → renderer-IGSHD3OT.js} +4 -4
  98. package/dist/{renderer-DD6T3C4J.js → renderer-ISQJ2ELA.js} +2 -2
  99. package/dist/{renderer-CNRQFIEI.js → renderer-IVHD7ENY.js} +4 -4
  100. package/dist/{renderer-POBGKIUT.js → renderer-IXBOT6DU.js} +3 -3
  101. package/dist/{renderer-GFALLIYT.js → renderer-JYHA6R2D.js} +3 -3
  102. package/dist/{renderer-GOXAP2IE.js → renderer-KJ3V5BYP.js} +2 -2
  103. package/dist/{renderer-GFGFHYWY.js → renderer-MWZC7TMD.js} +2 -2
  104. package/dist/{renderer-3F47TPZ2.js → renderer-O6RP52CQ.js} +2 -2
  105. package/dist/{renderer-PBKTAWQV.js → renderer-P3WR7TKD.js} +14 -13
  106. package/dist/{renderer-VDBNC7A6.js → renderer-QTPWRRJ7.js} +2 -2
  107. package/dist/{renderer-J3QWK6IM.js → renderer-RUSQJFFC.js} +3 -3
  108. package/dist/{renderer-7Y3LR4R2.js → renderer-TINHO2IO.js} +2 -2
  109. package/dist/{renderer-J6H4Q3RM.js → renderer-UGW4N2W5.js} +4 -3
  110. package/dist/{renderer-EVKWGUGT.js → renderer-UTEEJDGB.js} +4 -3
  111. package/dist/{renderer-ISDKO3DI.js → renderer-WPN2SXGT.js} +2 -2
  112. package/dist/{renderer-6GJWADLO.js → renderer-XCLARXCK.js} +2 -2
  113. package/dist/{renderer-RDI2RYUP.js → renderer-XDX6S7OV.js} +3 -3
  114. package/dist/{renderer-VI34SD5V.js → renderer-Y47EM337.js} +6 -6
  115. package/dist/{renderer-HJ54YXT3.js → renderer-ZV6KCLCL.js} +14 -14
  116. package/dist/{state-parser-NPYTMRXP.js → state-parser-PMNHLHLE.js} +5 -5
  117. package/dist/{state-renderer-44K2KDX7.js → state-renderer-JQ5DS66J.js} +14 -13
  118. package/docs/language-reference.md +2 -2
  119. package/package.json +5 -1
  120. package/dist/layout-33FHGDEV.js +0 -23
  121. package/dist/layout-FTOGDNUL.js +0 -23
  122. package/dist/layout-J3AGFZZ7.js +0 -10
@@ -3,13 +3,13 @@ import {
3
3
  } from "./chunk-R2K5L4UA.js";
4
4
  import {
5
5
  formatDateLabel
6
- } from "./chunk-3GCASUWA.js";
6
+ } from "./chunk-TASRGDYP.js";
7
7
  import {
8
8
  parseTimelineDate
9
9
  } from "./chunk-BQO24BDM.js";
10
10
  import {
11
11
  layoutInlineHeader
12
- } from "./chunk-42UJ23H3.js";
12
+ } from "./chunk-UIEKW4SG.js";
13
13
  import {
14
14
  TITLE_FONT_SIZE,
15
15
  TITLE_FONT_WEIGHT,
@@ -31,10 +31,10 @@ import {
31
31
  } from "./chunk-JILEYZ2V.js";
32
32
  import {
33
33
  wrapDescriptionLines
34
- } from "./chunk-MB2SQF7N.js";
34
+ } from "./chunk-7WEYXYLC.js";
35
35
  import {
36
36
  renderInlineText
37
- } from "./chunk-65GPEV5V.js";
37
+ } from "./chunk-QKPOIJBT.js";
38
38
  import {
39
39
  FONT_FAMILY
40
40
  } from "./chunk-ICLB27PZ.js";
@@ -61,91 +61,34 @@ import {
61
61
 
62
62
  // src/event-line/renderer.ts
63
63
  import * as d3Selection from "d3-selection";
64
+
65
+ // src/event-line/layout.ts
64
66
  var CARD_W = 210;
65
67
  var H_MARGIN = 58;
66
- var TITLE_AREA = 48;
67
- var LEGEND_BAND = 34;
68
68
  var LEADER_ABOVE = 46;
69
69
  var LEADER_BELOW = 46;
70
70
  var DESC_FONT = 11.5;
71
71
  var DESC_LINE_H = 16;
72
72
  var CARD_PAD = 9;
73
- var CARD_BODY_TOP = HEADER_HEIGHT + SEPARATOR_GAP;
74
73
  var TITLE_LINE_H = 16;
75
74
  var TITLE_MAX_W = (CARD_W - CARD_PAD * 2) * 0.93;
76
- var DOT_R = 5.5;
77
75
  var MIN_SPACING = 96;
78
76
  var LANE_GAP = 16;
79
77
  var NEUTRAL_TAG = UNTAGGED_TAG_COLOR;
80
78
  var CARD_INSET = 18;
81
79
  var FAN_GAP = 6;
82
80
  var DATE_SUBTITLE_H = 15;
83
- var DATE_SUBTITLE_FONT = 10;
84
- var SHELF_TINT = 13;
85
- var SHELF_EDGE = 4;
86
- var SHELF_EDGE_STYLE = "spine";
87
- var SHELF_LIP = 2;
88
81
  var ERA_BLOCK = 30;
89
- var ERA_BRACKET_CAP = 8;
82
+ var DOT_R = 5.5;
90
83
  var ERA_BRACKET_PAD = 14;
91
84
  var ERA_SEAM_GAP = 3;
92
- var BREAK_HALF_H = 6;
93
- var BREAK_AMP = 2.6;
94
- var BREAK_GAP = 5;
95
85
  var ERA_LEG = 13;
96
- var ERA_LABEL_FONT = 11.5;
97
- var HL = "dgmo-evt-hl";
98
- var DIM = "dgmo-evt-dim";
99
- var ERA_HL = "dgmo-evt-era-hl";
100
- var COLLAPSED = "dgmo-evt-collapsed";
101
- var OFF = "dgmo-evt-off";
102
- var HIDDEN_ATTR = "data-evt-hidden";
103
- var HOVER_CSS = `.dgmo-event-dot{transition:transform .12s ease;transform-box:fill-box;transform-origin:center}.dgmo-event-leader,.dgmo-event-card,.dgmo-event-era{transition:opacity .12s ease,filter .12s ease}.dgmo-event-dot,.dgmo-event-card,.dgmo-event-era{cursor:default}[data-legend-entry]{cursor:pointer}.${DIM}{opacity:.2}.dgmo-event-dot.${HL}{transform:scale(1.55)}.dgmo-event-leader.${HL}{stroke-opacity:1;stroke-width:2.5}.dgmo-event-card.${HL}{filter:drop-shadow(0 2px 5px rgba(0,0,0,.22))}.dgmo-event-era.${ERA_HL}{filter:drop-shadow(0 1px 2px rgba(0,0,0,.28))}.dgmo-event-card.${COLLAPSED},.dgmo-event-leader.${COLLAPSED}{opacity:0;pointer-events:none}.dgmo-event-card.${COLLAPSED}.${HL},.dgmo-event-leader.${COLLAPSED}.${HL}{opacity:1;pointer-events:auto}[data-legend-entry].${OFF} text{opacity:.5}`;
104
- function renderEventLine(container, parsed, palette, isDark, onClickItem, exportDims, tagOverride, nowDate) {
105
- if (parsed.events.length === 0) return;
106
- d3Selection.select(container).selectAll(":not([data-d3-tooltip])").remove();
107
- const width = exportDims?.width ?? container.clientWidth;
108
- const heightHint = exportDims?.height ?? container.clientHeight;
109
- if (width <= 0 || heightHint <= 0) return;
110
- const exportMode = !!exportDims;
111
- const hiddenSet = exportMode ? /* @__PURE__ */ new Set() : readHidden(container);
86
+ function layoutEventLine(parsed, palette, isDark, opts) {
87
+ const { width, heightHint, exportMode, hiddenSet, activeGroup, nowDate } = opts;
88
+ const header = layoutHeader(parsed, activeGroup, width, exportMode);
89
+ const { topUsed } = header;
112
90
  const seriesColors = getSeriesColors(palette);
113
91
  const accent = seriesColors[0];
114
- const activeGroup = resolveActiveTagGroup(
115
- parsed.tagGroups,
116
- void 0,
117
- tagOverride
118
- );
119
- const showTitle = !!parsed.title && !parsed.options.noTitle;
120
- const hasLegend = parsed.tagGroups.length > 0 && activeGroup !== null && !parsed.options.noLegend;
121
- const titleH = showTitle ? TITLE_AREA : 0;
122
- const legendH = hasLegend ? LEGEND_BAND : 0;
123
- const inlineRequested = parsed.options.legendInline === true;
124
- const legendExtent = inlineRequested && hasLegend ? getLegendExtent(
125
- {
126
- groups: parsed.tagGroups,
127
- position: {
128
- placement: "top-center",
129
- titleRelation: "inline-with-title"
130
- },
131
- mode: exportMode ? "export" : "preview"
132
- },
133
- { activeGroup: activeGroup ?? null },
134
- width
135
- ) : { width: 0, height: 0 };
136
- const header = layoutInlineHeader({
137
- requested: inlineRequested,
138
- title: parsed.title ?? "",
139
- hasLegend,
140
- legendWidth: legendExtent.width,
141
- legendHeight: legendExtent.height,
142
- containerWidth: width,
143
- titleBandHeight: titleH,
144
- legendReserve: legendH,
145
- titleBaselineY: TITLE_Y,
146
- titleFontSize: TITLE_FONT_SIZE
147
- });
148
- const topUsed = header.inline ? titleH : titleH + legendH;
149
92
  const eraByName = new Map(parsed.eras.map((e) => [e.name, e]));
150
93
  const collapsedSet = new Set(
151
94
  parsed.eras.filter((e) => e.collapsed).map((e) => e.name)
@@ -447,10 +390,10 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
447
390
  const TOP_PAD = 14;
448
391
  const BOT_PAD = 14;
449
392
  const spineY2 = topUsed + TOP_PAD + aboveExt;
450
- const eraBaseY2 = eraSide === "above" ? spineY2 - (contentAbove + 14) : spineY2 + (contentBelow + 14);
451
- const contentH2 = spineY2 + belowExt + BOT_PAD;
452
- const totalH2 = exportMode ? contentH2 : Math.max(heightHint, contentH2);
453
- return { contentW: contentW2, contentH: contentH2, totalH: totalH2, spineY: spineY2, eraBaseY: eraBaseY2, laneNear: laneNear2 };
393
+ const eraBaseY = eraSide === "above" ? spineY2 - (contentAbove + 14) : spineY2 + (contentBelow + 14);
394
+ const contentH = spineY2 + belowExt + BOT_PAD;
395
+ const totalH2 = exportMode ? contentH : Math.max(heightHint, contentH);
396
+ return { contentW: contentW2, contentH, totalH: totalH2, spineY: spineY2, eraBaseY, laneNear: laneNear2 };
454
397
  };
455
398
  const envStretch = Number(
456
399
  globalThis.process?.env?.["DGMO_EVT_STRETCH"]
@@ -483,7 +426,307 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
483
426
  }
484
427
  }
485
428
  }
486
- const { contentW, contentH, totalH, spineY, eraBaseY, laneNear } = layout;
429
+ const { contentW, totalH, spineY, laneNear } = layout;
430
+ const spine = layoutSpine(placed, scaled);
431
+ const cards = layoutCards(placed, spineY, laneNear, spine);
432
+ return {
433
+ header,
434
+ placed,
435
+ scaled,
436
+ hasEras,
437
+ eraSide,
438
+ eraByName,
439
+ ...layout,
440
+ cards,
441
+ spine,
442
+ eraBrackets: hasEras ? layoutEraBrackets(placed, eraByName, contentW) : [],
443
+ now: parsed.now && scaled ? layoutNowMarker(parsed, placed, cards, {
444
+ spineY,
445
+ contentW,
446
+ totalH,
447
+ topUsed,
448
+ nowDate
449
+ }) : null
450
+ };
451
+ }
452
+ var TITLE_AREA = 48;
453
+ var LEGEND_BAND = 34;
454
+ function layoutHeader(parsed, activeGroup, width, exportMode) {
455
+ const showTitle = !!parsed.title && !parsed.options.noTitle;
456
+ const hasLegend = parsed.tagGroups.length > 0 && activeGroup !== null && !parsed.options.noLegend;
457
+ const titleH = showTitle ? TITLE_AREA : 0;
458
+ const legendH = hasLegend ? LEGEND_BAND : 0;
459
+ const inlineRequested = parsed.options.legendInline === true;
460
+ const legendExtent = inlineRequested && hasLegend ? getLegendExtent(
461
+ {
462
+ groups: parsed.tagGroups,
463
+ position: {
464
+ placement: "top-center",
465
+ titleRelation: "inline-with-title"
466
+ },
467
+ mode: exportMode ? "export" : "preview"
468
+ },
469
+ { activeGroup: activeGroup ?? null },
470
+ width
471
+ ) : { width: 0, height: 0 };
472
+ const placement = layoutInlineHeader({
473
+ requested: inlineRequested,
474
+ title: parsed.title ?? "",
475
+ hasLegend,
476
+ legendWidth: legendExtent.width,
477
+ legendHeight: legendExtent.height,
478
+ containerWidth: width,
479
+ titleBandHeight: titleH,
480
+ legendReserve: legendH,
481
+ titleBaselineY: TITLE_Y,
482
+ titleFontSize: TITLE_FONT_SIZE
483
+ });
484
+ const topUsed = placement.inline ? titleH : titleH + legendH;
485
+ return { showTitle, hasLegend, titleH, topUsed, placement };
486
+ }
487
+ function eventTagHidden(metadata, hidden) {
488
+ if (hidden.size === 0) return false;
489
+ for (const [k, v] of Object.entries(metadata)) {
490
+ if (hidden.has(`${k.toLowerCase()}:${String(v).toLowerCase()}`))
491
+ return true;
492
+ }
493
+ return false;
494
+ }
495
+ function wrapDescription(lines, charsPerLine) {
496
+ return wrapDescriptionLines([...lines], charsPerLine);
497
+ }
498
+ function layoutSpine(placed, scaled) {
499
+ const x0 = Math.min(...placed.map((p) => p.x - p.spanHalf));
500
+ const x1 = Math.max(...placed.map((p) => p.x + p.spanHalf));
501
+ const left = x0 - 20;
502
+ const right = x1 + 20;
503
+ const dashed = scaled && placed.some((p) => p.future && !p.futureSpan);
504
+ const realRightX = placed.length ? Math.max(
505
+ left,
506
+ ...placed.filter((p) => !p.future).map((p) => p.x + p.spanHalf)
507
+ ) : right;
508
+ return { left, right, solidRight: dashed ? realRightX : right, dashed };
509
+ }
510
+ function layoutCards(placed, spineY, laneNear, spine) {
511
+ const boxes = placed.map((p) => {
512
+ const near = p.side === "above" ? spineY - laneNear(p) : spineY + laneNear(p);
513
+ const top = p.side === "above" ? near - p.cardH : near;
514
+ return { p, near, top };
515
+ });
516
+ const leaderCrossesBox = (owner, near) => {
517
+ const lo = Math.min(spineY, near);
518
+ const hi = Math.max(spineY, near);
519
+ return boxes.some(
520
+ (g) => g.p !== owner && owner.x > g.p.left && owner.x < g.p.left + CARD_W && hi > g.top && lo < g.top + g.p.cardH
521
+ );
522
+ };
523
+ const eraBarY = (p) => spineY + ERA_LEG * (p.side === "above" ? -1 : 1);
524
+ return boxes.map(({ p, near, top }) => {
525
+ let eraBar = null;
526
+ if (p.kind === "era") {
527
+ const half = Math.max(16, p.spanHalf);
528
+ eraBar = {
529
+ x0: Math.max(spine.left, p.x - half),
530
+ x1: Math.min(spine.right, p.x + half),
531
+ y: eraBarY(p)
532
+ };
533
+ }
534
+ return {
535
+ p,
536
+ near,
537
+ top,
538
+ leaderFrom: p.kind === "era" ? eraBarY(p) : spineY,
539
+ leaderCrossesCard: leaderCrossesBox(p, near),
540
+ eraBar
541
+ };
542
+ });
543
+ }
544
+ function layoutEraBrackets(placed, eraByName, contentW) {
545
+ const runs = [];
546
+ let prevName = null;
547
+ for (const p of placed) {
548
+ const name = p.eraName;
549
+ if (name && eraByName.has(name)) {
550
+ if (prevName === name && runs.length > 0) {
551
+ const r = runs[runs.length - 1];
552
+ r.firstX = Math.min(r.firstX, p.x);
553
+ r.lastX = Math.max(r.lastX, p.x);
554
+ } else {
555
+ runs.push({
556
+ era: eraByName.get(name),
557
+ collapsed: p.kind === "era",
558
+ firstX: p.x,
559
+ lastX: p.x
560
+ });
561
+ }
562
+ prevName = name;
563
+ } else {
564
+ prevName = null;
565
+ }
566
+ }
567
+ const clampX = (v) => Math.max(4, Math.min(contentW - 4, v));
568
+ const drawn = runs.filter((r) => !r.collapsed).map((r) => ({
569
+ era: r.era,
570
+ firstX: r.firstX,
571
+ lastX: r.lastX,
572
+ x0: clampX(r.firstX - ERA_BRACKET_PAD),
573
+ x1: clampX(r.lastX + ERA_BRACKET_PAD)
574
+ })).sort((a, b) => a.firstX - b.firstX);
575
+ for (let i = 1; i < drawn.length; i++) {
576
+ const prev = drawn[i - 1];
577
+ const cur = drawn[i];
578
+ if (prev.x1 <= cur.x0) continue;
579
+ const mid = (prev.lastX + cur.firstX) / 2;
580
+ prev.x1 = Math.max(prev.lastX, Math.min(prev.x1, mid - ERA_SEAM_GAP));
581
+ cur.x0 = Math.min(cur.firstX, Math.max(cur.x0, mid + ERA_SEAM_GAP));
582
+ }
583
+ return drawn;
584
+ }
585
+ function layoutNowMarker(parsed, placed, cards, frame) {
586
+ const now = parsed.now;
587
+ const { spineY, contentW, totalH, topUsed } = frame;
588
+ let nowValue = now.dateValue;
589
+ let nowIso = now.date;
590
+ if (now.computed) {
591
+ const d = frame.nowDate ?? /* @__PURE__ */ new Date();
592
+ const iso = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
593
+ nowIso = iso;
594
+ const v = parseTimelineDate(iso);
595
+ nowValue = Number.isFinite(v) ? v : null;
596
+ }
597
+ const dated = placed.filter((p) => p.kind !== "era" && !p.future && p.dateValue !== null).sort((a, b) => a.dateValue - b.dateValue);
598
+ let nowX = nowValue === null ? null : valueToSpineX(nowValue, dated);
599
+ const lastReal = dated[dated.length - 1];
600
+ if (nowX !== null && nowValue !== null && lastReal && nowValue > lastReal.dateValue) {
601
+ const trailing = placed.filter((p) => p.future && !p.futureSpan).map((p) => p.x);
602
+ if (trailing.length) nowX = Math.min(...trailing);
603
+ }
604
+ if (nowX === null) return null;
605
+ const label = now.label ?? (nowIso ? formatDateLabel(nowIso) : "now");
606
+ const pillH = 18;
607
+ const pillW = Math.max(28, label.length * 6.6 + 16);
608
+ const PIN_GAP = 6;
609
+ const xHalf = pillW / 2 + 4;
610
+ const xLo = nowX - xHalf;
611
+ const xHi = nowX + xHalf;
612
+ const boxes = cards.map((g) => ({
613
+ side: g.p.side,
614
+ top: g.top,
615
+ bot: g.top + g.p.cardH,
616
+ left: g.p.left,
617
+ right: g.p.left + CARD_W
618
+ }));
619
+ const overlapsX = (b) => b.left < xHi && b.right > xLo;
620
+ const cands = [];
621
+ const addAbove = (pillBottom) => {
622
+ const y0 = pillBottom - pillH;
623
+ if (y0 > topUsed + 1)
624
+ cands.push({ y0, y1: pillBottom, stem: spineY - pillBottom });
625
+ };
626
+ const addBelow = (pillTop) => {
627
+ const y1 = pillTop + pillH;
628
+ if (y1 < totalH - 1)
629
+ cands.push({ y0: pillTop, y1, stem: pillTop - spineY });
630
+ };
631
+ addAbove(spineY - LEADER_ABOVE / 2 + pillH / 2);
632
+ addBelow(spineY + LEADER_BELOW / 2 - pillH / 2);
633
+ for (const b of boxes) {
634
+ if (!overlapsX(b)) continue;
635
+ if (b.side === "above") addAbove(b.top - PIN_GAP);
636
+ else addBelow(b.bot + PIN_GAP);
637
+ }
638
+ const isClear = (c) => !boxes.some((b) => overlapsX(b) && b.bot > c.y0 && b.top < c.y1);
639
+ const byStem = (a, b) => a.stem - b.stem;
640
+ const slot = cands.filter(isClear).sort(byStem)[0] ?? cands.sort(byStem)[0] ?? {
641
+ y0: spineY - PIN_GAP - pillH,
642
+ y1: spineY - PIN_GAP,
643
+ stem: PIN_GAP + pillH
644
+ };
645
+ const above = slot.y1 <= spineY;
646
+ return {
647
+ x: nowX,
648
+ label,
649
+ pillW,
650
+ pillH,
651
+ pillCX: Math.max(pillW / 2 + 2, Math.min(nowX, contentW - pillW / 2 - 2)),
652
+ y0: slot.y0,
653
+ y1: slot.y1,
654
+ stemFrom: above ? spineY - DOT_R : spineY + DOT_R,
655
+ stemTo: above ? slot.y1 : slot.y0
656
+ };
657
+ }
658
+ function valueToSpineX(v, dated) {
659
+ if (!dated.length) return null;
660
+ const first = dated[0];
661
+ const last = dated[dated.length - 1];
662
+ if (v <= first.dateValue) return first.x;
663
+ if (v >= last.dateValue) return last.x;
664
+ for (let i = 0; i < dated.length - 1; i++) {
665
+ const a = dated[i];
666
+ const b = dated[i + 1];
667
+ if (v >= a.dateValue && v <= b.dateValue) {
668
+ const span = b.dateValue - a.dateValue;
669
+ if (span <= 0) return a.x;
670
+ return a.x + (v - a.dateValue) / span * (b.x - a.x);
671
+ }
672
+ }
673
+ return last.x;
674
+ }
675
+
676
+ // src/event-line/renderer.ts
677
+ var CARD_BODY_TOP = HEADER_HEIGHT + SEPARATOR_GAP;
678
+ var DATE_SUBTITLE_FONT = 10;
679
+ var SHELF_TINT = 13;
680
+ var SHELF_EDGE = 4;
681
+ var SHELF_EDGE_STYLE = "spine";
682
+ var SHELF_LIP = 2;
683
+ var ERA_BRACKET_CAP = 8;
684
+ var BREAK_HALF_H = 6;
685
+ var BREAK_AMP = 2.6;
686
+ var BREAK_GAP = 5;
687
+ var ERA_LABEL_FONT = 11.5;
688
+ var HL = "dgmo-evt-hl";
689
+ var DIM = "dgmo-evt-dim";
690
+ var ERA_HL = "dgmo-evt-era-hl";
691
+ var COLLAPSED = "dgmo-evt-collapsed";
692
+ var OFF = "dgmo-evt-off";
693
+ var HIDDEN_ATTR = "data-evt-hidden";
694
+ var HOVER_CSS = `.dgmo-event-dot{transition:transform .12s ease;transform-box:fill-box;transform-origin:center}.dgmo-event-leader,.dgmo-event-card,.dgmo-event-era{transition:opacity .12s ease,filter .12s ease}.dgmo-event-dot,.dgmo-event-card,.dgmo-event-era{cursor:default}[data-legend-entry]{cursor:pointer}.${DIM}{opacity:.2}.dgmo-event-dot.${HL}{transform:scale(1.55)}.dgmo-event-leader.${HL}{stroke-opacity:1;stroke-width:2.5}.dgmo-event-card.${HL}{filter:drop-shadow(0 2px 5px rgba(0,0,0,.22))}.dgmo-event-era.${ERA_HL}{filter:drop-shadow(0 1px 2px rgba(0,0,0,.28))}.dgmo-event-card.${COLLAPSED},.dgmo-event-leader.${COLLAPSED}{opacity:0;pointer-events:none}.dgmo-event-card.${COLLAPSED}.${HL},.dgmo-event-leader.${COLLAPSED}.${HL}{opacity:1;pointer-events:auto}[data-legend-entry].${OFF} text{opacity:.5}`;
695
+ function renderEventLine(container, parsed, palette, isDark, onClickItem, exportDims, tagOverride, nowDate) {
696
+ if (parsed.events.length === 0) return;
697
+ d3Selection.select(container).selectAll(":not([data-d3-tooltip])").remove();
698
+ const width = exportDims?.width ?? container.clientWidth;
699
+ const heightHint = exportDims?.height ?? container.clientHeight;
700
+ if (width <= 0 || heightHint <= 0) return;
701
+ const exportMode = !!exportDims;
702
+ const hiddenSet = exportMode ? /* @__PURE__ */ new Set() : readHidden(container);
703
+ const activeGroup = resolveActiveTagGroup(
704
+ parsed.tagGroups,
705
+ void 0,
706
+ tagOverride
707
+ );
708
+ const {
709
+ header: band,
710
+ hasEras,
711
+ eraSide,
712
+ contentW,
713
+ contentH,
714
+ totalH,
715
+ spineY,
716
+ eraBaseY,
717
+ cards,
718
+ spine,
719
+ eraBrackets,
720
+ now
721
+ } = layoutEventLine(parsed, palette, isDark, {
722
+ width,
723
+ heightHint,
724
+ exportMode,
725
+ hiddenSet,
726
+ activeGroup,
727
+ nowDate
728
+ });
729
+ const { showTitle, hasLegend, titleH, topUsed, placement: header } = band;
487
730
  const svg = d3Selection.select(container).append("svg").attr("width", contentW).attr("height", totalH).attr("viewBox", `0 0 ${contentW} ${totalH}`).attr("preserveAspectRatio", "xMidYMin meet").attr("xmlns", "http://www.w3.org/2000/svg").style("font-family", FONT_FAMILY);
488
731
  if (!exportMode && totalH > contentH) {
489
732
  svg.attr("data-content-height", Math.ceil(contentH));
@@ -525,43 +768,16 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
525
768
  });
526
769
  if (!exportMode) addLegendEntryHitAreas(legendG);
527
770
  }
528
- const x0 = Math.min(...placed.map((p) => p.x - p.spanHalf));
529
- const x1 = Math.max(...placed.map((p) => p.x + p.spanHalf));
530
- const spineLeft = x0 - 20;
531
- const spineRight = x1 + 20;
532
- const hasTrailingFuture = scaled && placed.some((p) => p.future && !p.futureSpan);
533
- const realRightX = placed.length ? Math.max(
534
- spineLeft,
535
- ...placed.filter((p) => !p.future).map((p) => p.x + p.spanHalf)
536
- ) : spineRight;
537
- const solidRight = hasTrailingFuture ? realRightX : spineRight;
538
- svg.append("line").attr("x1", spineLeft).attr("y1", spineY).attr("x2", solidRight).attr("y2", spineY).attr("stroke", palette.text).attr("stroke-width", 2.5).attr("stroke-linecap", "round");
539
- if (hasTrailingFuture) {
540
- svg.append("line").attr("x1", solidRight).attr("y1", spineY).attr("x2", spineRight).attr("y2", spineY).attr("stroke", palette.text).attr("stroke-width", 2.5).attr("stroke-linecap", "round").attr("stroke-opacity", 0.45).attr("stroke-dasharray", HAIRLINE_DASH);
771
+ svg.append("line").attr("x1", spine.left).attr("y1", spineY).attr("x2", spine.solidRight).attr("y2", spineY).attr("stroke", palette.text).attr("stroke-width", 2.5).attr("stroke-linecap", "round");
772
+ if (spine.dashed) {
773
+ svg.append("line").attr("x1", spine.solidRight).attr("y1", spineY).attr("x2", spine.right).attr("y2", spineY).attr("stroke", palette.text).attr("stroke-width", 2.5).attr("stroke-linecap", "round").attr("stroke-opacity", 0.45).attr("stroke-dasharray", HAIRLINE_DASH);
541
774
  }
542
- const geo = placed.map((p) => {
543
- const near = p.side === "above" ? spineY - laneNear(p) : spineY + laneNear(p);
544
- const top = p.side === "above" ? near - p.cardH : near;
545
- return { p, near, top };
546
- });
547
- const leaderCrossesBox = (owner, near) => {
548
- const lo = Math.min(spineY, near);
549
- const hi = Math.max(spineY, near);
550
- return geo.some(
551
- (g) => g.p !== owner && owner.x > g.p.left && owner.x < g.p.left + CARD_W && hi > g.top && lo < g.top + g.p.cardH
552
- );
553
- };
554
- const eraBarY = (p) => spineY + ERA_LEG * (p.side === "above" ? -1 : 1);
555
- for (const { p, near } of geo) {
556
- const spineEnd = p.kind === "era" ? eraBarY(p) : spineY;
557
- const leader = svg.append("line").attr("class", "dgmo-event-leader").attr("x1", p.x).attr("y1", spineEnd).attr("x2", p.x).attr("y2", near).attr("stroke", p.color).attr("stroke-width", 1.5).attr(
558
- "stroke-opacity",
559
- leaderCrossesBox(p, near) ? 0.18 : p.future ? 0.4 : 0.65
560
- );
775
+ for (const { p, near, leaderFrom, leaderCrossesCard } of cards) {
776
+ const leader = svg.append("line").attr("class", "dgmo-event-leader").attr("x1", p.x).attr("y1", leaderFrom).attr("x2", p.x).attr("y2", near).attr("stroke", p.color).attr("stroke-width", 1.5).attr("stroke-opacity", leaderCrossesCard ? 0.18 : p.future ? 0.4 : 0.65);
561
777
  applyHoverHooks(leader, p);
562
778
  }
563
779
  let shelfClipSeq = 0;
564
- for (const { p, top } of geo) {
780
+ for (const { p, top } of cards) {
565
781
  const left = p.left;
566
782
  const cardG = svg.append("g").attr("class", "dgmo-event-card").attr("transform", `translate(${left}, ${top})`).attr("data-line-number", p.lineNumber);
567
783
  applyHoverHooks(cardG, p);
@@ -659,15 +875,10 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
659
875
  }
660
876
  }
661
877
  }
662
- for (const p of placed) {
663
- if (p.kind === "era") {
878
+ for (const { p, eraBar } of cards) {
879
+ if (eraBar) {
664
880
  const col = p.color;
665
- const half = Math.max(16, p.spanHalf);
666
- const spineLeft2 = x0 - 20;
667
- const spineRight2 = x1 + 20;
668
- const bx0 = Math.max(spineLeft2, p.x - half);
669
- const bx1 = Math.min(spineRight2, p.x + half);
670
- const barY = eraBarY(p);
881
+ const { x0: bx0, x1: bx1, y: barY } = eraBar;
671
882
  const eg = svg.append("g").attr("class", "dgmo-event-era").attr("data-era", p.era.name).attr("data-era-collapsed", "true").attr("data-line-number", p.lineNumber);
672
883
  if (onClickItem) {
673
884
  const hy0 = Math.min(barY, spineY) - 4;
@@ -693,52 +904,17 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
693
904
  applyHoverHooks(dot, p);
694
905
  }
695
906
  if (hasEras) {
696
- const runs = [];
697
- let prevName = null;
698
- for (const p of placed) {
699
- const name = p.eraName;
700
- if (name && eraByName.has(name)) {
701
- if (prevName === name && runs.length > 0) {
702
- const r = runs[runs.length - 1];
703
- r.firstX = Math.min(r.firstX, p.x);
704
- r.lastX = Math.max(r.lastX, p.x);
705
- } else {
706
- runs.push({
707
- era: eraByName.get(name),
708
- collapsed: p.kind === "era",
709
- firstX: p.x,
710
- lastX: p.x
711
- });
712
- }
713
- prevName = name;
714
- } else {
715
- prevName = null;
716
- }
717
- }
718
- const clampX = (v) => Math.max(4, Math.min(contentW - 4, v));
719
907
  const cap = eraSide === "above" ? 1 : -1;
720
- const drawn = runs.filter((r) => !r.collapsed).map((r) => ({
721
- run: r,
722
- x0: clampX(r.firstX - ERA_BRACKET_PAD),
723
- x1: clampX(r.lastX + ERA_BRACKET_PAD)
724
- })).sort((a, b) => a.run.firstX - b.run.firstX);
725
- for (let i = 1; i < drawn.length; i++) {
726
- const prev = drawn[i - 1];
727
- const cur = drawn[i];
728
- if (prev.x1 <= cur.x0) continue;
729
- const mid = (prev.run.lastX + cur.run.firstX) / 2;
730
- prev.x1 = Math.max(prev.run.lastX, Math.min(prev.x1, mid - ERA_SEAM_GAP));
731
- cur.x0 = Math.min(cur.run.firstX, Math.max(cur.x0, mid + ERA_SEAM_GAP));
732
- }
733
- for (const { run: r, x0: x02, x1: x12 } of drawn) {
908
+ for (const r of eraBrackets) {
909
+ const { x0, x1 } = r;
734
910
  const neutral = !r.era.color;
735
911
  const col = neutral ? palette.text : resolveColor(r.era.color, palette) ?? palette.text;
736
912
  const op = neutral ? 0.5 : 0.85;
737
913
  const y = eraBaseY;
738
- const eg = svg.append("g").attr("class", "dgmo-event-era").attr("data-era", r.era.name).attr("data-era-collapsed", String(r.collapsed)).attr("data-line-number", r.era.lineNumber);
914
+ const eg = svg.append("g").attr("class", "dgmo-event-era").attr("data-era", r.era.name).attr("data-era-collapsed", "false").attr("data-line-number", r.era.lineNumber);
739
915
  eg.append("path").attr(
740
916
  "d",
741
- `M${x02},${y + ERA_BRACKET_CAP * cap} L${x02},${y} L${x12},${y} L${x12},${y + ERA_BRACKET_CAP * cap}`
917
+ `M${x0},${y + ERA_BRACKET_CAP * cap} L${x0},${y} L${x1},${y} L${x1},${y + ERA_BRACKET_CAP * cap}`
742
918
  ).attr("fill", "none").attr("stroke", col).attr("stroke-width", 1.5).attr("stroke-linecap", "round").attr("stroke-linejoin", "round").attr("stroke-opacity", op);
743
919
  eg.append("text").attr("x", (r.firstX + r.lastX) / 2).attr("y", eraSide === "above" ? y - 6 : y + ERA_LABEL_FONT + 4).attr("text-anchor", "middle").attr("font-family", FONT_FAMILY).attr("font-size", ERA_LABEL_FONT).attr("font-weight", "bold").attr("fill", col).attr("fill-opacity", neutral ? 0.85 : 1).text(r.era.name);
744
920
  if (onClickItem) {
@@ -747,110 +923,47 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
747
923
  }
748
924
  }
749
925
  }
750
- if (parsed.now && scaled) {
751
- let nowValue = parsed.now.dateValue;
752
- let nowIso = parsed.now.date;
753
- if (parsed.now.computed) {
754
- const d = nowDate ?? /* @__PURE__ */ new Date();
755
- const iso = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
756
- nowIso = iso;
757
- const v = parseTimelineDate(iso);
758
- nowValue = Number.isFinite(v) ? v : null;
759
- }
760
- const dated = placed.filter((p) => p.kind !== "era" && !p.future && p.dateValue !== null).sort((a, b) => a.dateValue - b.dateValue);
761
- let nowX = nowValue === null ? null : valueToSpineX(nowValue, dated);
762
- const lastReal = dated[dated.length - 1];
763
- if (nowX !== null && nowValue !== null && lastReal && nowValue > lastReal.dateValue) {
764
- const trailing = placed.filter((p) => p.future && !p.futureSpan).map((p) => p.x);
765
- if (trailing.length) nowX = Math.min(...trailing);
766
- }
767
- if (nowX !== null) {
768
- const base = themeBaseBg(palette, isDark);
769
- const nowColor = parsed.now.color ? resolveColor(parsed.now.color, palette) ?? palette.destructive : palette.destructive;
770
- const fillMode = parsed.options.fillMode;
771
- const pillFill = shapeFill(palette, nowColor, isDark, { mode: fillMode });
772
- const pillStroke = fillMode === "solid" ? "none" : fillMode === "outline" ? nowColor : mix(nowColor, base, 55);
773
- const pillStrokeW = fillMode === "solid" ? 0 : fillMode === "outline" ? 1.3 : 1;
774
- const pillText = fillMode === "solid" ? contrastText(
775
- pillFill,
776
- palette.textOnFillLight,
777
- palette.textOnFillDark
778
- ) : nowColor;
779
- const label = parsed.now.label ?? (nowIso ? formatDateLabel(nowIso) : "now");
780
- const pillH = 18;
781
- const pillW = Math.max(28, label.length * 6.6 + 16);
782
- const PIN_GAP = 6;
783
- const xHalf = pillW / 2 + 4;
784
- const xLo = nowX - xHalf;
785
- const xHi = nowX + xHalf;
786
- const boxes = geo.map((g) => ({
787
- side: g.p.side,
788
- top: g.top,
789
- bot: g.top + g.p.cardH,
790
- left: g.p.left,
791
- right: g.p.left + CARD_W
792
- }));
793
- const overlapsX = (b) => b.left < xHi && b.right > xLo;
794
- const cands = [];
795
- const addAbove = (pillBottom) => {
796
- const y0 = pillBottom - pillH;
797
- if (y0 > topUsed + 1)
798
- cands.push({ y0, y1: pillBottom, stem: spineY - pillBottom });
799
- };
800
- const addBelow = (pillTop) => {
801
- const y1 = pillTop + pillH;
802
- if (y1 < totalH - 1)
803
- cands.push({ y0: pillTop, y1, stem: pillTop - spineY });
804
- };
805
- addAbove(spineY - LEADER_ABOVE / 2 + pillH / 2);
806
- addBelow(spineY + LEADER_BELOW / 2 - pillH / 2);
807
- for (const b of boxes) {
808
- if (!overlapsX(b)) continue;
809
- if (b.side === "above") addAbove(b.top - PIN_GAP);
810
- else addBelow(b.bot + PIN_GAP);
811
- }
812
- const isClear = (c) => !boxes.some((b) => overlapsX(b) && b.bot > c.y0 && b.top < c.y1);
813
- const byStem = (a, b) => a.stem - b.stem;
814
- const slot = cands.filter(isClear).sort(byStem)[0] ?? cands.sort(byStem)[0] ?? {
815
- y0: spineY - PIN_GAP - pillH,
816
- y1: spineY - PIN_GAP,
817
- stem: PIN_GAP + pillH
818
- };
819
- const above = slot.y1 <= spineY;
820
- const stemFrom = above ? spineY - DOT_R : spineY + DOT_R;
821
- const stemTo = above ? slot.y1 : slot.y0;
822
- const pillCX = Math.max(
823
- pillW / 2 + 2,
824
- Math.min(nowX, contentW - pillW / 2 - 2)
825
- );
826
- const nowG = svg.append("g").attr("class", "evt-now").attr("data-now-x", nowX.toFixed(2));
827
- const FADE_SPAN = Math.min(LEADER_ABOVE, LEADER_BELOW);
828
- const ruleTop = spineY - FADE_SPAN;
829
- const ruleBot = spineY + FADE_SPAN;
830
- const gradId = `evt-now-fade-${nowX.toFixed(0)}`;
831
- const grad = nowG.append("defs").append("linearGradient").attr("id", gradId).attr("gradientUnits", "userSpaceOnUse").attr("x1", 0).attr("y1", ruleTop).attr("x2", 0).attr("y2", ruleBot);
832
- grad.append("stop").attr("offset", "0%").attr("stop-color", nowColor).attr("stop-opacity", 0);
833
- grad.append("stop").attr("offset", "50%").attr("stop-color", nowColor).attr("stop-opacity", 0.9);
834
- grad.append("stop").attr("offset", "100%").attr("stop-color", nowColor).attr("stop-opacity", 0);
835
- nowG.append("line").attr("x1", nowX).attr("y1", ruleTop).attr("x2", nowX).attr("y2", ruleBot).attr("stroke", `url(#${gradId})`).attr("stroke-width", 1.6).attr("stroke-dasharray", HAIRLINE_DASH).attr("stroke-linecap", "round");
836
- nowG.append("path").attr("d", `M${nowX} ${spineY - 5} l5 5 l-5 5 l-5 -5 z`).attr("fill", nowColor);
837
- nowG.append("line").attr("x1", nowX).attr("y1", stemFrom).attr("x2", nowX).attr("y2", stemTo).attr("stroke", nowColor).attr("stroke-width", 1.6);
838
- nowG.append("rect").attr("x", pillCX - pillW / 2).attr("y", slot.y0).attr("width", pillW).attr("height", pillH).attr("rx", pillH / 2).attr("fill", pillFill).attr("stroke", pillStroke).attr("stroke-width", pillStrokeW);
839
- nowG.append("text").attr("x", pillCX).attr("y", slot.y0 + pillH / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-family", FONT_FAMILY).attr("font-size", 10.5).attr("font-weight", "bold").attr("fill", pillText).text(label);
840
- if (!exportMode) {
841
- const hoverRule = nowG.append("line").attr("class", "evt-now-rule-full").attr("x1", nowX).attr("y1", Math.max(topUsed, 2)).attr("x2", nowX).attr("y2", totalH - 2).attr("stroke", nowColor).attr("stroke-width", 1.4).attr("stroke-dasharray", HAIRLINE_DASH).attr("stroke-linecap", "round").attr("opacity", 0).style("transition", "opacity 120ms ease").style("pointer-events", "none");
842
- hoverRule.lower();
843
- const nowNode = nowG.node();
844
- if (nowNode) {
845
- nowNode.addEventListener(
846
- "mouseenter",
847
- () => hoverRule.attr("opacity", 0.5)
848
- );
849
- nowNode.addEventListener(
850
- "mouseleave",
851
- () => hoverRule.attr("opacity", 0)
852
- );
853
- }
926
+ if (parsed.now && now) {
927
+ const nowX = now.x;
928
+ const base = themeBaseBg(palette, isDark);
929
+ const nowColor = parsed.now.color ? resolveColor(parsed.now.color, palette) ?? palette.destructive : palette.destructive;
930
+ const fillMode = parsed.options.fillMode;
931
+ const pillFill = shapeFill(palette, nowColor, isDark, { mode: fillMode });
932
+ const pillStroke = fillMode === "solid" ? "none" : fillMode === "outline" ? nowColor : mix(nowColor, base, 55);
933
+ const pillStrokeW = fillMode === "solid" ? 0 : fillMode === "outline" ? 1.3 : 1;
934
+ const pillText = fillMode === "solid" ? contrastText(
935
+ pillFill,
936
+ palette.textOnFillLight,
937
+ palette.textOnFillDark
938
+ ) : nowColor;
939
+ const { label, pillW, pillH, pillCX, y0, stemFrom, stemTo } = now;
940
+ const nowG = svg.append("g").attr("class", "evt-now").attr("data-now-x", nowX.toFixed(2));
941
+ const FADE_SPAN = Math.min(LEADER_ABOVE, LEADER_BELOW);
942
+ const ruleTop = spineY - FADE_SPAN;
943
+ const ruleBot = spineY + FADE_SPAN;
944
+ const gradId = `evt-now-fade-${nowX.toFixed(0)}`;
945
+ const grad = nowG.append("defs").append("linearGradient").attr("id", gradId).attr("gradientUnits", "userSpaceOnUse").attr("x1", 0).attr("y1", ruleTop).attr("x2", 0).attr("y2", ruleBot);
946
+ grad.append("stop").attr("offset", "0%").attr("stop-color", nowColor).attr("stop-opacity", 0);
947
+ grad.append("stop").attr("offset", "50%").attr("stop-color", nowColor).attr("stop-opacity", 0.9);
948
+ grad.append("stop").attr("offset", "100%").attr("stop-color", nowColor).attr("stop-opacity", 0);
949
+ nowG.append("line").attr("x1", nowX).attr("y1", ruleTop).attr("x2", nowX).attr("y2", ruleBot).attr("stroke", `url(#${gradId})`).attr("stroke-width", 1.6).attr("stroke-dasharray", HAIRLINE_DASH).attr("stroke-linecap", "round");
950
+ nowG.append("path").attr("d", `M${nowX} ${spineY - 5} l5 5 l-5 5 l-5 -5 z`).attr("fill", nowColor);
951
+ nowG.append("line").attr("x1", nowX).attr("y1", stemFrom).attr("x2", nowX).attr("y2", stemTo).attr("stroke", nowColor).attr("stroke-width", 1.6);
952
+ nowG.append("rect").attr("x", pillCX - pillW / 2).attr("y", y0).attr("width", pillW).attr("height", pillH).attr("rx", pillH / 2).attr("fill", pillFill).attr("stroke", pillStroke).attr("stroke-width", pillStrokeW);
953
+ nowG.append("text").attr("x", pillCX).attr("y", y0 + pillH / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-family", FONT_FAMILY).attr("font-size", 10.5).attr("font-weight", "bold").attr("fill", pillText).text(label);
954
+ if (!exportMode) {
955
+ const hoverRule = nowG.append("line").attr("class", "evt-now-rule-full").attr("x1", nowX).attr("y1", Math.max(topUsed, 2)).attr("x2", nowX).attr("y2", totalH - 2).attr("stroke", nowColor).attr("stroke-width", 1.4).attr("stroke-dasharray", HAIRLINE_DASH).attr("stroke-linecap", "round").attr("opacity", 0).style("transition", "opacity 120ms ease").style("pointer-events", "none");
956
+ hoverRule.lower();
957
+ const nowNode = nowG.node();
958
+ if (nowNode) {
959
+ nowNode.addEventListener(
960
+ "mouseenter",
961
+ () => hoverRule.attr("opacity", 0.5)
962
+ );
963
+ nowNode.addEventListener(
964
+ "mouseleave",
965
+ () => hoverRule.attr("opacity", 0)
966
+ );
854
967
  }
855
968
  }
856
969
  }
@@ -972,14 +1085,6 @@ function addLegendEntryHitAreas(legendG) {
972
1085
  d3Selection.select(this).insert("rect", ":first-child").attr("x", bb.x - PAD_X).attr("y", bb.y - PAD_Y).attr("width", bb.width + PAD_X * 2).attr("height", bb.height + PAD_Y * 2).attr("fill", "transparent").attr("data-legend-hit", "");
973
1086
  });
974
1087
  }
975
- function eventTagHidden(metadata, hidden) {
976
- if (hidden.size === 0) return false;
977
- for (const [k, v] of Object.entries(metadata)) {
978
- if (hidden.has(`${k.toLowerCase()}:${String(v).toLowerCase()}`))
979
- return true;
980
- }
981
- return false;
982
- }
983
1088
  function applyEvtHidden(container, root) {
984
1089
  const hidden = readHidden(container);
985
1090
  const isHidden = (el) => {
@@ -1092,23 +1197,6 @@ function renderEventLineForExport(container, parsed, palette, isDark, exportDims
1092
1197
  nowDate
1093
1198
  );
1094
1199
  }
1095
- function valueToSpineX(v, dated) {
1096
- if (!dated.length) return null;
1097
- const first = dated[0];
1098
- const last = dated[dated.length - 1];
1099
- if (v <= first.dateValue) return first.x;
1100
- if (v >= last.dateValue) return last.x;
1101
- for (let i = 0; i < dated.length - 1; i++) {
1102
- const a = dated[i];
1103
- const b = dated[i + 1];
1104
- if (v >= a.dateValue && v <= b.dateValue) {
1105
- const span = b.dateValue - a.dateValue;
1106
- if (span <= 0) return a.x;
1107
- return a.x + (v - a.dateValue) / span * (b.x - a.x);
1108
- }
1109
- }
1110
- return last.x;
1111
- }
1112
1200
  function axisBreakPaths(cx, cy) {
1113
1201
  const HUMPS = 2;
1114
1202
  const STEPS = 48;
@@ -1125,9 +1213,6 @@ function axisBreakPaths(cx, cy) {
1125
1213
  };
1126
1214
  return [wave(cx - BREAK_GAP / 2), wave(cx + BREAK_GAP / 2)];
1127
1215
  }
1128
- function wrapDescription(lines, charsPerLine) {
1129
- return wrapDescriptionLines([...lines], charsPerLine);
1130
- }
1131
1216
  function renderBody(cardG, lines, bodyColor, palette, startBaseline = CARD_BODY_TOP + DESC_FONT, bulletColors = [], bulletMeta = []) {
1132
1217
  let y = startBaseline;
1133
1218
  let bulletIdx = 0;