sketchmark 0.2.0 → 0.2.2

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.
@@ -262,10 +262,6 @@ var AIDiagram = (function (exports) {
262
262
  s.color = p.color;
263
263
  if (p.opacity)
264
264
  s.opacity = parseFloat(p.opacity);
265
- if (p.radius)
266
- s.radius = parseFloat(p.radius);
267
- if (p.shadow)
268
- s.shadow = p.shadow === "true";
269
265
  if (p["font-size"])
270
266
  s.fontSize = parseFloat(p["font-size"]);
271
267
  if (p["font-weight"])
@@ -282,8 +278,9 @@ var AIDiagram = (function (exports) {
282
278
  s.letterSpacing = parseFloat(p["letter-spacing"]);
283
279
  if (p.font)
284
280
  s.font = p.font;
285
- if (p["dash"]) {
286
- const parts = p["dash"]
281
+ const dashVal = p["dash"] || p["stroke-dash"];
282
+ if (dashVal) {
283
+ const parts = dashVal
287
284
  .split(",")
288
285
  .map(Number)
289
286
  .filter((n) => !isNaN(n));
@@ -1490,13 +1487,22 @@ var AIDiagram = (function (exports) {
1490
1487
  n.h = n.h || 50;
1491
1488
  break;
1492
1489
  case "text": {
1493
- // read fontSize from style if set, otherwise use default
1494
1490
  const fontSize = Number(n.style?.fontSize ?? 13);
1495
1491
  const charWidth = fontSize * 0.55;
1496
- const maxW = n.width ?? 400;
1497
- const approxLines = Math.ceil((n.label.length * charWidth) / (maxW - 16));
1498
- n.w = maxW;
1499
- n.h = n.height ?? Math.max(24, approxLines * fontSize * 1.5 + 8);
1492
+ const pad = Number(n.style?.padding ?? 8) * 2;
1493
+ if (n.width) {
1494
+ // User set width → word-wrap within it
1495
+ const approxLines = Math.ceil((n.label.length * charWidth) / (n.width - pad));
1496
+ n.w = n.width;
1497
+ n.h = n.height ?? Math.max(24, approxLines * fontSize * 1.5 + pad);
1498
+ }
1499
+ else {
1500
+ // Auto-size to content
1501
+ const lines = n.label.split("\\n");
1502
+ const longest = lines.reduce((a, b) => (a.length > b.length ? a : b), "");
1503
+ n.w = Math.max(MIN_W, Math.round(longest.length * charWidth + pad));
1504
+ n.h = n.height ?? Math.max(24, lines.length * fontSize * 1.5 + pad);
1505
+ }
1500
1506
  break;
1501
1507
  }
1502
1508
  default:
@@ -1713,17 +1719,16 @@ var AIDiagram = (function (exports) {
1713
1719
  if (layout === "row") {
1714
1720
  const ws = kids.map((r) => iW(r, nm, gm, tm, ntm, cm, mdm));
1715
1721
  const hs = kids.map((r) => iH(r, nm, gm, tm, ntm, cm, mdm));
1716
- const maxH = Math.max(...hs);
1717
1722
  const { start, gaps } = distribute(ws, contentW, gap, justify);
1718
1723
  let x = contentX + start;
1719
1724
  for (let i = 0; i < kids.length; i++) {
1720
1725
  let y;
1721
1726
  switch (align) {
1722
1727
  case "center":
1723
- y = contentY + (maxH - hs[i]) / 2;
1728
+ y = contentY + (contentH - hs[i]) / 2;
1724
1729
  break;
1725
1730
  case "end":
1726
- y = contentY + maxH - hs[i];
1731
+ y = contentY + contentH - hs[i];
1727
1732
  break;
1728
1733
  default:
1729
1734
  y = contentY;
@@ -1744,17 +1749,16 @@ var AIDiagram = (function (exports) {
1744
1749
  // column (default)
1745
1750
  const ws = kids.map((r) => iW(r, nm, gm, tm, ntm, cm, mdm));
1746
1751
  const hs = kids.map((r) => iH(r, nm, gm, tm, ntm, cm, mdm));
1747
- const maxW = Math.max(...ws);
1748
1752
  const { start, gaps } = distribute(hs, contentH, gap, justify);
1749
1753
  let y = contentY + start;
1750
1754
  for (let i = 0; i < kids.length; i++) {
1751
1755
  let x;
1752
1756
  switch (align) {
1753
1757
  case "center":
1754
- x = contentX + (maxW - ws[i]) / 2;
1758
+ x = contentX + (contentW - ws[i]) / 2;
1755
1759
  break;
1756
1760
  case "end":
1757
- x = contentX + maxW - ws[i];
1761
+ x = contentX + contentW - ws[i];
1758
1762
  break;
1759
1763
  default:
1760
1764
  x = contentX;
@@ -2178,13 +2182,13 @@ var AIDiagram = (function (exports) {
2178
2182
  g.setAttribute('class', cls);
2179
2183
  return g;
2180
2184
  }
2181
- function mkT(txt, x, y, sz = 10, wt = 400, col = '#4a2e10', anchor = 'middle') {
2185
+ function mkT(txt, x, y, sz = 10, wt = 400, col = '#4a2e10', anchor = 'middle', font = 'system-ui, sans-serif') {
2182
2186
  const t = se$1('text');
2183
2187
  t.setAttribute('x', String(x));
2184
2188
  t.setAttribute('y', String(y));
2185
2189
  t.setAttribute('text-anchor', anchor);
2186
2190
  t.setAttribute('dominant-baseline', 'middle');
2187
- t.setAttribute('font-family', 'system-ui, sans-serif');
2191
+ t.setAttribute('font-family', font);
2188
2192
  t.setAttribute('font-size', String(sz));
2189
2193
  t.setAttribute('font-weight', String(wt));
2190
2194
  t.setAttribute('fill', col);
@@ -2200,7 +2204,7 @@ var AIDiagram = (function (exports) {
2200
2204
  }
2201
2205
  const BASE = { roughness: 1.2, bowing: 0.7 };
2202
2206
  // ── Axes ───────────────────────────────────────────────────
2203
- function drawAxes$1(rc, g, c, px, py, pw, ph, allY, labelCol) {
2207
+ function drawAxes$1(rc, g, c, px, py, pw, ph, allY, labelCol, font = 'system-ui, sans-serif') {
2204
2208
  // Y axis
2205
2209
  g.appendChild(rc.line(px, py, px, py + ph, {
2206
2210
  roughness: 0.4, seed: hashStr$4(c.id + 'ya'), stroke: labelCol, strokeWidth: 1,
@@ -2219,7 +2223,7 @@ var AIDiagram = (function (exports) {
2219
2223
  g.appendChild(rc.line(px - 3, ty, px, ty, {
2220
2224
  roughness: 0.2, seed: hashStr$4(c.id + 'yt' + tick), stroke: labelCol, strokeWidth: 0.7,
2221
2225
  }));
2222
- g.appendChild(mkT(fmtNum$1(tick), px - 5, ty, 9, 400, labelCol, 'end'));
2226
+ g.appendChild(mkT(fmtNum$1(tick), px - 5, ty, 9, 400, labelCol, 'end', font));
2223
2227
  }
2224
2228
  }
2225
2229
  function fmtNum$1(v) {
@@ -2228,7 +2232,7 @@ var AIDiagram = (function (exports) {
2228
2232
  return String(v);
2229
2233
  }
2230
2234
  // ── Legend row ─────────────────────────────────────────────
2231
- function legend(g, labels, colors, x, y, labelCol) {
2235
+ function legend(g, labels, colors, x, y, labelCol, font = 'system-ui, sans-serif') {
2232
2236
  labels.forEach((lbl, i) => {
2233
2237
  const dot = se$1('rect');
2234
2238
  dot.setAttribute('x', String(x));
@@ -2238,7 +2242,7 @@ var AIDiagram = (function (exports) {
2238
2242
  dot.setAttribute('fill', colors[i % colors.length]);
2239
2243
  dot.setAttribute('rx', '1');
2240
2244
  g.appendChild(dot);
2241
- g.appendChild(mkT(lbl, x + 12, y + i * 14 + 4, 9, 400, labelCol, 'start'));
2245
+ g.appendChild(mkT(lbl, x + 12, y + i * 14 + 4, 9, 400, labelCol, 'start', font));
2242
2246
  });
2243
2247
  }
2244
2248
  // ── Public entry ───────────────────────────────────────────
@@ -2249,6 +2253,11 @@ var AIDiagram = (function (exports) {
2249
2253
  const bgFill = String(s.fill ?? palette.nodeFill);
2250
2254
  const bgStroke = String(s.stroke ?? (isDark ? '#5a4a30' : '#c8b898'));
2251
2255
  const lc = String(s.color ?? palette.titleText);
2256
+ const cFont = String(s.font ? `${s.font}, system-ui, sans-serif` : 'system-ui, sans-serif');
2257
+ const cFontSize = Number(s.fontSize ?? 12);
2258
+ const cFontWeight = s.fontWeight ?? 600;
2259
+ if (s.opacity != null)
2260
+ cg.setAttribute('opacity', String(s.opacity));
2252
2261
  // Background box
2253
2262
  cg.appendChild(rc.rectangle(c.x, c.y, c.w, c.h, {
2254
2263
  ...BASE, seed: hashStr$4(c.id),
@@ -2258,7 +2267,7 @@ var AIDiagram = (function (exports) {
2258
2267
  }));
2259
2268
  // Title
2260
2269
  if (c.title) {
2261
- cg.appendChild(mkT(c.title, c.x + c.w / 2, c.y + 14, 12, 600, lc));
2270
+ cg.appendChild(mkT(c.title, c.x + c.w / 2, c.y + 14, cFontSize, cFontWeight, lc, 'middle', cFont));
2262
2271
  }
2263
2272
  const { px, py, pw, ph, cx, cy } = chartLayout(c);
2264
2273
  // ── Pie / Donut ──────────────────────────────────────────
@@ -2284,7 +2293,7 @@ var AIDiagram = (function (exports) {
2284
2293
  angle += sweep;
2285
2294
  }
2286
2295
  // Mini legend on left
2287
- legend(cg, segments.map(s => `${s.label} ${Math.round(s.value / total * 100)}%`), segments.map(s => s.color), legendX, legendY, lc);
2296
+ legend(cg, segments.map(s => `${s.label} ${Math.round(s.value / total * 100)}%`), segments.map(s => s.color), legendX, legendY, lc, cFont);
2288
2297
  return cg;
2289
2298
  }
2290
2299
  // ── Scatter ───────────────────────────────────────────────
@@ -2305,7 +2314,7 @@ var AIDiagram = (function (exports) {
2305
2314
  strokeWidth: 1.2,
2306
2315
  }));
2307
2316
  });
2308
- legend(cg, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + (c.title ? 28 : 12), lc);
2317
+ legend(cg, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + (c.title ? 28 : 12), lc, cFont);
2309
2318
  return cg;
2310
2319
  }
2311
2320
  // ── Bar / Line / Area ─────────────────────────────────────
@@ -2314,10 +2323,10 @@ var AIDiagram = (function (exports) {
2314
2323
  const toY = makeValueToY(allY, py, ph);
2315
2324
  const baseline = toY(0);
2316
2325
  const n = labels.length;
2317
- drawAxes$1(rc, cg, c, px, py, pw, ph, allY, lc);
2326
+ drawAxes$1(rc, cg, c, px, py, pw, ph, allY, lc, cFont);
2318
2327
  // X labels
2319
2328
  labels.forEach((lbl, i) => {
2320
- cg.appendChild(mkT(lbl, px + (i + 0.5) * (pw / n), py + ph + 14, 9, 400, lc));
2329
+ cg.appendChild(mkT(lbl, px + (i + 0.5) * (pw / n), py + ph + 14, 9, 400, lc, 'middle', cFont));
2321
2330
  });
2322
2331
  if (c.chartType === 'bar') {
2323
2332
  const groupW = pw / n;
@@ -2388,7 +2397,7 @@ var AIDiagram = (function (exports) {
2388
2397
  }
2389
2398
  // Multi-series legend
2390
2399
  if (series.length > 1) {
2391
- legend(cg, series.map(s => s.name), series.map(s => s.color), px, py - 2, lc);
2400
+ legend(cg, series.map(s => s.name), series.map(s => s.color), px, py - 2, lc, cFont);
2392
2401
  }
2393
2402
  return cg;
2394
2403
  }
@@ -4854,6 +4863,14 @@ var AIDiagram = (function (exports) {
4854
4863
  return h;
4855
4864
  }
4856
4865
  const BASE_ROUGH = { roughness: 1.3, bowing: 0.7 };
4866
+ /** Darken a CSS hex colour by `amount` (0–1). Falls back to input for non-hex. */
4867
+ function darkenHex$1(hex, amount = 0.12) {
4868
+ const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
4869
+ if (!m)
4870
+ return hex;
4871
+ const d = (v) => Math.max(0, Math.round(parseInt(v, 16) * (1 - amount)));
4872
+ return `#${d(m[1]).toString(16).padStart(2, "0")}${d(m[2]).toString(16).padStart(2, "0")}${d(m[3]).toString(16).padStart(2, "0")}`;
4873
+ }
4857
4874
  // ── Small helper: load + resolve font from style or fall back ─────────────
4858
4875
  function resolveStyleFont$1(style, fallback) {
4859
4876
  const raw = String(style["font"] ?? "");
@@ -5024,6 +5041,7 @@ var AIDiagram = (function (exports) {
5024
5041
  fillStyle: "solid",
5025
5042
  stroke,
5026
5043
  strokeWidth: Number(s.strokeWidth ?? 1.9),
5044
+ ...(s.strokeDash ? { strokeLineDash: s.strokeDash } : {}),
5027
5045
  };
5028
5046
  const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
5029
5047
  const hw = n.w / 2 - 2;
@@ -5206,6 +5224,8 @@ var AIDiagram = (function (exports) {
5206
5224
  continue;
5207
5225
  const gs = g.style ?? {};
5208
5226
  const gg = mkGroup(`group-${g.id}`, "gg");
5227
+ if (gs.opacity != null)
5228
+ gg.setAttribute("opacity", String(gs.opacity));
5209
5229
  gg.appendChild(rc.rectangle(g.x, g.y, g.w, g.h, {
5210
5230
  ...BASE_ROUGH,
5211
5231
  roughness: 1.7,
@@ -5218,14 +5238,26 @@ var AIDiagram = (function (exports) {
5218
5238
  strokeLineDash: gs.strokeDash ?? palette.groupDash,
5219
5239
  }));
5220
5240
  // ── Group label typography ──────────────────────────
5221
- // supports: font, font-size, letter-spacing
5222
- // always left-anchored (single line)
5223
5241
  const gLabelColor = gs.color ? String(gs.color) : palette.groupLabel;
5224
5242
  const gFontSize = Number(gs.fontSize ?? 12);
5243
+ const gFontWeight = gs.fontWeight ?? 500;
5225
5244
  const gFont = resolveStyleFont$1(gs, diagramFont);
5226
5245
  const gLetterSpacing = gs.letterSpacing;
5246
+ const gPad = Number(gs.padding ?? 14);
5247
+ const gTextAlign = String(gs.textAlign ?? "left");
5248
+ const gAnchorMap = {
5249
+ left: "start",
5250
+ center: "middle",
5251
+ right: "end",
5252
+ };
5253
+ const gAnchor = gAnchorMap[gTextAlign] ?? "start";
5254
+ const gTextX = gTextAlign === "right"
5255
+ ? g.x + g.w - gPad
5256
+ : gTextAlign === "center"
5257
+ ? g.x + g.w / 2
5258
+ : g.x + gPad;
5227
5259
  if (g.label) {
5228
- gg.appendChild(mkText(g.label, g.x + 14, g.y + 14, gFontSize, 500, gLabelColor, "start", gFont, gLetterSpacing));
5260
+ gg.appendChild(mkText(g.label, gTextX, g.y + gPad, gFontSize, gFontWeight, gLabelColor, gAnchor, gFont, gLetterSpacing));
5229
5261
  }
5230
5262
  GL.appendChild(gg);
5231
5263
  }
@@ -5246,6 +5278,8 @@ var AIDiagram = (function (exports) {
5246
5278
  const [x1, y1] = getConnPoint$1(src, dstCX, dstCY);
5247
5279
  const [x2, y2] = getConnPoint$1(dst, srcCX, srcCY);
5248
5280
  const eg = mkGroup(`edge-${e.from}-${e.to}`, "eg");
5281
+ if (e.style?.opacity != null)
5282
+ eg.setAttribute("opacity", String(e.style.opacity));
5249
5283
  const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
5250
5284
  const nx = (x2 - x1) / len, ny = (y2 - y1) / len;
5251
5285
  const ecol = String(e.style?.stroke ?? palette.edgeStroke);
@@ -5286,7 +5320,9 @@ var AIDiagram = (function (exports) {
5286
5320
  const eFontSize = Number(e.style?.fontSize ?? 11);
5287
5321
  const eFont = resolveStyleFont$1(e.style ?? {}, diagramFont);
5288
5322
  const eLetterSpacing = e.style?.letterSpacing;
5289
- eg.appendChild(mkText(e.label, mx, my, eFontSize, 400, palette.edgeLabelText, "middle", eFont, eLetterSpacing));
5323
+ const eFontWeight = e.style?.fontWeight ?? 400;
5324
+ const eLabelColor = String(e.style?.color ?? palette.edgeLabelText);
5325
+ eg.appendChild(mkText(e.label, mx, my, eFontSize, eFontWeight, eLabelColor, "middle", eFont, eLetterSpacing));
5290
5326
  }
5291
5327
  EL.appendChild(eg);
5292
5328
  }
@@ -5295,6 +5331,8 @@ var AIDiagram = (function (exports) {
5295
5331
  const NL = mkGroup("node-layer");
5296
5332
  for (const n of sg.nodes) {
5297
5333
  const ng = mkGroup(`node-${n.id}`, "ng");
5334
+ if (n.style?.opacity != null)
5335
+ ng.setAttribute("opacity", String(n.style.opacity));
5298
5336
  renderShape$1(rc, n, palette).forEach((s) => ng.appendChild(s));
5299
5337
  // ── Node / text typography ─────────────────────────
5300
5338
  // supports: font, font-size, letter-spacing, text-align, line-height
@@ -5313,18 +5351,19 @@ var AIDiagram = (function (exports) {
5313
5351
  // line-height is a multiplier (e.g. 1.4 = 140% of font-size)
5314
5352
  const lineHeight = Number(n.style?.lineHeight ?? 1.3) * fontSize;
5315
5353
  const letterSpacing = n.style?.letterSpacing;
5354
+ const pad = Number(n.style?.padding ?? 8);
5316
5355
  // x shifts for left / right alignment
5317
5356
  const textX = textAlign === "left"
5318
- ? n.x + 8
5357
+ ? n.x + pad
5319
5358
  : textAlign === "right"
5320
- ? n.x + n.w - 8
5359
+ ? n.x + n.w - pad
5321
5360
  : n.x + n.w / 2;
5322
5361
  const lines = n.shape === 'text' && !n.label.includes('\n')
5323
- ? wrapText$1(n.label, n.w - 16, fontSize)
5362
+ ? wrapText$1(n.label, n.w - pad * 2, fontSize)
5324
5363
  : n.label.split('\n');
5325
5364
  const verticalAlign = String(n.style?.verticalAlign ?? "middle");
5326
- const nodeBodyTop = n.y + 6;
5327
- const nodeBodyBottom = n.y + n.h - 6;
5365
+ const nodeBodyTop = n.y + pad;
5366
+ const nodeBodyBottom = n.y + n.h - pad;
5328
5367
  const nodeBodyMid = n.y + n.h / 2;
5329
5368
  const blockH = (lines.length - 1) * lineHeight;
5330
5369
  const textCY = verticalAlign === "top"
@@ -5356,15 +5395,19 @@ var AIDiagram = (function (exports) {
5356
5395
  const fill = String(gs.fill ?? palette.tableFill);
5357
5396
  const strk = String(gs.stroke ?? palette.tableStroke);
5358
5397
  const textCol = String(gs.color ?? palette.tableText);
5359
- const hdrFill = palette.tableHeaderFill;
5398
+ const hdrFill = gs.fill ? darkenHex$1(fill, 0.08) : palette.tableHeaderFill;
5360
5399
  const hdrText = String(gs.color ?? palette.tableHeaderText);
5361
5400
  const divCol = palette.tableDivider;
5362
5401
  const pad = t.labelH;
5402
+ const tStrokeWidth = Number(gs.strokeWidth ?? 1.5);
5403
+ const tFontWeight = gs.fontWeight ?? 500;
5363
5404
  // ── Table-level font (applies to label + all cells) ─
5364
5405
  // supports: font, font-size, letter-spacing
5365
5406
  const tFontSize = Number(gs.fontSize ?? 12);
5366
5407
  const tFont = resolveStyleFont$1(gs, diagramFont);
5367
5408
  const tLetterSpacing = gs.letterSpacing;
5409
+ if (gs.opacity != null)
5410
+ tg.setAttribute("opacity", String(gs.opacity));
5368
5411
  // outer border
5369
5412
  tg.appendChild(rc.rectangle(t.x, t.y, t.w, t.h, {
5370
5413
  ...BASE_ROUGH,
@@ -5372,7 +5415,8 @@ var AIDiagram = (function (exports) {
5372
5415
  fill,
5373
5416
  fillStyle: "solid",
5374
5417
  stroke: strk,
5375
- strokeWidth: 1.5,
5418
+ strokeWidth: tStrokeWidth,
5419
+ ...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
5376
5420
  }));
5377
5421
  // label strip separator
5378
5422
  tg.appendChild(rc.line(t.x, t.y + pad, t.x + t.w, t.y + pad, {
@@ -5381,8 +5425,8 @@ var AIDiagram = (function (exports) {
5381
5425
  stroke: strk,
5382
5426
  strokeWidth: 1,
5383
5427
  }));
5384
- // ── Table label: font, font-size, letter-spacing (always left) ──
5385
- tg.appendChild(mkText(t.label, t.x + 10, t.y + pad / 2, tFontSize, 500, textCol, "start", tFont, tLetterSpacing));
5428
+ // ── Table label: font, font-size, font-weight, letter-spacing (always left) ──
5429
+ tg.appendChild(mkText(t.label, t.x + 10, t.y + pad / 2, tFontSize, tFontWeight, textCol, "start", tFont, tLetterSpacing));
5386
5430
  // rows
5387
5431
  let rowY = t.y + pad;
5388
5432
  for (const row of t.rows) {
@@ -5411,7 +5455,7 @@ var AIDiagram = (function (exports) {
5411
5455
  right: "end",
5412
5456
  };
5413
5457
  const cellAnchor = cellAnchorMap[cellAlignProp] ?? "middle";
5414
- const cellFw = row.kind === "header" ? 600 : 400;
5458
+ const cellFw = row.kind === "header" ? 600 : (gs.fontWeight ?? 400);
5415
5459
  const cellColor = row.kind === "header" ? hdrText : textCol;
5416
5460
  let cx = t.x;
5417
5461
  row.cells.forEach((cell, i) => {
@@ -5450,27 +5494,31 @@ var AIDiagram = (function (exports) {
5450
5494
  const gs = n.style ?? {};
5451
5495
  const fill = String(gs.fill ?? palette.noteFill);
5452
5496
  const strk = String(gs.stroke ?? palette.noteStroke);
5497
+ const nStrokeWidth = Number(gs.strokeWidth ?? 1.2);
5453
5498
  const fold = 14;
5454
5499
  const { x, y, w, h } = n;
5500
+ if (gs.opacity != null)
5501
+ ng.setAttribute("opacity", String(gs.opacity));
5455
5502
  // ── Note typography ─────────────────────────────────
5456
- // supports: font, font-size, letter-spacing, text-align, line-height
5457
5503
  const nFontSize = Number(gs.fontSize ?? 12);
5504
+ const nFontWeight = gs.fontWeight ?? 400;
5458
5505
  const nFont = resolveStyleFont$1(gs, diagramFont);
5459
5506
  const nLetterSpacing = gs.letterSpacing;
5460
5507
  const nLineHeight = Number(gs.lineHeight ?? 1.4) * nFontSize;
5461
5508
  const nTextAlign = String(gs.textAlign ?? "left");
5509
+ const nPad = Number(gs.padding ?? 12);
5462
5510
  const nAnchorMap = {
5463
5511
  left: "start",
5464
5512
  center: "middle",
5465
5513
  right: "end",
5466
5514
  };
5467
5515
  const nAnchor = nAnchorMap[nTextAlign] ?? "start";
5468
- // x position for the text block (pad from left, with alignment)
5469
5516
  const nTextX = nTextAlign === "right"
5470
- ? x + w - fold - 6
5517
+ ? x + w - fold - nPad
5471
5518
  : nTextAlign === "center"
5472
5519
  ? x + (w - fold) / 2
5473
- : x + 12;
5520
+ : x + nPad;
5521
+ const nFoldPad = fold + nPad; // text starts below fold + user padding
5474
5522
  ng.appendChild(rc.polygon([
5475
5523
  [x, y],
5476
5524
  [x + w - fold, y],
@@ -5483,7 +5531,8 @@ var AIDiagram = (function (exports) {
5483
5531
  fill,
5484
5532
  fillStyle: "solid",
5485
5533
  stroke: strk,
5486
- strokeWidth: 1.2,
5534
+ strokeWidth: nStrokeWidth,
5535
+ ...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
5487
5536
  }));
5488
5537
  ng.appendChild(rc.polygon([
5489
5538
  [x + w - fold, y],
@@ -5495,11 +5544,11 @@ var AIDiagram = (function (exports) {
5495
5544
  fill: palette.noteFold,
5496
5545
  fillStyle: "solid",
5497
5546
  stroke: strk,
5498
- strokeWidth: 0.8,
5547
+ strokeWidth: Math.min(nStrokeWidth, 0.8),
5499
5548
  }));
5500
5549
  const nVerticalAlign = String(gs.verticalAlign ?? "top");
5501
- const bodyTop = y + fold + 8; // below the fold triangle
5502
- const bodyBottom = y + h - 8; // above bottom edge
5550
+ const bodyTop = y + nFoldPad;
5551
+ const bodyBottom = y + h - nPad;
5503
5552
  const bodyMid = (bodyTop + bodyBottom) / 2;
5504
5553
  const blockH = (n.lines.length - 1) * nLineHeight;
5505
5554
  const blockCY = nVerticalAlign === "bottom"
@@ -5507,13 +5556,11 @@ var AIDiagram = (function (exports) {
5507
5556
  : nVerticalAlign === "middle"
5508
5557
  ? bodyMid
5509
5558
  : bodyTop + blockH / 2;
5510
- // multiline: use mkMultilineText so line-height is respected
5511
5559
  if (n.lines.length > 1) {
5512
- // vertical centre of the text block inside the note
5513
- ng.appendChild(mkMultilineText(n.lines, nTextX, blockCY, nFontSize, 400, String(gs.color ?? palette.noteText), nAnchor, nLineHeight, nFont, nLetterSpacing));
5560
+ ng.appendChild(mkMultilineText(n.lines, nTextX, blockCY, nFontSize, nFontWeight, String(gs.color ?? palette.noteText), nAnchor, nLineHeight, nFont, nLetterSpacing));
5514
5561
  }
5515
5562
  else {
5516
- ng.appendChild(mkText(n.lines[0] ?? "", nTextX, blockCY, nFontSize, 400, String(gs.color ?? palette.noteText), nAnchor, nFont, nLetterSpacing));
5563
+ ng.appendChild(mkText(n.lines[0] ?? "", nTextX, blockCY, nFontSize, nFontWeight, String(gs.color ?? palette.noteText), nAnchor, nFont, nLetterSpacing));
5517
5564
  }
5518
5565
  NoteL.appendChild(ng);
5519
5566
  }
@@ -5522,13 +5569,27 @@ var AIDiagram = (function (exports) {
5522
5569
  const MDL = mkGroup('markdown-layer');
5523
5570
  for (const m of sg.markdowns) {
5524
5571
  const mg = mkGroup(`markdown-${m.id}`, 'mdg');
5525
- const mFont = resolveStyleFont$1(m.style, diagramFont);
5526
- const baseColor = String(m.style?.color ?? palette.nodeText);
5527
- const textAlign = String(m.style?.textAlign ?? 'left');
5572
+ const gs = m.style ?? {};
5573
+ const mFont = resolveStyleFont$1(gs, diagramFont);
5574
+ const baseColor = String(gs.color ?? palette.nodeText);
5575
+ const textAlign = String(gs.textAlign ?? 'left');
5528
5576
  const anchor = textAlign === 'right' ? 'end'
5529
5577
  : textAlign === 'center' ? 'middle'
5530
5578
  : 'start';
5531
- const PAD = Number(m.style?.padding ?? 16);
5579
+ const PAD = Number(gs.padding ?? 16);
5580
+ const mLetterSpacing = gs.letterSpacing;
5581
+ if (gs.opacity != null)
5582
+ mg.setAttribute('opacity', String(gs.opacity));
5583
+ // Background + border
5584
+ if (gs.fill || gs.stroke) {
5585
+ mg.appendChild(rc.rectangle(m.x, m.y, m.w, m.h, {
5586
+ ...BASE_ROUGH, seed: hashStr$3(m.id),
5587
+ fill: String(gs.fill ?? 'none'), fillStyle: 'solid',
5588
+ stroke: String(gs.stroke ?? 'none'),
5589
+ strokeWidth: Number(gs.strokeWidth ?? 1.2),
5590
+ ...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
5591
+ }));
5592
+ }
5532
5593
  const textX = textAlign === 'right' ? m.x + m.w - PAD
5533
5594
  : textAlign === 'center' ? m.x + m.w / 2
5534
5595
  : m.x + PAD;
@@ -5551,6 +5612,8 @@ var AIDiagram = (function (exports) {
5551
5612
  t.setAttribute('fill', baseColor);
5552
5613
  t.setAttribute('pointer-events', 'none');
5553
5614
  t.setAttribute('user-select', 'none');
5615
+ if (mLetterSpacing != null)
5616
+ t.setAttribute('letter-spacing', String(mLetterSpacing));
5554
5617
  for (const run of line.runs) {
5555
5618
  const span = se('tspan');
5556
5619
  span.textContent = run.text;
@@ -5640,7 +5703,7 @@ var AIDiagram = (function (exports) {
5640
5703
  });
5641
5704
  }
5642
5705
  // ── Axes ───────────────────────────────────────────────────
5643
- function drawAxes(rc, ctx, c, px, py, pw, ph, allY, labelCol, R) {
5706
+ function drawAxes(rc, ctx, c, px, py, pw, ph, allY, labelCol, R, font = 'system-ui, sans-serif') {
5644
5707
  const toY = makeValueToY(allY, py, ph);
5645
5708
  const baseline = toY(0);
5646
5709
  // Y axis
@@ -5654,7 +5717,7 @@ var AIDiagram = (function (exports) {
5654
5717
  continue;
5655
5718
  rc.line(px - 3, ty, px, ty, { roughness: 0.2, seed: hashStr$2(c.id + 'yt' + tick), stroke: labelCol, strokeWidth: 0.7 });
5656
5719
  ctx.save();
5657
- ctx.font = '400 9px system-ui, sans-serif';
5720
+ ctx.font = `400 9px ${font}`;
5658
5721
  ctx.fillStyle = labelCol;
5659
5722
  ctx.textAlign = 'right';
5660
5723
  ctx.textBaseline = 'middle';
@@ -5663,9 +5726,9 @@ var AIDiagram = (function (exports) {
5663
5726
  }
5664
5727
  }
5665
5728
  // ── Legend ─────────────────────────────────────────────────
5666
- function drawLegend(ctx, labels, colors, x, y, labelCol) {
5729
+ function drawLegend(ctx, labels, colors, x, y, labelCol, font = 'system-ui, sans-serif') {
5667
5730
  ctx.save();
5668
- ctx.font = '400 9px system-ui, sans-serif';
5731
+ ctx.font = `400 9px ${font}`;
5669
5732
  ctx.textAlign = 'left';
5670
5733
  ctx.textBaseline = 'middle';
5671
5734
  labels.forEach((lbl, i) => {
@@ -5683,6 +5746,11 @@ var AIDiagram = (function (exports) {
5683
5746
  const bgFill = String(s.fill ?? pal.nodeFill);
5684
5747
  const bgStroke = String(s.stroke ?? (pal.nodeStroke === 'none' ? '#c8b898' : pal.nodeStroke));
5685
5748
  const lc = String(s.color ?? pal.labelText);
5749
+ const cFont = String(s.font ? `${s.font}, system-ui, sans-serif` : 'system-ui, sans-serif');
5750
+ const cFontSize = Number(s.fontSize ?? 12);
5751
+ const cFontWeight = s.fontWeight ?? 600;
5752
+ if (s.opacity != null)
5753
+ ctx.globalAlpha = Number(s.opacity);
5686
5754
  // Background
5687
5755
  rc.rectangle(c.x, c.y, c.w, c.h, {
5688
5756
  ...R, seed: hashStr$2(c.id),
@@ -5695,7 +5763,7 @@ var AIDiagram = (function (exports) {
5695
5763
  // Title
5696
5764
  if (c.title) {
5697
5765
  ctx.save();
5698
- ctx.font = '600 12px system-ui, sans-serif';
5766
+ ctx.font = `${cFontWeight} ${cFontSize}px ${cFont}`;
5699
5767
  ctx.fillStyle = lc;
5700
5768
  ctx.textAlign = 'center';
5701
5769
  ctx.textBaseline = 'middle';
@@ -5716,7 +5784,8 @@ var AIDiagram = (function (exports) {
5716
5784
  drawPieArc(rc, ctx, cx, cy, r, ir, angle, angle + sweep, seg.color, hashStr$2(c.id + seg.label + i));
5717
5785
  angle += sweep;
5718
5786
  });
5719
- drawLegend(ctx, segments.map(s => `${s.label} ${Math.round(s.value / total * 100)}%`), segments.map(s => s.color), legendX, legendY, lc);
5787
+ drawLegend(ctx, segments.map(s => `${s.label} ${Math.round(s.value / total * 100)}%`), segments.map(s => s.color), legendX, legendY, lc, cFont);
5788
+ ctx.globalAlpha = 1;
5720
5789
  return;
5721
5790
  }
5722
5791
  // ── Scatter ───────────────────────────────────────────────
@@ -5736,7 +5805,8 @@ var AIDiagram = (function (exports) {
5736
5805
  strokeWidth: 1.2,
5737
5806
  });
5738
5807
  });
5739
- drawLegend(ctx, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + (c.title ? 28 : 12), lc);
5808
+ drawLegend(ctx, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + (c.title ? 28 : 12), lc, cFont);
5809
+ ctx.globalAlpha = 1;
5740
5810
  return;
5741
5811
  }
5742
5812
  // ── Bar / Line / Area ─────────────────────────────────────
@@ -5745,10 +5815,10 @@ var AIDiagram = (function (exports) {
5745
5815
  const toY = makeValueToY(allY, py, ph);
5746
5816
  const baseline = toY(0);
5747
5817
  const n = labels.length;
5748
- drawAxes(rc, ctx, c, px, py, pw, ph, allY, lc, R);
5818
+ drawAxes(rc, ctx, c, px, py, pw, ph, allY, lc, R, cFont);
5749
5819
  // X labels
5750
5820
  ctx.save();
5751
- ctx.font = '400 9px system-ui, sans-serif';
5821
+ ctx.font = `400 9px ${cFont}`;
5752
5822
  ctx.fillStyle = lc;
5753
5823
  ctx.textAlign = 'center';
5754
5824
  ctx.textBaseline = 'top';
@@ -5825,8 +5895,9 @@ var AIDiagram = (function (exports) {
5825
5895
  }
5826
5896
  // Multi-series legend
5827
5897
  if (series.length > 1) {
5828
- drawLegend(ctx, series.map(s => s.name), series.map(s => s.color), px, py - 2, lc);
5898
+ drawLegend(ctx, series.map(s => s.name), series.map(s => s.color), px, py - 2, lc, cFont);
5829
5899
  }
5900
+ ctx.globalAlpha = 1;
5830
5901
  }
5831
5902
 
5832
5903
  // ============================================================
@@ -5839,6 +5910,14 @@ var AIDiagram = (function (exports) {
5839
5910
  h = ((h * 33) ^ s.charCodeAt(i)) & 0xffff;
5840
5911
  return h;
5841
5912
  }
5913
+ /** Darken a CSS hex colour by `amount` (0–1). Falls back to input for non-hex. */
5914
+ function darkenHex(hex, amount = 0.12) {
5915
+ const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
5916
+ if (!m)
5917
+ return hex;
5918
+ const d = (v) => Math.max(0, Math.round(parseInt(v, 16) * (1 - amount)));
5919
+ return `#${d(m[1]).toString(16).padStart(2, "0")}${d(m[2]).toString(16).padStart(2, "0")}${d(m[3]).toString(16).padStart(2, "0")}`;
5920
+ }
5842
5921
  // ── Small helper: load + resolve font from a style map ────────────────────
5843
5922
  function resolveStyleFont(style, fallback) {
5844
5923
  const raw = String(style['font'] ?? '');
@@ -5956,6 +6035,7 @@ var AIDiagram = (function (exports) {
5956
6035
  ...R, seed: hashStr$1(n.id),
5957
6036
  fill, fillStyle: 'solid',
5958
6037
  stroke, strokeWidth: Number(s.strokeWidth ?? 1.9),
6038
+ ...(s.strokeDash ? { strokeLineDash: s.strokeDash } : {}),
5959
6039
  };
5960
6040
  const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
5961
6041
  const hw = n.w / 2 - 2;
@@ -6091,6 +6171,8 @@ var AIDiagram = (function (exports) {
6091
6171
  if (!g.w)
6092
6172
  continue;
6093
6173
  const gs = g.style ?? {};
6174
+ if (gs.opacity != null)
6175
+ ctx.globalAlpha = Number(gs.opacity);
6094
6176
  rc.rectangle(g.x, g.y, g.w, g.h, {
6095
6177
  ...R, roughness: 1.7, bowing: 0.4, seed: hashStr$1(g.id),
6096
6178
  fill: String(gs.fill ?? palette.groupFill),
@@ -6099,16 +6181,21 @@ var AIDiagram = (function (exports) {
6099
6181
  strokeWidth: Number(gs.strokeWidth ?? 1.2),
6100
6182
  strokeLineDash: gs.strokeDash ?? palette.groupDash,
6101
6183
  });
6102
- // ── Group label ──────────────────────────────────────
6103
- // Only render when label has content — empty label = no reserved space
6104
- // supports: font, font-size, letter-spacing (always left-anchored)
6105
6184
  if (g.label) {
6106
6185
  const gFontSize = Number(gs.fontSize ?? 12);
6186
+ const gFontWeight = gs.fontWeight ?? 500;
6107
6187
  const gFont = resolveStyleFont(gs, diagramFont);
6108
6188
  const gLetterSpacing = gs.letterSpacing;
6109
6189
  const gLabelColor = gs.color ? String(gs.color) : palette.groupLabel;
6110
- drawText(ctx, g.label, g.x + 14, g.y + 16, gFontSize, 500, gLabelColor, 'left', gFont, gLetterSpacing);
6190
+ const gPad = Number(gs.padding ?? 14);
6191
+ const gTextAlign = String(gs.textAlign ?? 'left');
6192
+ const gTextX = gTextAlign === 'right' ? g.x + g.w - gPad
6193
+ : gTextAlign === 'center' ? g.x + g.w / 2
6194
+ : g.x + gPad;
6195
+ drawText(ctx, g.label, gTextX, g.y + gPad + 2, gFontSize, gFontWeight, gLabelColor, gTextAlign, gFont, gLetterSpacing);
6111
6196
  }
6197
+ if (gs.opacity != null)
6198
+ ctx.globalAlpha = 1;
6112
6199
  }
6113
6200
  // ── Edges ─────────────────────────────────────────────────
6114
6201
  for (const e of sg.edges) {
@@ -6120,6 +6207,8 @@ var AIDiagram = (function (exports) {
6120
6207
  const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
6121
6208
  const [x1, y1] = getConnPoint(src, dstCX, dstCY);
6122
6209
  const [x2, y2] = getConnPoint(dst, srcCX, srcCY);
6210
+ if (e.style?.opacity != null)
6211
+ ctx.globalAlpha = Number(e.style.opacity);
6123
6212
  const ecol = String(e.style?.stroke ?? palette.edgeStroke);
6124
6213
  const { arrowAt, dashed } = connMeta(e.connector);
6125
6214
  const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
@@ -6148,17 +6237,22 @@ var AIDiagram = (function (exports) {
6148
6237
  const eFontSize = Number(e.style?.fontSize ?? 11);
6149
6238
  const eFont = resolveStyleFont(e.style ?? {}, diagramFont);
6150
6239
  const eLetterSpacing = e.style?.letterSpacing;
6240
+ const eFontWeight = e.style?.fontWeight ?? 400;
6241
+ const eLabelColor = String(e.style?.color ?? palette.edgeLabelText);
6151
6242
  ctx.save();
6152
- ctx.font = `400 ${eFontSize}px ${eFont}`;
6243
+ ctx.font = `${eFontWeight} ${eFontSize}px ${eFont}`;
6153
6244
  const tw = ctx.measureText(e.label).width + 12;
6154
6245
  ctx.restore();
6155
6246
  ctx.fillStyle = palette.edgeLabelBg;
6156
6247
  ctx.fillRect(mx - tw / 2, my - 8, tw, 15);
6157
- drawText(ctx, e.label, mx, my + 3, eFontSize, 400, palette.edgeLabelText, 'center', eFont, eLetterSpacing);
6248
+ drawText(ctx, e.label, mx, my + 3, eFontSize, eFontWeight, eLabelColor, 'center', eFont, eLetterSpacing);
6158
6249
  }
6250
+ ctx.globalAlpha = 1;
6159
6251
  }
6160
6252
  // ── Nodes ─────────────────────────────────────────────────
6161
6253
  for (const n of sg.nodes) {
6254
+ if (n.style?.opacity != null)
6255
+ ctx.globalAlpha = Number(n.style.opacity);
6162
6256
  renderShape(rc, ctx, n, palette, R);
6163
6257
  // ── Node / text typography ─────────────────────────
6164
6258
  // supports: font, font-size, letter-spacing, text-align,
@@ -6172,18 +6266,19 @@ var AIDiagram = (function (exports) {
6172
6266
  const lineHeight = Number(n.style?.lineHeight ?? 1.3) * fontSize;
6173
6267
  const letterSpacing = n.style?.letterSpacing;
6174
6268
  const vertAlign = String(n.style?.verticalAlign ?? 'middle');
6269
+ const pad = Number(n.style?.padding ?? 8);
6175
6270
  // x shifts for left/right alignment
6176
- const textX = textAlign === 'left' ? n.x + 8
6177
- : textAlign === 'right' ? n.x + n.w - 8
6271
+ const textX = textAlign === 'left' ? n.x + pad
6272
+ : textAlign === 'right' ? n.x + n.w - pad
6178
6273
  : n.x + n.w / 2;
6179
6274
  // word-wrap for text shape; explicit \n for all others
6180
6275
  const rawLines = n.label.split('\n');
6181
6276
  const lines = n.shape === 'text' && rawLines.length === 1
6182
- ? wrapText(n.label, n.w - 16, fontSize)
6277
+ ? wrapText(n.label, n.w - pad * 2, fontSize)
6183
6278
  : rawLines;
6184
6279
  // vertical-align: compute textCY from top/middle/bottom
6185
- const nodeBodyTop = n.y + 6;
6186
- const nodeBodyBottom = n.y + n.h - 6;
6280
+ const nodeBodyTop = n.y + pad;
6281
+ const nodeBodyBottom = n.y + n.h - pad;
6187
6282
  const blockH = (lines.length - 1) * lineHeight;
6188
6283
  const textCY = vertAlign === 'top' ? nodeBodyTop + blockH / 2
6189
6284
  : vertAlign === 'bottom' ? nodeBodyBottom - blockH / 2
@@ -6194,6 +6289,8 @@ var AIDiagram = (function (exports) {
6194
6289
  else {
6195
6290
  drawText(ctx, lines[0] ?? '', textX, textCY, fontSize, fontWeight, textColor, textAlign, nodeFont, letterSpacing);
6196
6291
  }
6292
+ if (n.style?.opacity != null)
6293
+ ctx.globalAlpha = 1;
6197
6294
  }
6198
6295
  // ── Tables ────────────────────────────────────────────────
6199
6296
  for (const t of sg.tables) {
@@ -6203,25 +6300,28 @@ var AIDiagram = (function (exports) {
6203
6300
  const textCol = String(gs.color ?? palette.tableText);
6204
6301
  const pad = t.labelH;
6205
6302
  // ── Table-level font ────────────────────────────────
6206
- // supports: font, font-size, letter-spacing
6207
- // cells also support text-align
6208
6303
  const tFontSize = Number(gs.fontSize ?? 12);
6209
6304
  const tFont = resolveStyleFont(gs, diagramFont);
6210
6305
  const tLetterSpacing = gs.letterSpacing;
6306
+ const tStrokeWidth = Number(gs.strokeWidth ?? 1.5);
6307
+ const tFontWeight = gs.fontWeight ?? 500;
6308
+ if (gs.opacity != null)
6309
+ ctx.globalAlpha = Number(gs.opacity);
6211
6310
  rc.rectangle(t.x, t.y, t.w, t.h, {
6212
6311
  ...R, seed: hashStr$1(t.id),
6213
- fill, fillStyle: 'solid', stroke: strk, strokeWidth: 1.5,
6312
+ fill, fillStyle: 'solid', stroke: strk, strokeWidth: tStrokeWidth,
6313
+ ...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
6214
6314
  });
6215
6315
  rc.line(t.x, t.y + pad, t.x + t.w, t.y + pad, {
6216
6316
  roughness: 0.6, seed: hashStr$1(t.id + 'l'), stroke: strk, strokeWidth: 1,
6217
6317
  });
6218
6318
  // ── Table label: always left-anchored ───────────────
6219
- drawText(ctx, t.label, t.x + 10, t.y + pad / 2, tFontSize, 500, textCol, 'left', tFont, tLetterSpacing);
6319
+ drawText(ctx, t.label, t.x + 10, t.y + pad / 2, tFontSize, tFontWeight, textCol, 'left', tFont, tLetterSpacing);
6220
6320
  let rowY = t.y + pad;
6221
6321
  for (const row of t.rows) {
6222
6322
  const rh = row.kind === 'header' ? t.headerH : t.rowH;
6223
6323
  if (row.kind === 'header') {
6224
- ctx.fillStyle = palette.tableHeaderFill;
6324
+ ctx.fillStyle = gs.fill ? darkenHex(fill, 0.08) : palette.tableHeaderFill;
6225
6325
  ctx.fillRect(t.x + 1, rowY + 1, t.w - 2, rh - 1);
6226
6326
  }
6227
6327
  rc.line(t.x, rowY + rh, t.x + t.w, rowY + rh, {
@@ -6234,7 +6334,7 @@ var AIDiagram = (function (exports) {
6234
6334
  const cellAlignProp = (row.kind === 'header'
6235
6335
  ? 'center'
6236
6336
  : String(gs.textAlign ?? 'center'));
6237
- const cellFw = row.kind === 'header' ? 600 : 400;
6337
+ const cellFw = row.kind === 'header' ? 600 : (gs.fontWeight ?? 400);
6238
6338
  const cellColor = row.kind === 'header'
6239
6339
  ? String(gs.color ?? palette.tableHeaderText)
6240
6340
  : textCol;
@@ -6255,63 +6355,87 @@ var AIDiagram = (function (exports) {
6255
6355
  });
6256
6356
  rowY += rh;
6257
6357
  }
6358
+ ctx.globalAlpha = 1;
6258
6359
  }
6259
6360
  // ── Notes ─────────────────────────────────────────────────
6260
6361
  for (const n of sg.notes) {
6261
6362
  const gs = n.style ?? {};
6262
6363
  const fill = String(gs.fill ?? palette.noteFill);
6263
6364
  const strk = String(gs.stroke ?? palette.noteStroke);
6365
+ const nStrokeWidth = Number(gs.strokeWidth ?? 1.2);
6264
6366
  const fold = 14;
6265
6367
  const { x, y, w, h } = n;
6368
+ if (gs.opacity != null)
6369
+ ctx.globalAlpha = Number(gs.opacity);
6266
6370
  rc.polygon([
6267
6371
  [x, y],
6268
6372
  [x + w - fold, y],
6269
6373
  [x + w, y + fold],
6270
6374
  [x + w, y + h],
6271
6375
  [x, y + h],
6272
- ], { ...R, seed: hashStr$1(n.id), fill, fillStyle: 'solid', stroke: strk, strokeWidth: 1.2 });
6376
+ ], { ...R, seed: hashStr$1(n.id), fill, fillStyle: 'solid', stroke: strk,
6377
+ strokeWidth: nStrokeWidth,
6378
+ ...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
6379
+ });
6273
6380
  rc.polygon([
6274
6381
  [x + w - fold, y],
6275
6382
  [x + w, y + fold],
6276
6383
  [x + w - fold, y + fold],
6277
6384
  ], { roughness: 0.4, seed: hashStr$1(n.id + 'f'),
6278
- fill: palette.noteFold, fillStyle: 'solid', stroke: strk, strokeWidth: 0.8 });
6279
- // ── Note typography ─────────────────────────────────
6280
- // supports: font, font-size, letter-spacing, text-align,
6281
- // vertical-align, line-height
6385
+ fill: palette.noteFold, fillStyle: 'solid', stroke: strk,
6386
+ strokeWidth: Math.min(nStrokeWidth, 0.8),
6387
+ });
6282
6388
  const nFontSize = Number(gs.fontSize ?? 12);
6389
+ const nFontWeight = gs.fontWeight ?? 400;
6283
6390
  const nFont = resolveStyleFont(gs, diagramFont);
6284
6391
  const nLetterSpacing = gs.letterSpacing;
6285
6392
  const nLineHeight = Number(gs.lineHeight ?? 1.4) * nFontSize;
6286
6393
  const nTextAlign = String(gs.textAlign ?? 'left');
6287
6394
  const nVertAlign = String(gs.verticalAlign ?? 'top');
6288
6395
  const nColor = String(gs.color ?? palette.noteText);
6289
- const nTextX = nTextAlign === 'right' ? x + w - fold - 6
6396
+ const nPad = Number(gs.padding ?? 12);
6397
+ const nTextX = nTextAlign === 'right' ? x + w - fold - nPad
6290
6398
  : nTextAlign === 'center' ? x + (w - fold) / 2
6291
- : x + 12;
6292
- // vertical-align inside note body (below fold)
6293
- const bodyTop = y + fold + 8;
6294
- const bodyBottom = y + h - 8;
6399
+ : x + nPad;
6400
+ const nFoldPad = fold + nPad;
6401
+ const bodyTop = y + nFoldPad;
6402
+ const bodyBottom = y + h - nPad;
6295
6403
  const blockH = (n.lines.length - 1) * nLineHeight;
6296
6404
  const blockCY = nVertAlign === 'bottom' ? bodyBottom - blockH / 2
6297
6405
  : nVertAlign === 'middle' ? (bodyTop + bodyBottom) / 2
6298
- : bodyTop + blockH / 2; // top (default)
6406
+ : bodyTop + blockH / 2;
6299
6407
  if (n.lines.length > 1) {
6300
- drawMultilineText(ctx, n.lines, nTextX, blockCY, nFontSize, 400, nColor, nTextAlign, nLineHeight, nFont, nLetterSpacing);
6408
+ drawMultilineText(ctx, n.lines, nTextX, blockCY, nFontSize, nFontWeight, nColor, nTextAlign, nLineHeight, nFont, nLetterSpacing);
6301
6409
  }
6302
6410
  else {
6303
- drawText(ctx, n.lines[0] ?? '', nTextX, blockCY, nFontSize, 400, nColor, nTextAlign, nFont, nLetterSpacing);
6411
+ drawText(ctx, n.lines[0] ?? '', nTextX, blockCY, nFontSize, nFontWeight, nColor, nTextAlign, nFont, nLetterSpacing);
6304
6412
  }
6413
+ if (gs.opacity != null)
6414
+ ctx.globalAlpha = 1;
6305
6415
  }
6306
6416
  // ── Markdown blocks ────────────────────────────────────────
6307
6417
  // Renders prose with Markdown headings and bold/italic inline spans.
6308
6418
  // Canvas has no native bold-within-a-run, so each run is drawn
6309
6419
  // individually with its own ctx.font setting.
6310
6420
  for (const m of (sg.markdowns ?? [])) {
6311
- const mFont = resolveStyleFont(m.style, diagramFont);
6312
- const baseColor = String(m.style?.color ?? palette.nodeText);
6313
- const textAlign = String(m.style?.textAlign ?? 'left');
6314
- const PAD = Number(m.style?.padding ?? 16);
6421
+ const gs = m.style ?? {};
6422
+ const mFont = resolveStyleFont(gs, diagramFont);
6423
+ const baseColor = String(gs.color ?? palette.nodeText);
6424
+ const textAlign = String(gs.textAlign ?? 'left');
6425
+ const PAD = Number(gs.padding ?? 16);
6426
+ const mLetterSpacing = gs.letterSpacing;
6427
+ if (gs.opacity != null)
6428
+ ctx.globalAlpha = Number(gs.opacity);
6429
+ // Background + border
6430
+ if (gs.fill || gs.stroke) {
6431
+ rc.rectangle(m.x, m.y, m.w, m.h, {
6432
+ ...R, seed: hashStr$1(m.id),
6433
+ fill: String(gs.fill ?? 'none'), fillStyle: 'solid',
6434
+ stroke: String(gs.stroke ?? 'none'),
6435
+ strokeWidth: Number(gs.strokeWidth ?? 1.2),
6436
+ ...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
6437
+ });
6438
+ }
6315
6439
  const anchorX = textAlign === 'right' ? m.x + m.w - PAD
6316
6440
  : textAlign === 'center' ? m.x + m.w / 2
6317
6441
  : m.x + PAD;
@@ -6329,14 +6453,29 @@ var AIDiagram = (function (exports) {
6329
6453
  ctx.save();
6330
6454
  ctx.textBaseline = 'middle';
6331
6455
  ctx.fillStyle = baseColor;
6456
+ const ls = mLetterSpacing ?? 0;
6457
+ // measure run width including letter-spacing
6458
+ const runW = (run) => {
6459
+ return ctx.measureText(run.text).width + ls * run.text.length;
6460
+ };
6461
+ const drawRun = (run, rx) => {
6462
+ if (ls) {
6463
+ for (const ch of run.text) {
6464
+ ctx.fillText(ch, rx, lineY);
6465
+ rx += ctx.measureText(ch).width + ls;
6466
+ }
6467
+ }
6468
+ else {
6469
+ ctx.fillText(run.text, rx, lineY);
6470
+ }
6471
+ };
6332
6472
  if (textAlign === 'center' || textAlign === 'right') {
6333
- // Measure full line width first
6334
6473
  let totalW = 0;
6335
6474
  for (const run of line.runs) {
6336
6475
  const runStyle = run.italic ? 'italic ' : '';
6337
6476
  const runWeight = run.bold ? 700 : fontWeight;
6338
6477
  ctx.font = `${runStyle}${runWeight} ${fontSize}px ${mFont}`;
6339
- totalW += ctx.measureText(run.text).width;
6478
+ totalW += runW(run);
6340
6479
  }
6341
6480
  let runX = textAlign === 'center' ? anchorX - totalW / 2 : anchorX - totalW;
6342
6481
  ctx.textAlign = 'left';
@@ -6344,25 +6483,25 @@ var AIDiagram = (function (exports) {
6344
6483
  const runStyle = run.italic ? 'italic ' : '';
6345
6484
  const runWeight = run.bold ? 700 : fontWeight;
6346
6485
  ctx.font = `${runStyle}${runWeight} ${fontSize}px ${mFont}`;
6347
- ctx.fillText(run.text, runX, lineY);
6348
- runX += ctx.measureText(run.text).width;
6486
+ drawRun(run, runX);
6487
+ runX += runW(run);
6349
6488
  }
6350
6489
  }
6351
6490
  else {
6352
- // left-aligned — draw runs left to right from anchorX
6353
6491
  let runX = anchorX;
6354
6492
  ctx.textAlign = 'left';
6355
6493
  for (const run of line.runs) {
6356
6494
  const runStyle = run.italic ? 'italic ' : '';
6357
6495
  const runWeight = run.bold ? 700 : fontWeight;
6358
6496
  ctx.font = `${runStyle}${runWeight} ${fontSize}px ${mFont}`;
6359
- ctx.fillText(run.text, runX, lineY);
6360
- runX += ctx.measureText(run.text).width;
6497
+ drawRun(run, runX);
6498
+ runX += runW(run);
6361
6499
  }
6362
6500
  }
6363
6501
  ctx.restore();
6364
6502
  y += LINE_SPACING[line.kind];
6365
6503
  }
6504
+ ctx.globalAlpha = 1;
6366
6505
  }
6367
6506
  // ── Charts ────────────────────────────────────────────────
6368
6507
  for (const c of sg.charts) {
@@ -6400,6 +6539,7 @@ var AIDiagram = (function (exports) {
6400
6539
  const getTableEl = (svg, id) => getEl(svg, `table-${id}`);
6401
6540
  const getNoteEl = (svg, id) => getEl(svg, `note-${id}`);
6402
6541
  const getChartEl = (svg, id) => getEl(svg, `chart-${id}`);
6542
+ const getMarkdownEl = (svg, id) => getEl(svg, `markdown-${id}`);
6403
6543
  function resolveEl(svg, target) {
6404
6544
  // check edge first — target contains connector like "a-->b"
6405
6545
  const edge = parseEdgeTarget(target);
@@ -6411,6 +6551,7 @@ var AIDiagram = (function (exports) {
6411
6551
  getTableEl(svg, target) ??
6412
6552
  getNoteEl(svg, target) ??
6413
6553
  getChartEl(svg, target) ??
6554
+ getMarkdownEl(svg, target) ??
6414
6555
  null);
6415
6556
  }
6416
6557
  function pathLength(p) {
@@ -6421,6 +6562,15 @@ var AIDiagram = (function (exports) {
6421
6562
  return 200;
6422
6563
  }
6423
6564
  }
6565
+ function clearDashOverridesAfter(el, delayMs) {
6566
+ setTimeout(() => {
6567
+ el.querySelectorAll('path').forEach(p => {
6568
+ p.style.strokeDasharray = '';
6569
+ p.style.strokeDashoffset = '';
6570
+ p.style.transition = '';
6571
+ });
6572
+ }, delayMs);
6573
+ }
6424
6574
  // ── Arrow connector parser ────────────────────────────────
6425
6575
  const ARROW_CONNECTORS = ["<-->", "<->", "-->", "<--", "->", "<-", "---", "--"];
6426
6576
  function parseEdgeTarget(target) {
@@ -6528,32 +6678,39 @@ var AIDiagram = (function (exports) {
6528
6678
  });
6529
6679
  }
6530
6680
  function animateEdgeDraw(el, conn) {
6531
- const paths = Array.from(el.querySelectorAll("path"));
6681
+ const paths = Array.from(el.querySelectorAll('path'));
6532
6682
  if (!paths.length)
6533
6683
  return;
6534
6684
  const linePath = paths[0];
6535
6685
  const headPaths = paths.slice(1);
6536
6686
  const STROKE_DUR = 360;
6537
6687
  const len = pathLength(linePath);
6538
- const reversed = conn.startsWith("<") && !conn.includes(">");
6688
+ const reversed = conn.startsWith('<') && !conn.includes('>');
6539
6689
  linePath.style.strokeDasharray = `${len}`;
6540
6690
  linePath.style.strokeDashoffset = reversed ? `${-len}` : `${len}`;
6541
- linePath.style.transition = "none";
6542
- headPaths.forEach((p) => {
6543
- p.style.opacity = "0";
6544
- p.style.transition = "none";
6691
+ linePath.style.transition = 'none';
6692
+ headPaths.forEach(p => {
6693
+ p.style.opacity = '0';
6694
+ p.style.transition = 'none';
6545
6695
  });
6546
- el.classList.remove("draw-hidden");
6547
- el.classList.add("draw-reveal");
6548
- el.style.opacity = "1";
6696
+ el.classList.remove('draw-hidden');
6697
+ el.classList.add('draw-reveal');
6698
+ el.style.opacity = '1';
6549
6699
  requestAnimationFrame(() => requestAnimationFrame(() => {
6550
6700
  linePath.style.transition = `stroke-dashoffset ${STROKE_DUR}ms cubic-bezier(.4,0,.2,1)`;
6551
- linePath.style.strokeDashoffset = "0";
6701
+ linePath.style.strokeDashoffset = '0';
6552
6702
  setTimeout(() => {
6553
- headPaths.forEach((p) => {
6554
- p.style.transition = "opacity 120ms ease";
6555
- p.style.opacity = "1";
6703
+ headPaths.forEach(p => {
6704
+ p.style.transition = 'opacity 120ms ease';
6705
+ p.style.opacity = '1';
6556
6706
  });
6707
+ // ── ADD: clear inline dash overrides so SVG attribute
6708
+ // (stroke-dasharray="6,5" for dashed arrows) takes over again
6709
+ setTimeout(() => {
6710
+ linePath.style.strokeDasharray = '';
6711
+ linePath.style.strokeDashoffset = '';
6712
+ linePath.style.transition = '';
6713
+ }, 160);
6557
6714
  }, STROKE_DUR - 40);
6558
6715
  }));
6559
6716
  }
@@ -6577,6 +6734,7 @@ var AIDiagram = (function (exports) {
6577
6734
  this.drawTargetTables = new Set();
6578
6735
  this.drawTargetNotes = new Set();
6579
6736
  this.drawTargetCharts = new Set();
6737
+ this.drawTargetMarkdowns = new Set();
6580
6738
  for (const s of steps) {
6581
6739
  if (s.action !== "draw" || parseEdgeTarget(s.target))
6582
6740
  continue;
@@ -6597,6 +6755,10 @@ var AIDiagram = (function (exports) {
6597
6755
  this.drawTargetCharts.add(`chart-${s.target}`);
6598
6756
  this.drawTargetNodes.delete(`node-${s.target}`);
6599
6757
  }
6758
+ if (svg.querySelector(`#markdown-${s.target}`)) {
6759
+ this.drawTargetMarkdowns.add(`markdown-${s.target}`);
6760
+ this.drawTargetNodes.delete(`node-${s.target}`);
6761
+ }
6600
6762
  }
6601
6763
  this._clearAll();
6602
6764
  }
@@ -6768,7 +6930,24 @@ var AIDiagram = (function (exports) {
6768
6930
  });
6769
6931
  }
6770
6932
  });
6771
- this.svg.querySelectorAll(".tg, .ntg, .cg").forEach((el) => {
6933
+ // Markdown
6934
+ this.svg.querySelectorAll(".mdg").forEach((el) => {
6935
+ clearDrawStyles(el);
6936
+ el.style.transition = "none";
6937
+ el.style.opacity = "";
6938
+ if (this.drawTargetMarkdowns.has(el.id)) {
6939
+ el.classList.add("gg-hidden");
6940
+ }
6941
+ else {
6942
+ el.classList.remove("gg-hidden");
6943
+ requestAnimationFrame(() => {
6944
+ el.style.transition = "";
6945
+ });
6946
+ }
6947
+ });
6948
+ this.svg
6949
+ .querySelectorAll(".tg, .ntg, .cg, .mdg")
6950
+ .forEach((el) => {
6772
6951
  el.style.transform = "";
6773
6952
  el.style.transition = "";
6774
6953
  el.style.opacity = "";
@@ -6946,6 +7125,9 @@ var AIDiagram = (function (exports) {
6946
7125
  if (!firstPath?.style.strokeDasharray)
6947
7126
  prepareForDraw(groupEl);
6948
7127
  animateShapeDraw(groupEl, 550, 40);
7128
+ const pathCount = groupEl.querySelectorAll('path').length;
7129
+ const totalMs = pathCount * 40 + 550 + 120; // stagger + duration + buffer
7130
+ clearDashOverridesAfter(groupEl, totalMs);
6949
7131
  }
6950
7132
  return;
6951
7133
  }
@@ -6966,6 +7148,8 @@ var AIDiagram = (function (exports) {
6966
7148
  tableEl.classList.remove("gg-hidden");
6967
7149
  prepareForDraw(tableEl);
6968
7150
  animateShapeDraw(tableEl, 500, 40);
7151
+ const tablePathCount = tableEl.querySelectorAll('path').length;
7152
+ clearDashOverridesAfter(tableEl, tablePathCount * 40 + 500 + 120);
6969
7153
  }
6970
7154
  return;
6971
7155
  }
@@ -6986,6 +7170,8 @@ var AIDiagram = (function (exports) {
6986
7170
  noteEl.classList.remove("gg-hidden");
6987
7171
  prepareForDraw(noteEl);
6988
7172
  animateShapeDraw(noteEl, 420, 55);
7173
+ const notePathCount = noteEl.querySelectorAll('path').length;
7174
+ clearDashOverridesAfter(noteEl, notePathCount * 55 + 420 + 120);
6989
7175
  }
6990
7176
  return;
6991
7177
  }
@@ -7013,6 +7199,28 @@ var AIDiagram = (function (exports) {
7013
7199
  }
7014
7200
  return;
7015
7201
  }
7202
+ // ── Markdown ──────────────────────────────────────────
7203
+ const markdownEl = getMarkdownEl(this.svg, target);
7204
+ if (markdownEl) {
7205
+ if (silent) {
7206
+ markdownEl.style.transition = "none";
7207
+ markdownEl.style.opacity = "";
7208
+ markdownEl.classList.remove("gg-hidden");
7209
+ markdownEl.style.opacity = "1";
7210
+ requestAnimationFrame(() => requestAnimationFrame(() => {
7211
+ markdownEl.style.transition = "";
7212
+ }));
7213
+ }
7214
+ else {
7215
+ markdownEl.style.opacity = "0";
7216
+ markdownEl.classList.remove("gg-hidden");
7217
+ requestAnimationFrame(() => requestAnimationFrame(() => {
7218
+ markdownEl.style.transition = "opacity 500ms ease";
7219
+ markdownEl.style.opacity = "1";
7220
+ }));
7221
+ }
7222
+ return;
7223
+ }
7016
7224
  // ── Node draw ──────────────────────────────────────
7017
7225
  const nodeEl = getNodeEl(this.svg, target);
7018
7226
  if (!nodeEl)
@@ -7026,14 +7234,16 @@ var AIDiagram = (function (exports) {
7026
7234
  if (!firstPath?.style.strokeDasharray)
7027
7235
  prepareForDraw(nodeEl);
7028
7236
  animateShapeDraw(nodeEl, 420, 55);
7237
+ const nodePathCount = nodeEl.querySelectorAll('path').length;
7238
+ clearDashOverridesAfter(nodeEl, nodePathCount * 55 + 420 + 120);
7029
7239
  }
7030
7240
  }
7031
7241
  // ── erase ─────────────────────────────────────────────────
7032
7242
  _doErase(target) {
7033
7243
  const el = resolveEl(this.svg, target); // handles edges too now
7034
7244
  if (el) {
7035
- el.style.transition = 'opacity 0.4s';
7036
- el.style.opacity = '0';
7245
+ el.style.transition = "opacity 0.4s";
7246
+ el.style.opacity = "0";
7037
7247
  }
7038
7248
  }
7039
7249
  // ── show / hide ───────────────────────────────────────────
@@ -7061,10 +7271,10 @@ var AIDiagram = (function (exports) {
7061
7271
  return;
7062
7272
  // edge — color stroke
7063
7273
  if (parseEdgeTarget(target)) {
7064
- el.querySelectorAll('path, line, polyline').forEach(p => {
7274
+ el.querySelectorAll("path, line, polyline").forEach((p) => {
7065
7275
  p.style.stroke = color;
7066
7276
  });
7067
- el.querySelectorAll('polygon').forEach(p => {
7277
+ el.querySelectorAll("polygon").forEach((p) => {
7068
7278
  p.style.fill = color;
7069
7279
  p.style.stroke = color;
7070
7280
  });
@@ -7072,22 +7282,24 @@ var AIDiagram = (function (exports) {
7072
7282
  }
7073
7283
  // everything else — color fill
7074
7284
  let hit = false;
7075
- el.querySelectorAll('path, rect, ellipse, polygon').forEach(c => {
7076
- const attrFill = c.getAttribute('fill');
7077
- if (attrFill === 'none')
7285
+ el.querySelectorAll("path, rect, ellipse, polygon").forEach((c) => {
7286
+ const attrFill = c.getAttribute("fill");
7287
+ if (attrFill === "none")
7078
7288
  return;
7079
- if (attrFill === null && c.tagName === 'path')
7289
+ if (attrFill === null && c.tagName === "path")
7080
7290
  return;
7081
7291
  c.style.fill = color;
7082
7292
  hit = true;
7083
7293
  });
7084
7294
  if (!hit) {
7085
- el.querySelectorAll('text').forEach(t => { t.style.fill = color; });
7295
+ el.querySelectorAll("text").forEach((t) => {
7296
+ t.style.fill = color;
7297
+ });
7086
7298
  }
7087
7299
  }
7088
7300
  }
7089
7301
  const ANIMATION_CSS = `
7090
- .ng, .gg, .tg, .ntg, .cg, .eg {
7302
+ .ng, .gg, .tg, .ntg, .cg, .eg, .mdg {
7091
7303
  transform-box: fill-box;
7092
7304
  transform-origin: center;
7093
7305
  transition: filter 0.3s, opacity 0.35s;
@@ -7098,9 +7310,10 @@ var AIDiagram = (function (exports) {
7098
7310
  .tg.hl path, .tg.hl rect,
7099
7311
  .ntg.hl path, .ntg.hl polygon,
7100
7312
  .cg.hl path, .cg.hl rect,
7313
+ .mdg.hl text,
7101
7314
  .eg.hl path, .eg.hl line, .eg.hl polygon { stroke-width: 2.8 !important; }
7102
7315
 
7103
- .ng.hl, .tg.hl, .ntg.hl, .cg.hl, .eg.hl {
7316
+ .ng.hl, .tg.hl, .ntg.hl, .cg.hl, .mdg.hl, .eg.hl {
7104
7317
  animation: ng-pulse 1.4s ease-in-out infinite;
7105
7318
  }
7106
7319
  @keyframes ng-pulse {
@@ -7109,7 +7322,8 @@ var AIDiagram = (function (exports) {
7109
7322
  }
7110
7323
 
7111
7324
  /* fade */
7112
- .ng.faded, .gg.faded, .tg.faded, .ntg.faded, .cg.faded, .eg.faded { opacity: 0.22; }
7325
+ .ng.faded, .gg.faded, .tg.faded, .ntg.faded,
7326
+ .cg.faded, .eg.faded, .mdg.faded { opacity: 0.22; }
7113
7327
 
7114
7328
  .ng.hidden { opacity: 0; pointer-events: none; }
7115
7329
  .eg.draw-hidden { opacity: 0; }
@@ -7118,6 +7332,7 @@ var AIDiagram = (function (exports) {
7118
7332
  .tg.gg-hidden { opacity: 0; }
7119
7333
  .ntg.gg-hidden { opacity: 0; }
7120
7334
  .cg.gg-hidden { opacity: 0; }
7335
+ .mdg.gg-hidden { opacity: 0; }
7121
7336
  `;
7122
7337
 
7123
7338
  // ============================================================