schematex 0.9.1 → 0.9.3

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 (194) hide show
  1. package/README.md +21 -0
  2. package/dist/ai/ai-sdk.cjs +24 -24
  3. package/dist/ai/ai-sdk.d.cts +3 -3
  4. package/dist/ai/ai-sdk.d.ts +3 -3
  5. package/dist/ai/ai-sdk.js +19 -19
  6. package/dist/ai/index.cjs +33 -33
  7. package/dist/ai/index.d.cts +4 -4
  8. package/dist/ai/index.d.ts +4 -4
  9. package/dist/ai/index.js +20 -20
  10. package/dist/{api-OED2jUVj.d.ts → api-B7C7qGiG.d.cts} +2 -2
  11. package/dist/{api-BDMaX1cT.d.cts → api-DXOhuK3e.d.ts} +2 -2
  12. package/dist/browser.cjs +25 -25
  13. package/dist/browser.d.cts +3 -3
  14. package/dist/browser.d.ts +3 -3
  15. package/dist/browser.js +19 -19
  16. package/dist/{chunk-JN6FHUC6.js → chunk-4CLAD7VZ.js} +3 -3
  17. package/dist/{chunk-JN6FHUC6.js.map → chunk-4CLAD7VZ.js.map} +1 -1
  18. package/dist/{chunk-YTGOLTLJ.js → chunk-6GHXO3WQ.js} +4 -4
  19. package/dist/{chunk-YTGOLTLJ.js.map → chunk-6GHXO3WQ.js.map} +1 -1
  20. package/dist/{chunk-T5KHNJ67.cjs → chunk-6W6RFZND.cjs} +6 -6
  21. package/dist/{chunk-T5KHNJ67.cjs.map → chunk-6W6RFZND.cjs.map} +1 -1
  22. package/dist/{chunk-KF3DFASA.js → chunk-7P4C5DMD.js} +3 -3
  23. package/dist/chunk-7P4C5DMD.js.map +1 -0
  24. package/dist/{chunk-KGOZBABH.cjs → chunk-AEZVCGH4.cjs} +12 -12
  25. package/dist/{chunk-KGOZBABH.cjs.map → chunk-AEZVCGH4.cjs.map} +1 -1
  26. package/dist/{chunk-QYC6WZEM.cjs → chunk-AHSSFGDJ.cjs} +3064 -511
  27. package/dist/chunk-AHSSFGDJ.cjs.map +1 -0
  28. package/dist/{chunk-FJVQGBPU.cjs → chunk-ATE7LD6I.cjs} +5 -5
  29. package/dist/{chunk-FJVQGBPU.cjs.map → chunk-ATE7LD6I.cjs.map} +1 -1
  30. package/dist/{chunk-VOJGLBE5.js → chunk-BP6MLXJU.js} +2890 -338
  31. package/dist/chunk-BP6MLXJU.js.map +1 -0
  32. package/dist/{chunk-YMFYPB5Y.cjs → chunk-BXS53MLV.cjs} +4 -4
  33. package/dist/{chunk-YMFYPB5Y.cjs.map → chunk-BXS53MLV.cjs.map} +1 -1
  34. package/dist/{chunk-B5AQ3CG3.cjs → chunk-C2BNO3CI.cjs} +16 -13
  35. package/dist/chunk-C2BNO3CI.cjs.map +1 -0
  36. package/dist/{chunk-NZT5P2XZ.cjs → chunk-CAAMBDEN.cjs} +347 -2
  37. package/dist/chunk-CAAMBDEN.cjs.map +1 -0
  38. package/dist/{chunk-UK6HF6PE.js → chunk-D4QZ6UDO.js} +342 -3
  39. package/dist/chunk-D4QZ6UDO.js.map +1 -0
  40. package/dist/{chunk-DHI7YAQJ.cjs → chunk-FFAJQ36U.cjs} +15 -15
  41. package/dist/{chunk-DHI7YAQJ.cjs.map → chunk-FFAJQ36U.cjs.map} +1 -1
  42. package/dist/{chunk-LENRV7ZJ.cjs → chunk-IOD2EFMX.cjs} +5 -5
  43. package/dist/chunk-IOD2EFMX.cjs.map +1 -0
  44. package/dist/{chunk-JIUC4DRS.cjs → chunk-IU26USXA.cjs} +6 -6
  45. package/dist/chunk-IU26USXA.cjs.map +1 -0
  46. package/dist/{chunk-Z5ML4QYG.js → chunk-JCJWSW5Y.js} +406 -4
  47. package/dist/chunk-JCJWSW5Y.js.map +1 -0
  48. package/dist/{chunk-DX44TBFZ.js → chunk-JEQGWH5N.js} +20 -28
  49. package/dist/chunk-JEQGWH5N.js.map +1 -0
  50. package/dist/{chunk-NXU4XKLY.cjs → chunk-JHJJT5H5.cjs} +9 -7
  51. package/dist/chunk-JHJJT5H5.cjs.map +1 -0
  52. package/dist/{chunk-AJJYWXZB.cjs → chunk-JYAL26WQ.cjs} +4 -4
  53. package/dist/chunk-JYAL26WQ.cjs.map +1 -0
  54. package/dist/{chunk-2R4UXKCT.cjs → chunk-LFJE64RD.cjs} +408 -6
  55. package/dist/chunk-LFJE64RD.cjs.map +1 -0
  56. package/dist/{chunk-IXRPRMHI.js → chunk-MTIZIHWE.js} +9 -7
  57. package/dist/chunk-MTIZIHWE.js.map +1 -0
  58. package/dist/{chunk-NFZMNKOR.cjs → chunk-MZWVJFTV.cjs} +15 -15
  59. package/dist/{chunk-NFZMNKOR.cjs.map → chunk-MZWVJFTV.cjs.map} +1 -1
  60. package/dist/{chunk-6L46VIXI.cjs → chunk-O6A2GJLI.cjs} +20 -28
  61. package/dist/chunk-O6A2GJLI.cjs.map +1 -0
  62. package/dist/{chunk-K2D6VFLP.js → chunk-OND4N5ZZ.js} +78 -22
  63. package/dist/chunk-OND4N5ZZ.js.map +1 -0
  64. package/dist/{chunk-J34HDRFY.js → chunk-PUD7PIY5.js} +4 -4
  65. package/dist/{chunk-J34HDRFY.js.map → chunk-PUD7PIY5.js.map} +1 -1
  66. package/dist/{chunk-6NUAGU6O.js → chunk-PZCYFT2A.js} +3 -3
  67. package/dist/{chunk-6NUAGU6O.js.map → chunk-PZCYFT2A.js.map} +1 -1
  68. package/dist/{chunk-EBX4KCYW.js → chunk-QHZEGWE7.js} +4 -4
  69. package/dist/{chunk-EBX4KCYW.js.map → chunk-QHZEGWE7.js.map} +1 -1
  70. package/dist/{chunk-ABCMTAOZ.js → chunk-TDSGQT7R.js} +7 -4
  71. package/dist/chunk-TDSGQT7R.js.map +1 -0
  72. package/dist/{chunk-47SGK5R6.cjs → chunk-TFNH2NLJ.cjs} +5 -5
  73. package/dist/{chunk-47SGK5R6.cjs.map → chunk-TFNH2NLJ.cjs.map} +1 -1
  74. package/dist/{chunk-CDK7KDIW.js → chunk-U4AJLMHC.js} +4 -4
  75. package/dist/{chunk-CDK7KDIW.js.map → chunk-U4AJLMHC.js.map} +1 -1
  76. package/dist/{chunk-UOM3EDE6.js → chunk-UNLLWCQK.js} +3 -3
  77. package/dist/{chunk-UOM3EDE6.js.map → chunk-UNLLWCQK.js.map} +1 -1
  78. package/dist/{chunk-D34VGLSE.cjs → chunk-VG5LP5A4.cjs} +5 -5
  79. package/dist/{chunk-D34VGLSE.cjs.map → chunk-VG5LP5A4.cjs.map} +1 -1
  80. package/dist/{chunk-Q2YRJHFB.cjs → chunk-VOFND6ZQ.cjs} +7 -7
  81. package/dist/chunk-VOFND6ZQ.cjs.map +1 -0
  82. package/dist/{chunk-JGCKW5RS.js → chunk-X3GEGVW2.js} +5 -5
  83. package/dist/chunk-X3GEGVW2.js.map +1 -0
  84. package/dist/{chunk-GDZNTILD.js → chunk-XAYD5NVJ.js} +4 -4
  85. package/dist/chunk-XAYD5NVJ.js.map +1 -0
  86. package/dist/{chunk-2SZJQVPN.cjs → chunk-Y4OBXYGW.cjs} +87 -30
  87. package/dist/chunk-Y4OBXYGW.cjs.map +1 -0
  88. package/dist/{chunk-E7LXMEKX.js → chunk-ZQECHIBI.js} +3 -3
  89. package/dist/{chunk-E7LXMEKX.js.map → chunk-ZQECHIBI.js.map} +1 -1
  90. package/dist/{chunk-E3CAJGJM.js → chunk-ZUK4BY55.js} +6 -6
  91. package/dist/chunk-ZUK4BY55.js.map +1 -0
  92. package/dist/{diagnostics-hObcaaFC.d.ts → diagnostics-fyjWjeDa.d.cts} +1 -1
  93. package/dist/{diagnostics-hObcaaFC.d.cts → diagnostics-fyjWjeDa.d.ts} +1 -1
  94. package/dist/diagrams/blockdiagram/index.cjs +6 -5
  95. package/dist/diagrams/blockdiagram/index.d.cts +2 -2
  96. package/dist/diagrams/blockdiagram/index.d.ts +2 -2
  97. package/dist/diagrams/blockdiagram/index.js +2 -1
  98. package/dist/diagrams/circuit/index.cjs +9 -9
  99. package/dist/diagrams/circuit/index.d.cts +1 -1
  100. package/dist/diagrams/circuit/index.d.ts +1 -1
  101. package/dist/diagrams/circuit/index.js +2 -2
  102. package/dist/diagrams/ecomap/index.cjs +7 -7
  103. package/dist/diagrams/ecomap/index.d.cts +1 -1
  104. package/dist/diagrams/ecomap/index.d.ts +1 -1
  105. package/dist/diagrams/ecomap/index.js +2 -2
  106. package/dist/diagrams/entity/index.cjs +6 -6
  107. package/dist/diagrams/entity/index.d.cts +1 -1
  108. package/dist/diagrams/entity/index.d.ts +1 -1
  109. package/dist/diagrams/entity/index.js +2 -2
  110. package/dist/diagrams/fishbone/index.cjs +8 -8
  111. package/dist/diagrams/fishbone/index.d.cts +1 -1
  112. package/dist/diagrams/fishbone/index.d.ts +1 -1
  113. package/dist/diagrams/fishbone/index.js +2 -2
  114. package/dist/diagrams/flowchart/index.cjs +8 -8
  115. package/dist/diagrams/flowchart/index.d.cts +2 -2
  116. package/dist/diagrams/flowchart/index.d.ts +2 -2
  117. package/dist/diagrams/flowchart/index.js +2 -2
  118. package/dist/diagrams/genogram/index.cjs +9 -9
  119. package/dist/diagrams/genogram/index.d.cts +1 -1
  120. package/dist/diagrams/genogram/index.d.ts +1 -1
  121. package/dist/diagrams/genogram/index.js +2 -2
  122. package/dist/diagrams/ladder/index.cjs +6 -6
  123. package/dist/diagrams/ladder/index.d.cts +1 -1
  124. package/dist/diagrams/ladder/index.d.ts +1 -1
  125. package/dist/diagrams/ladder/index.js +2 -2
  126. package/dist/diagrams/logic/index.cjs +8 -8
  127. package/dist/diagrams/logic/index.d.cts +1 -1
  128. package/dist/diagrams/logic/index.d.ts +1 -1
  129. package/dist/diagrams/logic/index.js +2 -2
  130. package/dist/diagrams/orgchart/index.cjs +8 -8
  131. package/dist/diagrams/orgchart/index.d.cts +1 -1
  132. package/dist/diagrams/orgchart/index.d.ts +1 -1
  133. package/dist/diagrams/orgchart/index.js +2 -2
  134. package/dist/diagrams/pedigree/index.cjs +7 -7
  135. package/dist/diagrams/pedigree/index.d.cts +1 -1
  136. package/dist/diagrams/pedigree/index.d.ts +1 -1
  137. package/dist/diagrams/pedigree/index.js +2 -2
  138. package/dist/diagrams/phylo/index.cjs +7 -7
  139. package/dist/diagrams/phylo/index.d.cts +1 -1
  140. package/dist/diagrams/phylo/index.d.ts +1 -1
  141. package/dist/diagrams/phylo/index.js +2 -2
  142. package/dist/diagrams/sld/index.cjs +8 -8
  143. package/dist/diagrams/sld/index.d.cts +1 -1
  144. package/dist/diagrams/sld/index.d.ts +1 -1
  145. package/dist/diagrams/sld/index.js +2 -2
  146. package/dist/diagrams/sociogram/index.cjs +6 -6
  147. package/dist/diagrams/sociogram/index.d.cts +1 -1
  148. package/dist/diagrams/sociogram/index.d.ts +1 -1
  149. package/dist/diagrams/sociogram/index.js +2 -2
  150. package/dist/diagrams/timing/index.cjs +4 -4
  151. package/dist/diagrams/timing/index.d.cts +1 -1
  152. package/dist/diagrams/timing/index.d.ts +1 -1
  153. package/dist/diagrams/timing/index.js +1 -1
  154. package/dist/diagrams/venn/index.cjs +9 -9
  155. package/dist/diagrams/venn/index.d.cts +1 -1
  156. package/dist/diagrams/venn/index.d.ts +1 -1
  157. package/dist/diagrams/venn/index.js +2 -2
  158. package/dist/{index-Cq8y1aaa.d.ts → index-CFaKjWPy.d.ts} +1 -1
  159. package/dist/{index-BiXWjQht.d.cts → index-DZNmJWGg.d.cts} +1 -1
  160. package/dist/index.cjs +113 -87
  161. package/dist/index.cjs.map +1 -1
  162. package/dist/index.d.cts +4 -4
  163. package/dist/index.d.ts +4 -4
  164. package/dist/index.js +49 -23
  165. package/dist/index.js.map +1 -1
  166. package/dist/react.cjs +19 -19
  167. package/dist/react.d.cts +2 -2
  168. package/dist/react.d.ts +2 -2
  169. package/dist/react.js +18 -18
  170. package/dist/{tools-D5dkAqNy.d.cts → tools-BDNih0Mb.d.ts} +3 -3
  171. package/dist/{tools-BbTuTWs_.d.ts → tools-C23t_WeN.d.cts} +3 -3
  172. package/package.json +1 -1
  173. package/dist/chunk-2R4UXKCT.cjs.map +0 -1
  174. package/dist/chunk-2SZJQVPN.cjs.map +0 -1
  175. package/dist/chunk-6L46VIXI.cjs.map +0 -1
  176. package/dist/chunk-ABCMTAOZ.js.map +0 -1
  177. package/dist/chunk-AJJYWXZB.cjs.map +0 -1
  178. package/dist/chunk-B5AQ3CG3.cjs.map +0 -1
  179. package/dist/chunk-DX44TBFZ.js.map +0 -1
  180. package/dist/chunk-E3CAJGJM.js.map +0 -1
  181. package/dist/chunk-GDZNTILD.js.map +0 -1
  182. package/dist/chunk-IXRPRMHI.js.map +0 -1
  183. package/dist/chunk-JGCKW5RS.js.map +0 -1
  184. package/dist/chunk-JIUC4DRS.cjs.map +0 -1
  185. package/dist/chunk-K2D6VFLP.js.map +0 -1
  186. package/dist/chunk-KF3DFASA.js.map +0 -1
  187. package/dist/chunk-LENRV7ZJ.cjs.map +0 -1
  188. package/dist/chunk-NXU4XKLY.cjs.map +0 -1
  189. package/dist/chunk-NZT5P2XZ.cjs.map +0 -1
  190. package/dist/chunk-Q2YRJHFB.cjs.map +0 -1
  191. package/dist/chunk-QYC6WZEM.cjs.map +0 -1
  192. package/dist/chunk-UK6HF6PE.js.map +0 -1
  193. package/dist/chunk-VOJGLBE5.js.map +0 -1
  194. package/dist/chunk-Z5ML4QYG.js.map +0 -1
@@ -1,21 +1,21 @@
1
- import { orgchart } from './chunk-E3CAJGJM.js';
2
- import { circuit } from './chunk-EBX4KCYW.js';
3
- import { blockdiagram } from './chunk-DX44TBFZ.js';
4
- import { ladder } from './chunk-JGCKW5RS.js';
5
- import { sld } from './chunk-KF3DFASA.js';
6
- import { entity } from './chunk-GDZNTILD.js';
7
- import { fishbone } from './chunk-YTGOLTLJ.js';
8
- import { venn } from './chunk-UOM3EDE6.js';
9
- import { flowchart, layoutFlowchart } from './chunk-J34HDRFY.js';
10
- import { genogram } from './chunk-6NUAGU6O.js';
11
- import { ecomap } from './chunk-K2D6VFLP.js';
12
- import { pedigree } from './chunk-JN6FHUC6.js';
1
+ import { orgchart } from './chunk-ZUK4BY55.js';
2
+ import { circuit } from './chunk-QHZEGWE7.js';
3
+ import { blockdiagram } from './chunk-JEQGWH5N.js';
4
+ import { ladder } from './chunk-X3GEGVW2.js';
5
+ import { sld } from './chunk-7P4C5DMD.js';
6
+ import { entity } from './chunk-XAYD5NVJ.js';
7
+ import { fishbone } from './chunk-6GHXO3WQ.js';
8
+ import { venn } from './chunk-UNLLWCQK.js';
9
+ import { flowchart, layoutFlowchart } from './chunk-PUD7PIY5.js';
10
+ import { genogram } from './chunk-PZCYFT2A.js';
11
+ import { ecomap, estimateTextWidth } from './chunk-OND4N5ZZ.js';
12
+ import { pedigree } from './chunk-4CLAD7VZ.js';
13
13
  import { parseFrontmatter } from './chunk-2KTQ75LN.js';
14
- import { phylo } from './chunk-E7LXMEKX.js';
15
- import { sociogram } from './chunk-ABCMTAOZ.js';
16
- import { timing } from './chunk-IXRPRMHI.js';
17
- import { logic } from './chunk-CDK7KDIW.js';
18
- import { resolveBaseTheme, resolveTimelineTheme, cssCustomProperties, resolvePetriTheme, resolveNetworkTheme, resolveUmlClassTheme, DEFAULT_FONT_FAMILY, FONT_SIZE, resolveReliabilityTheme, STROKE_WIDTH, resolveBowtieTheme, resolveMindmapTheme } from './chunk-UK6HF6PE.js';
14
+ import { phylo } from './chunk-ZQECHIBI.js';
15
+ import { sociogram } from './chunk-TDSGQT7R.js';
16
+ import { timing } from './chunk-MTIZIHWE.js';
17
+ import { logic } from './chunk-U4AJLMHC.js';
18
+ import { resolveBaseTheme, resolveTimelineTheme, resolveStateTheme, cssCustomProperties, resolvePetriTheme, resolveNetworkTheme, resolveUmlClassTheme, DEFAULT_FONT_FAMILY, FONT_SIZE, resolveReliabilityTheme, STROKE_WIDTH, resolveBowtieTheme, resolveMindmapTheme, resolveMatrixTheme, resolveBpmnTheme, resolveFloorplanTheme, TITLE } from './chunk-D4QZ6UDO.js';
19
19
  import { QUOTE_PAIRS, stripQuotes, isOpenQuote, extractQuotedString, matchQuotedTitle } from './chunk-TO6PNBT3.js';
20
20
  import { el, escapeXml, group, rect, text, line, path, circle, polygon, title, desc, svgRoot, defs, multilineText } from './chunk-SYYBKDL7.js';
21
21
 
@@ -1253,7 +1253,7 @@ var CLASS_PALETTE = [
1253
1253
  function buildCss2(t) {
1254
1254
  return `
1255
1255
  .lt-dtree { font-family: system-ui, -apple-system, sans-serif; }
1256
- .lt-dtree-title { font: 500 16px sans-serif; fill: ${t.text}; }
1256
+ .lt-dtree-title { font: 700 16px sans-serif; fill: ${t.text}; }
1257
1257
  .lt-dtree-edge { fill: none; stroke: ${t.stroke}; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
1258
1258
  .lt-dtree-edge-optimal { fill: none; stroke: ${t.positive}; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
1259
1259
  .lt-dtree-edge-leader { fill: none; stroke: ${t.stroke}; stroke-width: 1; stroke-dasharray: 2 2; opacity: 0.55; }
@@ -1585,7 +1585,7 @@ function renderDecisionTree(ast, config) {
1585
1585
  children.push(desc(`Decision tree (${ast.mode} mode) with ${layout.nodes.length} nodes and ${layout.edges.length} edges`));
1586
1586
  children.push(el("style", {}, buildCss2(t)));
1587
1587
  if (ast.title) {
1588
- children.push(text({ x: 20, y: 24, class: "lt-dtree-title" }, ast.title));
1588
+ children.push(text({ x: width / 2, y: 24, class: "lt-dtree-title", "text-anchor": "middle" }, ast.title));
1589
1589
  }
1590
1590
  const inner = [];
1591
1591
  for (const e of layout.edges) {
@@ -2283,11 +2283,11 @@ function layoutSwimlane(ast) {
2283
2283
  ];
2284
2284
  let labelY = candidates[0];
2285
2285
  for (const y of candidates) {
2286
- const box = { x1: x - labelW / 2, x2: x + labelW / 2, y };
2287
- const collide = labelBoxes.some((b) => Math.abs(b.y - box.y) < 13 && b.x1 < box.x2 && b.x2 > box.x1);
2286
+ const box2 = { x1: x - labelW / 2, x2: x + labelW / 2, y };
2287
+ const collide = labelBoxes.some((b) => Math.abs(b.y - box2.y) < 13 && b.x1 < box2.x2 && b.x2 > box2.x1);
2288
2288
  if (!collide) {
2289
2289
  labelY = y;
2290
- labelBoxes.push(box);
2290
+ labelBoxes.push(box2);
2291
2291
  break;
2292
2292
  }
2293
2293
  }
@@ -2448,10 +2448,10 @@ function layoutGantt(ast) {
2448
2448
  let labelY = pinZoneTop + 12;
2449
2449
  let step = 0;
2450
2450
  while (step < 4) {
2451
- const box = { x1: x - labelW / 2, x2: x + labelW / 2, y: labelY };
2452
- const collide = pinBoxes.some((b) => Math.abs(b.y - box.y) < 14 && b.x1 < box.x2 && b.x2 > box.x1);
2451
+ const box2 = { x1: x - labelW / 2, x2: x + labelW / 2, y: labelY };
2452
+ const collide = pinBoxes.some((b) => Math.abs(b.y - box2.y) < 14 && b.x1 < box2.x2 && b.x2 > box2.x1);
2453
2453
  if (!collide) {
2454
- pinBoxes.push(box);
2454
+ pinBoxes.push(box2);
2455
2455
  break;
2456
2456
  }
2457
2457
  labelY += 16;
@@ -4124,40 +4124,42 @@ function shiftPathY(d, dy) {
4124
4124
 
4125
4125
  // src/diagrams/state/renderer.ts
4126
4126
  var ARROW_MARKER_ID = "lt-state-arrow";
4127
- var STYLE = `
4128
- .lt-state-body { fill: #ffffff; stroke: #2a2a2a; stroke-width: 1.6; }
4129
- .lt-state-name { font: 600 12px system-ui, sans-serif; fill: #1a1a1a; }
4130
- .lt-state-div { stroke: #2a2a2a; stroke-width: 1; }
4131
- .lt-state-activity { font: 11px ui-monospace, monospace; fill: #444; }
4127
+ function buildStyle(t) {
4128
+ return `
4129
+ .lt-state-body { fill: ${t.stateFill}; stroke: ${t.stateStroke}; stroke-width: 1.6; }
4130
+ .lt-state-name { font: 600 12px system-ui, sans-serif; fill: ${t.stateText}; }
4131
+ .lt-state-div { stroke: ${t.stateStroke}; stroke-width: 1; }
4132
+ .lt-state-activity { font: 11px ui-monospace, monospace; fill: ${t.activityText}; }
4132
4133
 
4133
- .lt-composite-body { fill: #fafafa; stroke: #2a2a2a; stroke-width: 1.6; }
4134
- .lt-composite-title { font: 600 12px system-ui, sans-serif; fill: #1a1a1a; }
4135
- .lt-composite-titlebar { fill: #f0f0f0; stroke: #2a2a2a; stroke-width: 1; }
4136
- .lt-region-div { stroke: #888; stroke-width: 1; stroke-dasharray: 6 4; }
4134
+ .lt-composite-body { fill: ${t.compositeFill}; stroke: ${t.stateStroke}; stroke-width: 1.6; }
4135
+ .lt-composite-title { font: 600 12px system-ui, sans-serif; fill: ${t.stateText}; }
4136
+ .lt-composite-titlebar { fill: ${t.compositeTitlebar}; stroke: ${t.stateStroke}; stroke-width: 1; }
4137
+ .lt-region-div { stroke: ${t.regionDiv}; stroke-width: 1; stroke-dasharray: 6 4; }
4137
4138
 
4138
- .lt-ps-initial { fill: #1a1a1a; }
4139
- .lt-ps-final-outer { fill: #ffffff; stroke: #1a1a1a; stroke-width: 1.6; }
4140
- .lt-ps-final-inner { fill: #1a1a1a; }
4141
- .lt-ps-choice { fill: #ffffff; stroke: #1a1a1a; stroke-width: 1.6; }
4142
- .lt-ps-junction { fill: #1a1a1a; }
4143
- .lt-ps-bar { fill: #1a1a1a; }
4144
- .lt-ps-history-body { fill: #ffffff; stroke: #1a1a1a; stroke-width: 1.6; }
4145
- .lt-ps-history-text { font: 600 11px serif; fill: #1a1a1a; }
4146
- .lt-ps-terminate { stroke: #1a1a1a; stroke-width: 2; }
4147
- .lt-ps-entrypoint { fill: #ffffff; stroke: #1a1a1a; stroke-width: 1.6; }
4148
- .lt-ps-exitpoint { fill: #ffffff; stroke: #1a1a1a; stroke-width: 1.6; }
4139
+ .lt-ps-initial { fill: ${t.psInk}; }
4140
+ .lt-ps-final-outer { fill: ${t.stateFill}; stroke: ${t.psInk}; stroke-width: 1.6; }
4141
+ .lt-ps-final-inner { fill: ${t.psInk}; }
4142
+ .lt-ps-choice { fill: ${t.stateFill}; stroke: ${t.psInk}; stroke-width: 1.6; }
4143
+ .lt-ps-junction { fill: ${t.psInk}; }
4144
+ .lt-ps-bar { fill: ${t.psInk}; }
4145
+ .lt-ps-history-body { fill: ${t.stateFill}; stroke: ${t.psInk}; stroke-width: 1.6; }
4146
+ .lt-ps-history-text { font: 600 11px serif; fill: ${t.psInk}; }
4147
+ .lt-ps-terminate { stroke: ${t.psInk}; stroke-width: 2; }
4148
+ .lt-ps-entrypoint { fill: ${t.stateFill}; stroke: ${t.psInk}; stroke-width: 1.6; }
4149
+ .lt-ps-exitpoint { fill: ${t.stateFill}; stroke: ${t.psInk}; stroke-width: 1.6; }
4149
4150
 
4150
- .lt-transition { stroke: #2a2a2a; stroke-width: 1.4; fill: none; }
4151
- .lt-transition-label { font: 11px system-ui, sans-serif; fill: #1a1a1a; }
4152
- .lt-transition-label-bg { fill: #ffffff; opacity: 0.92; }
4151
+ .lt-transition { stroke: ${t.transitionStroke}; stroke-width: 1.4; fill: none; }
4152
+ .lt-transition-label { font: 11px system-ui, sans-serif; fill: ${t.transitionLabel}; }
4153
+ .lt-transition-label-bg { fill: ${t.labelBg}; opacity: 0.92; }
4153
4154
 
4154
- .lt-note-body { fill: #fff8c5; stroke: #b79400; stroke-width: 1; }
4155
- .lt-note-text { font: 11px system-ui, sans-serif; fill: #2a2a2a; }
4156
- .lt-note-leader { stroke: #b79400; stroke-width: 1; stroke-dasharray: 3 3; fill: none; }
4155
+ .lt-note-body { fill: ${t.noteFill}; stroke: ${t.noteStroke}; stroke-width: 1; }
4156
+ .lt-note-text { font: 11px system-ui, sans-serif; fill: ${t.noteText}; }
4157
+ .lt-note-leader { stroke: ${t.noteStroke}; stroke-width: 1; stroke-dasharray: 3 3; fill: none; }
4157
4158
 
4158
- .lt-title { font: 600 14px system-ui, sans-serif; fill: #1a1a1a; }
4159
+ .lt-title { font: 700 16px system-ui, sans-serif; fill: ${t.stateText}; }
4159
4160
  `;
4160
- function renderArrowMarker() {
4161
+ }
4162
+ function renderArrowMarker(t) {
4161
4163
  return el(
4162
4164
  "marker",
4163
4165
  {
@@ -4169,7 +4171,7 @@ function renderArrowMarker() {
4169
4171
  orient: "auto",
4170
4172
  markerUnits: "strokeWidth"
4171
4173
  },
4172
- [polygon({ points: "0,0 10,3 0,6", fill: "#2a2a2a" })]
4174
+ [polygon({ points: "0,0 10,3 0,6", fill: t.transitionStroke })]
4173
4175
  );
4174
4176
  }
4175
4177
  function activityText2(a) {
@@ -4400,12 +4402,12 @@ function renderNote(n) {
4400
4402
  }
4401
4403
  return group({ class: "lt-note", "data-target": n.note.target }, parts);
4402
4404
  }
4403
- function renderStateDiagram(ast, _config) {
4405
+ function renderStateDiagram(ast, config) {
4404
4406
  const layout = layoutStateDiagram(ast);
4405
- return renderLayout(layout);
4407
+ return renderLayout(layout, resolveStateTheme(config?.theme ?? "default"));
4406
4408
  }
4407
- function renderLayout(layout) {
4408
- const titleNode = layout.title ? text({ x: 16, y: 22, class: "lt-title" }, layout.title) : "";
4409
+ function renderLayout(layout, t) {
4410
+ const titleNode = layout.title ? text({ x: layout.width / 2, y: 22, class: "lt-title", "text-anchor": "middle" }, layout.title) : "";
4409
4411
  return svgRoot(
4410
4412
  {
4411
4413
  width: layout.width,
@@ -4417,7 +4419,7 @@ function renderLayout(layout) {
4417
4419
  [
4418
4420
  el("title", {}, escapeXml(`State Diagram${layout.title ? " \u2014 " + layout.title : ""}`)),
4419
4421
  el("desc", {}, "UML 2.5 / Harel statechart rendered by Schematex"),
4420
- defs([renderArrowMarker(), el("style", {}, STYLE)]),
4422
+ defs([renderArrowMarker(t), el("style", {}, buildStyle(t))]),
4421
4423
  titleNode,
4422
4424
  // Composite clusters first so simple-state bodies sit on top.
4423
4425
  group({ class: "lt-clusters" }, layout.clusters.map(renderComposite)),
@@ -4437,7 +4439,7 @@ var state = {
4437
4439
  parse: parseStateDiagram,
4438
4440
  render(text2, config) {
4439
4441
  const ast = parseStateDiagram(text2);
4440
- return renderStateDiagram(ast);
4442
+ return renderStateDiagram(ast, config);
4441
4443
  }
4442
4444
  };
4443
4445
 
@@ -5787,7 +5789,7 @@ function resolveAnchor(id, port, fallback, equipById, instById) {
5787
5789
  }
5788
5790
 
5789
5791
  // src/diagrams/pid/renderer.ts
5790
- var STYLE2 = `
5792
+ var STYLE = `
5791
5793
  .lt-pid-equip { fill: #ffffff; stroke: #1d1d1d; stroke-width: 1.6; }
5792
5794
  .lt-pid-equip-tag { font: 600 11px system-ui, sans-serif; fill: #1d1d1d; }
5793
5795
  .lt-pid-tray-line { stroke: #555; stroke-width: 1; fill: none; }
@@ -5813,7 +5815,7 @@ var STYLE2 = `
5813
5815
  .lt-pid-line-tag-bg { fill: #ffffff; stroke: #1d1d1d; stroke-width: 0.6; }
5814
5816
  .lt-pid-line-tag-text { font: 9px ui-monospace, monospace; fill: #1d1d1d; }
5815
5817
 
5816
- .lt-pid-title { font: 600 14px system-ui, sans-serif; fill: #1d1d1d; }
5818
+ .lt-pid-title { font: 700 16px system-ui, sans-serif; fill: #1d1d1d; }
5817
5819
 
5818
5820
  .lt-pid-unknown-box { fill: none; stroke: #c0392b; stroke-width: 1.6; stroke-dasharray: 5 3; }
5819
5821
  .lt-pid-unknown-mark { font: 700 18px system-ui, sans-serif; fill: #c0392b; }
@@ -5988,7 +5990,7 @@ function renderLayout2(layout) {
5988
5990
  }
5989
5991
  }
5990
5992
  }
5991
- const titleNode = layout.title ? text({ x: 16, y: 22, class: "lt-pid-title" }, layout.title) : "";
5993
+ const titleNode = layout.title ? text({ x: layout.width / 2, y: 22, class: "lt-pid-title", "text-anchor": "middle" }, layout.title) : "";
5992
5994
  return svgRoot(
5993
5995
  {
5994
5996
  width: layout.width,
@@ -6014,7 +6016,7 @@ function renderLayout2(layout) {
6014
6016
  },
6015
6017
  [polygon({ points: "0,0 10,3 0,6", fill: "#1d1d1d" })]
6016
6018
  ),
6017
- el("style", {}, STYLE2)
6019
+ el("style", {}, STYLE)
6018
6020
  ]),
6019
6021
  titleNode,
6020
6022
  // Z-order: process pipes behind equipment; signal lines + instruments above.
@@ -7386,7 +7388,7 @@ function headerHeightFor(lineCount) {
7386
7388
  function buildCss3(t) {
7387
7389
  return `
7388
7390
  .prisma { font-family: system-ui, -apple-system, sans-serif; }
7389
- .prisma-title { font: 600 17px sans-serif; fill: ${t.text}; }
7391
+ .prisma-title { font: 700 16px sans-serif; fill: ${t.text}; }
7390
7392
  .prisma-subtitle { font: 400 12px sans-serif; fill: ${t.textMuted}; }
7391
7393
 
7392
7394
  .prisma-stage { stroke-width: 1.1; stroke: #90a4ae; }
@@ -7451,10 +7453,10 @@ function arrowMarker(t) {
7451
7453
  )
7452
7454
  ]);
7453
7455
  }
7454
- function classForBox(box) {
7455
- if (box.variant === "previous") return "prisma-stage-previous";
7456
- const base = box.variant === "exclusion" ? "prisma-exclusion" : "prisma-stage";
7457
- return `${base} prisma-stage-${box.stage}`;
7456
+ function classForBox(box2) {
7457
+ if (box2.variant === "previous") return "prisma-stage-previous";
7458
+ const base = box2.variant === "exclusion" ? "prisma-exclusion" : "prisma-stage";
7459
+ return `${base} prisma-stage-${box2.stage}`;
7458
7460
  }
7459
7461
  function classForLine(style) {
7460
7462
  switch (style) {
@@ -7538,22 +7540,22 @@ function renderHeader(h) {
7538
7540
  );
7539
7541
  return group({ "data-header": h.column }, [r6, ...lines]);
7540
7542
  }
7541
- function renderBox(box) {
7543
+ function renderBox(box2) {
7542
7544
  const r6 = rect({
7543
- x: box.x,
7544
- y: box.y,
7545
- width: box.width,
7546
- height: box.height,
7545
+ x: box2.x,
7546
+ y: box2.y,
7547
+ width: box2.width,
7548
+ height: box2.height,
7547
7549
  rx: PRISMA_CONST.BOX_RADIUS,
7548
7550
  ry: PRISMA_CONST.BOX_RADIUS,
7549
- class: classForBox(box),
7550
- "data-role": box.role
7551
+ class: classForBox(box2),
7552
+ "data-role": box2.role
7551
7553
  });
7552
- const innerLeft = box.x + PRISMA_CONST.BOX_PAD_X;
7553
- const innerRight = box.x + box.width - PRISMA_CONST.BOX_PAD_X;
7554
- const centerX = box.x + box.width / 2;
7554
+ const innerLeft = box2.x + PRISMA_CONST.BOX_PAD_X;
7555
+ const innerRight = box2.x + box2.width - PRISMA_CONST.BOX_PAD_X;
7556
+ const centerX = box2.x + box2.width / 2;
7555
7557
  let widestIndented = 0;
7556
- for (const line2 of box.lines) {
7558
+ for (const line2 of box2.lines) {
7557
7559
  if ((line2.indent ?? 0) > 0) {
7558
7560
  const w = approxLineWidth(line2);
7559
7561
  if (w > widestIndented) widestIndented = w;
@@ -7563,9 +7565,9 @@ function renderBox(box) {
7563
7565
  innerLeft,
7564
7566
  Math.min(centerX - widestIndented / 2, innerRight - widestIndented)
7565
7567
  );
7566
- let textY = box.y + PRISMA_CONST.BOX_PAD_Y;
7568
+ let textY = box2.y + PRISMA_CONST.BOX_PAD_Y;
7567
7569
  const textEls = [];
7568
- for (const line2 of box.lines) {
7570
+ for (const line2 of box2.lines) {
7569
7571
  const lh = lineHeightForLine(line2.style);
7570
7572
  const baseline = textY + Math.round(lh * 0.75);
7571
7573
  const indented = (line2.indent ?? 0) > 0;
@@ -7593,9 +7595,9 @@ function renderBox(box) {
7593
7595
  }
7594
7596
  return group(
7595
7597
  {
7596
- "data-prisma-role": box.role,
7597
- "data-prisma-variant": box.variant,
7598
- "data-prisma-stage": box.stage
7598
+ "data-prisma-role": box2.role,
7599
+ "data-prisma-variant": box2.variant,
7600
+ "data-prisma-stage": box2.stage
7599
7601
  },
7600
7602
  [r6, ...textEls]
7601
7603
  );
@@ -8269,7 +8271,7 @@ function ellipsePerimeterPoint(cx, cy, rx, ry, tx, ty) {
8269
8271
  function isWideGlyph(cp) {
8270
8272
  return cp >= 12288 && cp <= 40959 || cp >= 44032 && cp <= 55203 || cp >= 65280 && cp <= 65519;
8271
8273
  }
8272
- function estimateTextWidth(s, charW) {
8274
+ function estimateTextWidth2(s, charW) {
8273
8275
  let w = 0;
8274
8276
  for (const ch of s) {
8275
8277
  w += isWideGlyph(ch.codePointAt(0) ?? 0) ? charW * 1.7 : charW;
@@ -8278,10 +8280,10 @@ function estimateTextWidth(s, charW) {
8278
8280
  }
8279
8281
  function sizeEllipse(uc) {
8280
8282
  const C2 = USECASE_CONST;
8281
- const nameW = estimateTextWidth(uc.name, C2.CHAR_W_NAME);
8283
+ const nameW = estimateTextWidth2(uc.name, C2.CHAR_W_NAME);
8282
8284
  let widest = nameW;
8283
8285
  if (uc.stereotype) {
8284
- widest = Math.max(widest, estimateTextWidth(`\xAB${uc.stereotype}\xBB`, C2.CHAR_W_EXT));
8286
+ widest = Math.max(widest, estimateTextWidth2(`\xAB${uc.stereotype}\xBB`, C2.CHAR_W_EXT));
8285
8287
  }
8286
8288
  let stack = 0;
8287
8289
  if (uc.stereotype) stack += C2.STEREO_LH;
@@ -8291,9 +8293,9 @@ function sizeEllipse(uc) {
8291
8293
  stack += 8;
8292
8294
  stack += C2.EXTPOINT_LH;
8293
8295
  stack += uc.extensionPoints.length * C2.EXTPOINT_LH;
8294
- widest = Math.max(widest, estimateTextWidth(C2.EXT_HEADER, C2.CHAR_W_EXT));
8296
+ widest = Math.max(widest, estimateTextWidth2(C2.EXT_HEADER, C2.CHAR_W_EXT));
8295
8297
  for (const ep of uc.extensionPoints) {
8296
- widest = Math.max(widest, estimateTextWidth(ep, C2.CHAR_W_EXT) + 16);
8298
+ widest = Math.max(widest, estimateTextWidth2(ep, C2.CHAR_W_EXT) + 16);
8297
8299
  }
8298
8300
  }
8299
8301
  const rx = Math.max(C2.MIN_RX, widest / 2 * Math.SQRT2 + C2.ELLIPSE_PAD_X);
@@ -8526,7 +8528,7 @@ function layoutUsecase(ast) {
8526
8528
  const y = cy - h / 2;
8527
8529
  const anchorX = side === "left" ? x + w : x;
8528
8530
  const anchorY = isRect ? cy : cy - 6;
8529
- const box = {
8531
+ const box2 = {
8530
8532
  actor: a,
8531
8533
  x,
8532
8534
  y,
@@ -8536,8 +8538,8 @@ function layoutUsecase(ast) {
8536
8538
  anchorX,
8537
8539
  anchorY
8538
8540
  };
8539
- actorBoxes.push(box);
8540
- actorById2.set(a.id, box);
8541
+ actorBoxes.push(box2);
8542
+ actorById2.set(a.id, box2);
8541
8543
  cy += C2.ACTOR_PITCH;
8542
8544
  }
8543
8545
  }
@@ -8549,7 +8551,7 @@ function layoutUsecase(ast) {
8549
8551
  let right = b.x + b.width;
8550
8552
  const isRect = b.actor.kind === "external" || b.actor.kind === "system";
8551
8553
  if (!isRect) {
8552
- const labelW = estimateTextWidth(b.actor.name, 6.4);
8554
+ const labelW = estimateTextWidth2(b.actor.name, 6.4);
8553
8555
  const cx = b.x + b.width / 2;
8554
8556
  left = Math.min(left, cx - labelW / 2);
8555
8557
  right = Math.max(right, cx + labelW / 2);
@@ -10124,7 +10126,7 @@ function layoutNetwork(ast, schedule) {
10124
10126
  y = cursor;
10125
10127
  cursor += C2.BOX_H + C2.V_GAP;
10126
10128
  }
10127
- const box = {
10129
+ const box2 = {
10128
10130
  id,
10129
10131
  task: t,
10130
10132
  computed,
@@ -10135,8 +10137,8 @@ function layoutNetwork(ast, schedule) {
10135
10137
  milestone: t.milestone,
10136
10138
  rank: r6
10137
10139
  };
10138
- boxes.push(box);
10139
- boxById.set(id, box);
10140
+ boxes.push(box2);
10141
+ boxById.set(id, box2);
10140
10142
  }
10141
10143
  }
10142
10144
  const edges = [];
@@ -10343,7 +10345,7 @@ function layoutSwimlane2(ast, schedule) {
10343
10345
  const w = t.milestone ? C2.MS_W : C2.BOX_W;
10344
10346
  const x = colX(r6) + (C2.BOX_W - w) / 2;
10345
10347
  const y = y0 + idx * (C2.BOX_H + C2.V_GAP);
10346
- const box = {
10348
+ const box2 = {
10347
10349
  id,
10348
10350
  task: t,
10349
10351
  computed: schedule.computed.get(id),
@@ -10354,8 +10356,8 @@ function layoutSwimlane2(ast, schedule) {
10354
10356
  milestone: t.milestone,
10355
10357
  rank: r6
10356
10358
  };
10357
- boxes.push(box);
10358
- boxById.set(id, box);
10359
+ boxes.push(box2);
10360
+ boxById.set(id, box2);
10359
10361
  });
10360
10362
  }
10361
10363
  }
@@ -10425,7 +10427,7 @@ function layoutTimescaled(ast, schedule) {
10425
10427
  const iv = intervalById.get(t.id);
10426
10428
  const lane = laneOf.get(t.id);
10427
10429
  const y = topPad + lane * (C2.TS_BOX_H + C2.TS_LANE_GAP);
10428
- const box = {
10430
+ const box2 = {
10429
10431
  id: t.id,
10430
10432
  task: t,
10431
10433
  computed: schedule.computed.get(t.id),
@@ -10436,8 +10438,8 @@ function layoutTimescaled(ast, schedule) {
10436
10438
  milestone: t.milestone,
10437
10439
  rank: 0
10438
10440
  };
10439
- boxes.push(box);
10440
- boxById.set(t.id, box);
10441
+ boxes.push(box2);
10442
+ boxById.set(t.id, box2);
10441
10443
  }
10442
10444
  const edges = [];
10443
10445
  for (const t of ast.tasks) {
@@ -13514,6 +13516,7 @@ var DET = "sx-net-detail";
13514
13516
  var GLY = "sx-net-glyph";
13515
13517
  var GLYL = "sx-net-glyph-line";
13516
13518
  var ITX = "sx-net-icontext";
13519
+ var ITAG = "sx-net-icontag";
13517
13520
  var CLOUD = "sx-net-cloud-body";
13518
13521
  var CTX = "sx-net-cloudtext";
13519
13522
  var r2 = (n) => Math.round(n * 100) / 100;
@@ -13557,7 +13560,7 @@ function switchBox(b, glyph) {
13557
13560
  }
13558
13561
  function poeSwitch(b) {
13559
13562
  const cx = b.x + b.w / 2;
13560
- return group({}, [switchBox(b, "straight"), text({ class: ITX, x: r2(cx), y: r2(b.y + b.h * 0.78 + 8), "text-anchor": "middle" }, "PoE")]);
13563
+ return group({}, [switchBox(b, "straight"), text({ class: ITAG, x: r2(cx), y: r2(b.y + b.h * 0.78 + 8), "text-anchor": "middle" }, "PoE")]);
13561
13564
  }
13562
13565
  function firewall(b) {
13563
13566
  const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
@@ -13612,7 +13615,7 @@ function serverFarm(b, d) {
13612
13615
  for (let i = 2; i >= 0; i--) {
13613
13616
  parts.push(server({ x: b.x + i * off, y: b.y - i * off * 0.5, w: b.w - 2 * off, h: b.h - off }));
13614
13617
  }
13615
- if (d.count) parts.push(text({ class: ITX, x: r2(b.x + b.w - 6), y: r2(b.y + b.h - 2), "text-anchor": "end" }, `\xD7${d.count}`));
13618
+ if (d.count) parts.push(text({ class: ITAG, x: r2(b.x + b.w - 6), y: r2(b.y + b.h - 2), "text-anchor": "end" }, `\xD7${d.count}`));
13616
13619
  return group({}, parts);
13617
13620
  }
13618
13621
  function pc(b) {
@@ -13750,9 +13753,9 @@ function drawDeviceIcon(d, b) {
13750
13753
  case "router":
13751
13754
  return router(b);
13752
13755
  case "gateway":
13753
- return group({}, [router(b), text({ class: ITX, x: r2(b.x + b.w / 2), y: r2(b.y + b.h - 1), "text-anchor": "middle" }, "GW")]);
13756
+ return group({}, [router(b), text({ class: ITAG, x: r2(b.x + b.w / 2), y: r2(b.y + b.h - 1), "text-anchor": "middle" }, "GW")]);
13754
13757
  case "vpngw":
13755
- return group({}, [router(b), text({ class: ITX, x: r2(b.x + b.w / 2), y: r2(b.y + b.h - 1), "text-anchor": "middle" }, "VPN")]);
13758
+ return group({}, [router(b), text({ class: ITAG, x: r2(b.x + b.w / 2), y: r2(b.y + b.h - 1), "text-anchor": "middle" }, "VPN")]);
13756
13759
  case "switch":
13757
13760
  return switchBox(b, "straight");
13758
13761
  case "l3switch":
@@ -13861,26 +13864,26 @@ function labelText(d) {
13861
13864
  function deviceFootprint(d) {
13862
13865
  return iconSize(d.kind);
13863
13866
  }
13864
- function effBox(box) {
13865
- const labelW = Math.max(box.w, labelText(box.device).length * NET_CONST.CHAR_W + 6);
13867
+ function effBox(box2) {
13868
+ const labelW = Math.max(box2.w, labelText(box2.device).length * NET_CONST.CHAR_W + 6);
13866
13869
  const half = labelW / 2;
13867
13870
  return {
13868
- left: box.cx - half,
13869
- top: box.y,
13870
- right: box.cx + half,
13871
- bottom: box.y + box.h + labelExtra(box.device)
13872
- };
13873
- }
13874
- function edgePoint(box, tx, ty) {
13875
- const dx = tx - box.cx;
13876
- const dy = ty - box.cy;
13877
- if (dx === 0 && dy === 0) return { x: box.cx, y: box.cy };
13878
- const hw = box.w / 2;
13879
- const hh = box.h / 2;
13871
+ left: box2.cx - half,
13872
+ top: box2.y,
13873
+ right: box2.cx + half,
13874
+ bottom: box2.y + box2.h + labelExtra(box2.device)
13875
+ };
13876
+ }
13877
+ function edgePoint(box2, tx, ty) {
13878
+ const dx = tx - box2.cx;
13879
+ const dy = ty - box2.cy;
13880
+ if (dx === 0 && dy === 0) return { x: box2.cx, y: box2.cy };
13881
+ const hw = box2.w / 2;
13882
+ const hh = box2.h / 2;
13880
13883
  const sx = dx !== 0 ? hw / Math.abs(dx) : Infinity;
13881
13884
  const sy = dy !== 0 ? hh / Math.abs(dy) : Infinity;
13882
13885
  const s = Math.min(sx, sy);
13883
- return { x: box.cx + dx * s, y: box.cy + dy * s };
13886
+ return { x: box2.cx + dx * s, y: box2.cy + dy * s };
13884
13887
  }
13885
13888
  function placeBanded(ast, ranks) {
13886
13889
  const lr = ast.direction === "lr";
@@ -14232,16 +14235,54 @@ function layoutNetwork2(ast) {
14232
14235
  depth: gb.depth
14233
14236
  });
14234
14237
  }
14238
+ const labelClearsDevices = (x, y, halfW) => {
14239
+ const margin = 6;
14240
+ for (const b of boxes) {
14241
+ const e = effBox(b);
14242
+ if (x + halfW >= e.left - margin && x - halfW <= e.right + margin && y + 5 >= e.top - margin && y - 5 <= e.bottom + margin) {
14243
+ return false;
14244
+ }
14245
+ }
14246
+ return true;
14247
+ };
14248
+ const placedLabels = [];
14249
+ const labelClearsPlaced = (x, y) => placedLabels.every((p) => Math.abs(p.x - x) > 90 || Math.abs(p.y - y) > 16);
14235
14250
  const linkGeoms = links.map((link) => {
14236
14251
  const a = boxById.get(link.from);
14237
14252
  const b = boxById.get(link.to);
14238
14253
  const p1 = edgePoint(a, b.cx, b.cy);
14239
14254
  const p2 = edgePoint(b, a.cx, a.cy);
14255
+ const hasAnnotation = Boolean(
14256
+ link.label || link.speed || link.mode || link.vlans && link.vlans.length
14257
+ );
14258
+ let labelT = 0.5;
14259
+ if (hasAnnotation) {
14260
+ const annLen = (link.mode ? link.mode.length + 3 : 0) + (link.vlans && link.vlans.length ? 6 + link.vlans.join(",").length + 3 : 0) + (link.speed ? link.speed.length + 3 : 0) + (link.label ? link.label.length : 0);
14261
+ const halfW = annLen * 5.4 / 2;
14262
+ const candidates = [0.5, 0.38, 0.62, 0.28, 0.72, 0.2, 0.8, 0.14, 0.86];
14263
+ const at = (t) => ({
14264
+ x: p1.x + (p2.x - p1.x) * t,
14265
+ y: p1.y + (p2.y - p1.y) * t
14266
+ });
14267
+ const strict = candidates.find((t) => {
14268
+ const p = at(t);
14269
+ return labelClearsDevices(p.x, p.y, halfW) && labelClearsPlaced(p.x, p.y);
14270
+ });
14271
+ const relaxed = strict ?? candidates.find((t) => {
14272
+ const p = at(t);
14273
+ return labelClearsDevices(p.x, p.y, halfW);
14274
+ });
14275
+ labelT = relaxed ?? 0.5;
14276
+ placedLabels.push({
14277
+ x: p1.x + (p2.x - p1.x) * labelT,
14278
+ y: p1.y + (p2.y - p1.y) * labelT
14279
+ });
14280
+ }
14240
14281
  return {
14241
14282
  link,
14242
14283
  points: [p1, p2],
14243
- labelX: (p1.x + p2.x) / 2,
14244
- labelY: (p1.y + p2.y) / 2
14284
+ labelX: p1.x + (p2.x - p1.x) * labelT,
14285
+ labelY: p1.y + (p2.y - p1.y) * labelT
14245
14286
  };
14246
14287
  });
14247
14288
  const width = maxX - minX + 2 * NET_CONST.PAD;
@@ -14269,11 +14310,12 @@ function buildCss8(t) {
14269
14310
  .sx-net-glyph { fill: ${t.deviceAccent}; stroke: none; }
14270
14311
  .sx-net-glyph-line { fill: none; stroke: ${t.deviceAccent}; stroke-width: 1.4; }
14271
14312
  .sx-net-icontext { font: 700 8px sans-serif; fill: ${t.deviceAccent}; }
14313
+ .sx-net-icontag { font: 700 8px sans-serif; fill: ${t.subLabel}; paint-order: stroke; stroke: ${t.bg}; stroke-width: 2.5px; stroke-linejoin: round; }
14272
14314
  .sx-net-cloud-body { fill: ${t.cloudFill}; stroke: ${t.cloudStroke}; stroke-width: 2; }
14273
14315
  .sx-net-cloudtext { font: 600 13px sans-serif; fill: ${t.text}; }
14274
14316
  .sx-net-bus { stroke: ${t.deviceStroke}; stroke-width: 4; stroke-linecap: round; }
14275
- .sx-net-label { font: 12px sans-serif; fill: ${t.label}; }
14276
- .sx-net-sublabel { font: 10px sans-serif; fill: ${t.subLabel}; }
14317
+ .sx-net-label { font: 12px sans-serif; fill: ${t.label}; paint-order: stroke; stroke: ${t.bg}; stroke-width: 3px; stroke-linejoin: round; }
14318
+ .sx-net-sublabel { font: 10px sans-serif; fill: ${t.subLabel}; paint-order: stroke; stroke: ${t.bg}; stroke-width: 3px; stroke-linejoin: round; }
14277
14319
  .sx-net-link { fill: none; stroke-width: 2; }
14278
14320
  .sx-net-link-wireless, .sx-net-link-vpn { stroke-dasharray: 5 4; }
14279
14321
  .sx-net-link-lag { stroke-width: 3; }
@@ -16208,7 +16250,7 @@ function renderUmlClassLayout(layout, config) {
16208
16250
  .sx-umlclass-edge-label { fill: ${theme.edgeLabel}; font-size: ${FONT_SIZE.small + 1}px; font-weight: 500; }
16209
16251
  .sx-umlclass-edge-name { fill: ${theme.edgeLabel}; font-size: ${FONT_SIZE.label}px; font-style: italic; }
16210
16252
  .sx-umlclass-edge-name-halo { fill: ${theme.bg}; stroke: ${theme.bg}; stroke-width: ${UMLCLASS_CONST.EDGE_LABEL_HALO}; stroke-linejoin: round; }
16211
- .sx-umlclass-title { fill: ${theme.nameText}; font-size: ${FONT_SIZE.title}px; font-weight: 600; }
16253
+ .sx-umlclass-title { fill: ${theme.nameText}; font-size: ${FONT_SIZE.title}px; font-weight: 700; }
16212
16254
  .sx-umlclass-package { fill: ${theme.packageFill}; stroke: ${theme.packageStroke}; stroke-width: 1; }
16213
16255
  .sx-umlclass-package-label { fill: ${theme.packageLabel}; font-size: ${FONT_SIZE.label}px; font-weight: 600; }
16214
16256
  `.trim()
@@ -17516,7 +17558,7 @@ function layoutFaultTree(ast) {
17516
17558
  bump(g.cx + g.width / 2, g.cy + g.height / 2);
17517
17559
  if (g.cond) bump(g.cond.x + g.cond.w / 2, g.cond.y + g.cond.h / 2);
17518
17560
  }
17519
- for (const box of cutSetBoxes) bump(box.x + box.width, box.y + box.height);
17561
+ for (const box2 of cutSetBoxes) bump(box2.x + box2.width, box2.y + box2.height);
17520
17562
  for (const t of transfers) bump(t.x + 22, t.y + 30);
17521
17563
  if (ast.title) bump(C2.CANVAS_PAD + ast.title.length * 8.5, 0);
17522
17564
  if (ast.analysis.probability) {
@@ -17592,23 +17634,23 @@ function renderFaultTreeLayout(layout, config) {
17592
17634
  const inner = [];
17593
17635
  if (ast.title) {
17594
17636
  inner.push(
17595
- text({ x: FAULTTREE_CONST.CANVAS_PAD, y: 22, class: "sx-ft-title", "font-family": fontFamily }, ast.title)
17637
+ text({ x: layout.width / 2, y: 22, class: "sx-ft-title", "font-family": fontFamily, "text-anchor": "middle" }, ast.title)
17596
17638
  );
17597
17639
  }
17598
- for (const box of layout.cutSetBoxes) {
17640
+ for (const box2 of layout.cutSetBoxes) {
17599
17641
  inner.push(
17600
17642
  rect({
17601
- x: box.x,
17602
- y: box.y,
17603
- width: box.width,
17604
- height: box.height,
17643
+ x: box2.x,
17644
+ y: box2.y,
17645
+ width: box2.width,
17646
+ height: box2.height,
17605
17647
  rx: 6,
17606
17648
  class: "sx-ft-cutset",
17607
- "data-cutset": box.cutSet.events.join(","),
17608
- "data-cutset-index": String(box.index),
17609
- "data-order": String(box.cutSet.order),
17610
- ...box.cutSet.isSpof ? { "data-spof": "true" } : {},
17611
- ...box.cutSet.prob !== void 0 ? { "data-cutset-prob": String(box.cutSet.prob) } : {}
17649
+ "data-cutset": box2.cutSet.events.join(","),
17650
+ "data-cutset-index": String(box2.index),
17651
+ "data-order": String(box2.cutSet.order),
17652
+ ...box2.cutSet.isSpof ? { "data-spof": "true" } : {},
17653
+ ...box2.cutSet.prob !== void 0 ? { "data-cutset-prob": String(box2.cutSet.prob) } : {}
17612
17654
  })
17613
17655
  );
17614
17656
  }
@@ -18289,7 +18331,7 @@ function renderBowtieLayout(layout, config) {
18289
18331
  ];
18290
18332
  const inner = [];
18291
18333
  if (ast.title) {
18292
- inner.push(text({ x: BOWTIE_CONST.PAGE_PAD, y: BOWTIE_CONST.PAGE_PAD + 6, class: "sx-bowtie-title", "font-family": fontFamily }, ast.title));
18334
+ inner.push(text({ x: layout.width / 2, y: BOWTIE_CONST.PAGE_PAD + 6, class: "sx-bowtie-title", "font-family": fontFamily, "text-anchor": "middle" }, ast.title));
18293
18335
  }
18294
18336
  if (layout.hazardTie) {
18295
18337
  inner.push(line({ x1: layout.hazardTie.x, y1: layout.hazardTie.y1, x2: layout.hazardTie.x, y2: layout.hazardTie.y2, class: "sx-bowtie-line" }));
@@ -18917,7 +18959,7 @@ function renderEventTreeLayout(layout, config) {
18917
18959
  ];
18918
18960
  const inner = [];
18919
18961
  if (ast.title) {
18920
- inner.push(text({ x: EVENTTREE_CONST.CANVAS_PAD, y: EVENTTREE_CONST.CANVAS_PAD + 4, class: "sx-et-title", "font-family": fontFamily }, ast.title));
18962
+ inner.push(text({ x: layout.width / 2, y: EVENTTREE_CONST.CANVAS_PAD + 4, class: "sx-et-title", "font-family": fontFamily, "text-anchor": "middle" }, ast.title));
18921
18963
  }
18922
18964
  for (const h of layout.headers) {
18923
18965
  inner.push(
@@ -20111,7 +20153,7 @@ function renderHeaderBlock(layout) {
20111
20153
  const x0 = FMEA_CONST.CANVAS_PAD;
20112
20154
  let y = FMEA_CONST.CANVAS_PAD + 14;
20113
20155
  if (ast.title) {
20114
- out.push(text({ x: x0, y, class: "sx-fmea-title" }, ast.title));
20156
+ out.push(text({ x: layout.width / 2, y, class: "sx-fmea-title", "text-anchor": "middle" }, ast.title));
20115
20157
  y += FMEA_CONST.TITLE_H - 8;
20116
20158
  }
20117
20159
  const metaEntries = Object.entries(ast.metadata);
@@ -20894,7 +20936,7 @@ function renderCausalLoopLayout(layout, config) {
20894
20936
  if (ast.title) {
20895
20937
  inner.push(
20896
20938
  text(
20897
- { x: CAUSALLOOP_CONST.CANVAS_PAD, y: 20, class: "sx-cld-title", "font-family": fontFamily },
20939
+ { x: layout.width / 2, y: 20, class: "sx-cld-title", "font-family": fontFamily, "text-anchor": "middle" },
20898
20940
  ast.title
20899
20941
  )
20900
20942
  );
@@ -21693,16 +21735,16 @@ function layoutMarkov(ast) {
21693
21735
  const tag = classOf?.[st.id];
21694
21736
  const isAbsorbing = tag === "absorbing";
21695
21737
  const pi = piMap?.[st.id] ?? perClassPi[st.id];
21696
- const box = {
21738
+ const box2 = {
21697
21739
  state: st,
21698
21740
  cx: c.x,
21699
21741
  cy: c.y,
21700
21742
  r: C2.STATE_R,
21701
21743
  isAbsorbing
21702
21744
  };
21703
- if (tag) box.classTag = tag;
21704
- if (pi !== void 0) box.pi = pi;
21705
- return box;
21745
+ if (tag) box2.classTag = tag;
21746
+ if (pi !== void 0) box2.pi = pi;
21747
+ return box2;
21706
21748
  });
21707
21749
  const arcKey = (a, b) => `${a}\0${b}`;
21708
21750
  const present = /* @__PURE__ */ new Set();
@@ -22721,7 +22763,7 @@ function renderGitGraphLayout(layout, config) {
22721
22763
  const width = layout.width + pad * 2;
22722
22764
  const height = layout.height + pad * 2;
22723
22765
  const a11y = ast.title ?? "Git commit graph";
22724
- const styleBlock = buildStyle(pal, ast.showBranches);
22766
+ const styleBlock = buildStyle2(pal, ast.showBranches);
22725
22767
  const children = [
22726
22768
  title(a11y),
22727
22769
  desc(summarise6(layout)),
@@ -22937,7 +22979,7 @@ function renderCommitLabel(id, x, y, rotate, fontFamily) {
22937
22979
  id
22938
22980
  );
22939
22981
  }
22940
- function buildStyle(pal, showBranches) {
22982
+ function buildStyle2(pal, showBranches) {
22941
22983
  const laneStyle = showBranches ? `
22942
22984
  .sx-gg-lane { stroke-width: ${STROKE_WIDTH.thick}; stroke-linecap: round; opacity: 0.9; }
22943
22985
  .sx-gg-pill { stroke: none; }
@@ -23559,7 +23601,8 @@ function layoutEpc(ast) {
23559
23601
  const shift = (maxCrossExtent - rankWidths[r6]) / 2 + EPC_CONST.CANVAS_PAD;
23560
23602
  for (const id of ranks[r6]) crossPos.set(id, crossPos.get(id) + shift);
23561
23603
  }
23562
- const crossExtent = maxCrossExtent + EPC_CONST.CANVAS_PAD * 2 + EPC_CONST.BACK_MARGIN;
23604
+ const backMargin = backSet.size > 0 ? EPC_CONST.BACK_MARGIN : 0;
23605
+ const crossExtent = maxCrossExtent + EPC_CONST.CANVAS_PAD * 2 + backMargin;
23563
23606
  const flaggedIds = /* @__PURE__ */ new Set();
23564
23607
  for (const v of analysis.violations) {
23565
23608
  if (v.severity === "error") for (const id of v.nodes) flaggedIds.add(id);
@@ -23840,7 +23883,7 @@ function renderEpcLayout(layout, config) {
23840
23883
  ];
23841
23884
  const inner = [];
23842
23885
  if (ast.title) {
23843
- inner.push(text({ x: EPC_CONST.CANVAS_PAD, y: 22, class: "sx-epc-title", "font-family": fontFamily }, ast.title));
23886
+ inner.push(text({ x: layout.width / 2, y: 22, class: "sx-epc-title", "font-family": fontFamily, "text-anchor": "middle" }, ast.title));
23844
23887
  }
23845
23888
  for (const e of layout.edges) inner.push(renderEdge6(e));
23846
23889
  for (const n of layout.nodes) inner.push(renderNode3(n));
@@ -24120,13 +24163,13 @@ function parseFunction2(ast, rest, lineNo) {
24120
24163
  ast.warnings.push(`Line ${lineNo}: box "${id}" redeclared \u2014 keeping first declaration.`);
24121
24164
  return;
24122
24165
  }
24123
- const box = {
24166
+ const box2 = {
24124
24167
  id,
24125
24168
  name,
24126
24169
  // analysis assigns the real number; explicit wins if given.
24127
24170
  number: explicitNumber ?? 0
24128
24171
  };
24129
- ast.boxes.push(box);
24172
+ ast.boxes.push(box2);
24130
24173
  }
24131
24174
  function parseRoleArrow(ast, role, rest, lineNo) {
24132
24175
  const idM = /^([A-Za-z_]\w*)/.exec(rest);
@@ -24398,8 +24441,8 @@ function layoutIdef0(astIn) {
24398
24441
  const C2 = IDEF0_CONST;
24399
24442
  const ox = C2.MARGIN;
24400
24443
  const oy = C2.MARGIN + C2.TITLE_H;
24401
- const boxes = ast.boxes.map((box, idx) => ({
24402
- box,
24444
+ const boxes = ast.boxes.map((box2, idx) => ({
24445
+ box: box2,
24403
24446
  x: ox + idx * C2.STEP_X,
24404
24447
  y: oy + idx * C2.STEP_Y,
24405
24448
  width: C2.BOX_W,
@@ -24578,7 +24621,7 @@ function renderIdef0Layout(layout, config) {
24578
24621
  const inner = [];
24579
24622
  if (ast.title) {
24580
24623
  inner.push(
24581
- text({ x: IDEF0_CONST.MARGIN, y: 24, class: "sx-idef0-title", "font-family": fontFamily }, ast.title)
24624
+ text({ x: layout.width / 2, y: 24, class: "sx-idef0-title", "font-family": fontFamily, "text-anchor": "middle" }, ast.title)
24582
24625
  );
24583
24626
  }
24584
24627
  for (const a of layout.arrows) {
@@ -25399,7 +25442,7 @@ function renderThreatModelLayout(layout, config) {
25399
25442
  if (layout.ast.title) {
25400
25443
  inner.push(
25401
25444
  text(
25402
- { x: TM_CONST.PAD, y: 24, class: "sx-tm-title", "font-family": fontFamily },
25445
+ { x: layout.width / 2, y: 24, class: "sx-tm-title", "font-family": fontFamily, "text-anchor": "middle" },
25403
25446
  layout.ast.title
25404
25447
  )
25405
25448
  );
@@ -28317,7 +28360,8 @@ function layoutSipoc(ast) {
28317
28360
  var QFD_CELL = 46;
28318
28361
  var QFD_WHAT_LABEL_W = 190;
28319
28362
  var QFD_WEIGHT_W = 46;
28320
- var QFD_HOW_LABEL_H = 130;
28363
+ var QFD_HOW_FONT = 11.5;
28364
+ var QFD_HOW_ANGLE_SIN = Math.sin(60 * Math.PI / 180);
28321
28365
  var QFD_FOOTER_H = 64;
28322
28366
  var QFD_PAD = 24;
28323
28367
  function layoutQfd(ast) {
@@ -28329,7 +28373,11 @@ function layoutQfd(ast) {
28329
28373
  const roofH = Math.ceil(cellW / 2 * cols) + 8;
28330
28374
  const whatLabelW = QFD_WHAT_LABEL_W;
28331
28375
  const weightW = QFD_WEIGHT_W;
28332
- const howLabelH = QFD_HOW_LABEL_H;
28376
+ const maxHowW = Math.max(
28377
+ 0,
28378
+ ...(ast.qfd?.hows ?? []).map((h) => estimateTextWidth(h.label, QFD_HOW_FONT))
28379
+ );
28380
+ const howLabelH = Math.max(48, Math.min(220, Math.ceil(maxHowW * QFD_HOW_ANGLE_SIN) + 26));
28333
28381
  const footerH = QFD_FOOTER_H;
28334
28382
  const gridX0 = QFD_PAD + whatLabelW + weightW;
28335
28383
  const gridY0 = QFD_PAD + titleH + roofH + howLabelH;
@@ -28466,76 +28514,78 @@ var HEAT_RAMP = [
28466
28514
  "#ef4444",
28467
28515
  "#b91c1c"
28468
28516
  ];
28469
- var CSS2 = `
28517
+ function buildMatrixCss(t) {
28518
+ return `
28470
28519
  .sx-matrix { font-family: system-ui, -apple-system, "Segoe UI", sans-serif; }
28471
- .sx-matrix-title { font: 600 16px sans-serif; fill: #111; }
28472
- .sx-matrix-grid { stroke: #e5e7eb; stroke-width: 1; fill: none; }
28473
- .sx-matrix-mid { stroke: #9ca3af; stroke-width: 1.2; stroke-dasharray: 4 3; fill: none; }
28474
- .sx-matrix-plot-border { stroke: #374151; stroke-width: 1.2; fill: none; }
28475
- .sx-matrix-axis-label { font: 500 12px sans-serif; fill: #374151; }
28476
- .sx-matrix-axis-end { font: 500 11px sans-serif; fill: #6b7280; }
28477
- .sx-matrix-quad-annot { font: 600 13px sans-serif; fill: #475569; opacity: 0.75; }
28478
- .sx-matrix-quad-desc { font: 400 10.5px sans-serif; fill: #64748b; opacity: 0.85; }
28479
- .sx-matrix-corr-header { font: 600 11.5px sans-serif; fill: #1f2937; text-anchor: middle; }
28480
- .sx-matrix-corr-rowlabel { font: 500 11.5px sans-serif; fill: #1f2937; text-anchor: end; dominant-baseline: central; }
28481
- .sx-matrix-corr-margin { font: 500 11px sans-serif; fill: #374151; text-anchor: middle; dominant-baseline: central; }
28482
- .sx-matrix-corr-margin-best { font: 700 11.5px sans-serif; fill: #111; text-anchor: middle; dominant-baseline: central; }
28483
- .sx-matrix-corr-grid { stroke: #d1d5db; stroke-width: 0.8; fill: none; }
28484
- .sx-matrix-corr-rowbg-a { fill: #f0fdf4; }
28485
- .sx-matrix-corr-rowbg-b { fill: #fff; }
28486
- .sx-matrix-cell-label { font: 500 12px sans-serif; fill: #1f2937; text-anchor: middle; }
28487
- .sx-matrix-cell-title { font: 600 13px sans-serif; fill: #111827; }
28488
- .sx-matrix-cell-subtitle { font: 400 11px sans-serif; fill: #6b7280; }
28489
- .sx-matrix-cell-item { font: 500 12px sans-serif; fill: #1f2937; }
28490
- .sx-matrix-cell-value { font: 600 18px sans-serif; fill: #111; text-anchor: middle; }
28520
+ .sx-matrix-title { font: 700 16px sans-serif; fill: ${t.inkStrong}; }
28521
+ .sx-matrix-grid { stroke: ${t.gridFaint}; stroke-width: 1; fill: none; }
28522
+ .sx-matrix-mid { stroke: ${t.gridStrong}; stroke-width: 1.2; stroke-dasharray: 4 3; fill: none; }
28523
+ .sx-matrix-plot-border { stroke: ${t.inkMuted}; stroke-width: 1.2; fill: none; }
28524
+ .sx-matrix-axis-label { font: 500 12px sans-serif; fill: ${t.inkMuted}; }
28525
+ .sx-matrix-axis-end { font: 500 11px sans-serif; fill: ${t.inkFaint}; }
28526
+ .sx-matrix-quad-annot { font: 600 13px sans-serif; fill: ${t.inkMuted}; opacity: 0.75; }
28527
+ .sx-matrix-quad-desc { font: 400 10.5px sans-serif; fill: ${t.inkFaint}; opacity: 0.85; }
28528
+ .sx-matrix-corr-header { font: 600 11.5px sans-serif; fill: ${t.ink}; text-anchor: middle; }
28529
+ .sx-matrix-corr-rowlabel { font: 500 11.5px sans-serif; fill: ${t.ink}; text-anchor: end; dominant-baseline: central; }
28530
+ .sx-matrix-corr-margin { font: 500 11px sans-serif; fill: ${t.inkMuted}; text-anchor: middle; dominant-baseline: central; }
28531
+ .sx-matrix-corr-margin-best { font: 700 11.5px sans-serif; fill: ${t.inkStrong}; text-anchor: middle; dominant-baseline: central; }
28532
+ .sx-matrix-corr-grid { stroke: ${t.grid}; stroke-width: 0.8; fill: none; }
28533
+ .sx-matrix-corr-rowbg-a { fill: ${t.surfaceTint}; }
28534
+ .sx-matrix-corr-rowbg-b { fill: ${t.surface}; }
28535
+ .sx-matrix-cell-label { font: 500 12px sans-serif; fill: ${t.ink}; text-anchor: middle; }
28536
+ .sx-matrix-cell-title { font: 600 13px sans-serif; fill: ${t.inkStrong}; }
28537
+ .sx-matrix-cell-subtitle { font: 400 11px sans-serif; fill: ${t.inkFaint}; }
28538
+ .sx-matrix-cell-item { font: 500 12px sans-serif; fill: ${t.ink}; }
28539
+ .sx-matrix-cell-value { font: 600 18px sans-serif; fill: ${t.inkStrong}; text-anchor: middle; }
28491
28540
  .sx-matrix-bubble { stroke-width: 1.5; }
28492
- .sx-matrix-label { font: 500 11px sans-serif; fill: #111827; text-anchor: middle; dominant-baseline: central; pointer-events: none; }
28493
- .sx-matrix-leader { stroke: #94a3b8; stroke-width: 0.6; opacity: 0.7; fill: none; }
28494
- .sx-matrix-legend-text { font: 500 11px sans-serif; fill: #374151; }
28495
- .sx-matrix-offchart { fill: #ea580c; }
28496
- .sx-sipoc-header { font: 700 13px sans-serif; fill: #fff; text-anchor: middle; dominant-baseline: central; }
28497
- .sx-sipoc-headbox { stroke: #fff; stroke-width: 1; }
28498
- .sx-sipoc-cell { fill: #fff; stroke: #cbd5e1; stroke-width: 1; }
28499
- .sx-sipoc-cell-alt { fill: #f8fafc; stroke: #cbd5e1; stroke-width: 1; }
28500
- .sx-sipoc-process { fill: #eff6ff; stroke: #cbd5e1; stroke-width: 1; }
28501
- .sx-sipoc-item { font: 500 12px sans-serif; fill: #1f2937; text-anchor: middle; dominant-baseline: central; }
28502
- .sx-sipoc-step { font: 600 12px sans-serif; fill: #1e3a8a; text-anchor: middle; dominant-baseline: central; }
28503
- .sx-qfd-grid { stroke: #cbd5e1; stroke-width: 1; fill: none; }
28504
- .sx-qfd-cellbg { fill: #fff; }
28505
- .sx-qfd-cellbg-alt { fill: #f8fafc; }
28506
- .sx-qfd-what { font: 500 12px sans-serif; fill: #1f2937; text-anchor: end; dominant-baseline: central; }
28507
- .sx-qfd-how { font: 500 11.5px sans-serif; fill: #1f2937; }
28508
- .sx-qfd-weight { font: 600 12px sans-serif; fill: #111; text-anchor: middle; dominant-baseline: central; }
28509
- .sx-qfd-weight-head { font: 600 10px sans-serif; fill: #475569; text-anchor: middle; dominant-baseline: central; }
28510
- .sx-qfd-rel-strong { fill: #2563eb; }
28511
- .sx-qfd-rel-medium { fill: #93c5fd; stroke: #2563eb; stroke-width: 1.4; }
28512
- .sx-qfd-rel-weak { fill: none; stroke: #2563eb; stroke-width: 1.4; }
28513
- .sx-qfd-roof-cell { fill: #f8fafc; stroke: #94a3b8; stroke-width: 0.8; }
28514
- .sx-qfd-roof-cell-filled { fill: #eef2ff; stroke: #64748b; stroke-width: 0.9; }
28541
+ .sx-matrix-label { font: 500 11px sans-serif; fill: ${t.inkStrong}; text-anchor: middle; dominant-baseline: central; pointer-events: none; }
28542
+ .sx-matrix-leader { stroke: ${t.gridStrong}; stroke-width: 0.6; opacity: 0.7; fill: none; }
28543
+ .sx-matrix-legend-text { font: 500 11px sans-serif; fill: ${t.inkMuted}; }
28544
+ .sx-matrix-offchart { fill: ${t.warnDeep}; }
28545
+ .sx-sipoc-header { font: 700 13px sans-serif; fill: ${t.onHeader}; text-anchor: middle; dominant-baseline: central; }
28546
+ .sx-sipoc-headbox { stroke: ${t.surface}; stroke-width: 1; }
28547
+ .sx-sipoc-cell { fill: ${t.surface}; stroke: ${t.gridMid}; stroke-width: 1; }
28548
+ .sx-sipoc-cell-alt { fill: ${t.surfaceAlt}; stroke: ${t.gridMid}; stroke-width: 1; }
28549
+ .sx-sipoc-process { fill: ${t.accentTint}; stroke: ${t.gridMid}; stroke-width: 1; }
28550
+ .sx-sipoc-item { font: 500 12px sans-serif; fill: ${t.ink}; text-anchor: middle; dominant-baseline: central; }
28551
+ .sx-sipoc-step { font: 600 12px sans-serif; fill: ${t.accentDeep}; text-anchor: middle; dominant-baseline: central; }
28552
+ .sx-qfd-grid { stroke: ${t.gridMid}; stroke-width: 1; fill: none; }
28553
+ .sx-qfd-cellbg { fill: ${t.surface}; }
28554
+ .sx-qfd-cellbg-alt { fill: ${t.surfaceAlt}; }
28555
+ .sx-qfd-what { font: 500 12px sans-serif; fill: ${t.ink}; text-anchor: end; dominant-baseline: central; }
28556
+ .sx-qfd-how { font: 500 11.5px sans-serif; fill: ${t.ink}; }
28557
+ .sx-qfd-weight { font: 600 12px sans-serif; fill: ${t.inkStrong}; text-anchor: middle; dominant-baseline: central; }
28558
+ .sx-qfd-weight-head { font: 600 10px sans-serif; fill: ${t.inkMuted}; text-anchor: middle; dominant-baseline: central; }
28559
+ .sx-qfd-rel-strong { fill: ${t.accent}; }
28560
+ .sx-qfd-rel-medium { fill: ${t.accentSoft}; stroke: ${t.accent}; stroke-width: 1.4; }
28561
+ .sx-qfd-rel-weak { fill: none; stroke: ${t.accent}; stroke-width: 1.4; }
28562
+ .sx-qfd-roof-cell { fill: ${t.surfaceAlt}; stroke: ${t.gridStrong}; stroke-width: 0.8; }
28563
+ .sx-qfd-roof-cell-filled { fill: ${t.roofFilled}; stroke: ${t.inkFaint}; stroke-width: 0.9; }
28515
28564
  .sx-qfd-corr { font: 700 13px sans-serif; text-anchor: middle; dominant-baseline: central; }
28516
- .sx-qfd-corr-strong-pos { fill: #15803d; }
28517
- .sx-qfd-corr-pos { fill: #16a34a; }
28518
- .sx-qfd-corr-neg { fill: #dc2626; }
28519
- .sx-qfd-corr-strong-neg { fill: #b91c1c; }
28520
- .sx-qfd-imp-band { fill: #eff6ff; stroke: #cbd5e1; stroke-width: 1; }
28521
- .sx-qfd-imp-head { font: 600 11px sans-serif; fill: #1e3a8a; text-anchor: end; dominant-baseline: central; }
28522
- .sx-qfd-imp-value { font: 700 13px sans-serif; fill: #1e3a8a; text-anchor: middle; dominant-baseline: central; }
28523
- .sx-qfd-imp-value-top { font: 800 14px sans-serif; fill: #dc2626; text-anchor: middle; dominant-baseline: central; }
28524
- .sx-qfd-dir { font: 700 13px sans-serif; fill: #475569; text-anchor: middle; dominant-baseline: central; }
28525
- .sx-punnett-corner { fill: #f1f5f9; stroke: #94a3b8; stroke-width: 1; }
28526
- .sx-punnett-cornerline { stroke: #cbd5e1; stroke-width: 1; }
28527
- .sx-punnett-corner-p1 { font: 600 11px sans-serif; fill: #1e3a8a; dominant-baseline: hanging; }
28528
- .sx-punnett-corner-p2 { font: 600 11px sans-serif; fill: #1e3a8a; }
28529
- .sx-punnett-header { fill: #e2e8f0; stroke: #94a3b8; stroke-width: 1; }
28530
- .sx-punnett-gamete { font: 700 14px ui-monospace, "SF Mono", Menlo, monospace; fill: #0f172a; text-anchor: middle; dominant-baseline: central; }
28531
- .sx-punnett-cell { stroke: #94a3b8; stroke-width: 1; }
28532
- .sx-punnett-genotype { font: 700 15px ui-monospace, "SF Mono", Menlo, monospace; fill: #0f172a; text-anchor: middle; dominant-baseline: central; }
28533
- .sx-punnett-footer-head { font: 700 13px sans-serif; fill: #111; }
28534
- .sx-punnett-legend { font: 500 12.5px sans-serif; fill: #1f2937; dominant-baseline: central; }
28535
- .sx-punnett-geno-ratio { font: 500 12px sans-serif; fill: #475569; }
28536
- .sx-punnett-hint { font: 500 13px sans-serif; fill: #64748b; text-anchor: middle; }
28565
+ .sx-qfd-corr-strong-pos { fill: ${t.positiveDeep}; }
28566
+ .sx-qfd-corr-pos { fill: ${t.positive}; }
28567
+ .sx-qfd-corr-neg { fill: ${t.negative}; }
28568
+ .sx-qfd-corr-strong-neg { fill: ${t.negativeDeep}; }
28569
+ .sx-qfd-imp-band { fill: ${t.accentTint}; stroke: ${t.gridMid}; stroke-width: 1; }
28570
+ .sx-qfd-imp-head { font: 600 11px sans-serif; fill: ${t.accentDeep}; text-anchor: end; dominant-baseline: central; }
28571
+ .sx-qfd-imp-value { font: 700 13px sans-serif; fill: ${t.accentDeep}; text-anchor: middle; dominant-baseline: central; }
28572
+ .sx-qfd-imp-value-top { font: 800 14px sans-serif; fill: ${t.negative}; text-anchor: middle; dominant-baseline: central; }
28573
+ .sx-qfd-dir { font: 700 13px sans-serif; fill: ${t.inkMuted}; text-anchor: middle; dominant-baseline: central; }
28574
+ .sx-punnett-corner { fill: ${t.cornerFill}; stroke: ${t.gridStrong}; stroke-width: 1; }
28575
+ .sx-punnett-cornerline { stroke: ${t.gridMid}; stroke-width: 1; }
28576
+ .sx-punnett-corner-p1 { font: 600 11px sans-serif; fill: ${t.accentDeep}; dominant-baseline: hanging; }
28577
+ .sx-punnett-corner-p2 { font: 600 11px sans-serif; fill: ${t.accentDeep}; }
28578
+ .sx-punnett-header { fill: ${t.headerFill}; stroke: ${t.gridStrong}; stroke-width: 1; }
28579
+ .sx-punnett-gamete { font: 700 14px ui-monospace, "SF Mono", Menlo, monospace; fill: ${t.inkStrong}; text-anchor: middle; dominant-baseline: central; }
28580
+ .sx-punnett-cell { stroke: ${t.gridStrong}; stroke-width: 1; }
28581
+ .sx-punnett-genotype { font: 700 15px ui-monospace, "SF Mono", Menlo, monospace; fill: ${t.inkStrong}; text-anchor: middle; dominant-baseline: central; }
28582
+ .sx-punnett-footer-head { font: 700 13px sans-serif; fill: ${t.inkStrong}; }
28583
+ .sx-punnett-legend { font: 500 12.5px sans-serif; fill: ${t.ink}; dominant-baseline: central; }
28584
+ .sx-punnett-geno-ratio { font: 500 12px sans-serif; fill: ${t.inkMuted}; }
28585
+ .sx-punnett-hint { font: 500 13px sans-serif; fill: ${t.inkFaint}; text-anchor: middle; }
28537
28586
  `.trim();
28538
- function axisArrow() {
28587
+ }
28588
+ function axisArrow(t) {
28539
28589
  return el(
28540
28590
  "marker",
28541
28591
  {
@@ -28547,7 +28597,7 @@ function axisArrow() {
28547
28597
  markerHeight: 8,
28548
28598
  orient: "auto-start-reverse"
28549
28599
  },
28550
- [el("path", { d: "M0,0 L10,5 L0,10 z", fill: "#374151" })]
28600
+ [el("path", { d: "M0,0 L10,5 L0,10 z", fill: t.border })]
28551
28601
  );
28552
28602
  }
28553
28603
  function bubbleFill(p, categories) {
@@ -29334,7 +29384,8 @@ function wrapToLines(textStr, maxChars, maxLines) {
29334
29384
  kept[maxLines - 1] = kept[maxLines - 1].replace(/\s+\S*$/, "") + "\u2026";
29335
29385
  return kept;
29336
29386
  }
29337
- function renderSipocAST(ast) {
29387
+ function renderSipocAST(ast, config) {
29388
+ const t = resolveMatrixTheme(config?.theme ?? "default");
29338
29389
  const sipoc = ast.sipoc ?? { suppliers: [], inputs: [], process: [], outputs: [], customers: [] };
29339
29390
  const lay = layoutSipoc(ast);
29340
29391
  const nodes = [];
@@ -29416,7 +29467,7 @@ function renderSipocAST(ast) {
29416
29467
  desc(
29417
29468
  `SIPOC scoping table \u2014 ${sipoc.suppliers.length} supplier(s), ${sipoc.inputs.length} input(s), ${sipoc.process.length} process step(s), ${sipoc.outputs.length} output(s), ${sipoc.customers.length} customer(s)`
29418
29469
  ),
29419
- defs([el("style", {}, CSS2)]),
29470
+ defs([el("style", {}, buildMatrixCss(t))]),
29420
29471
  ...nodes
29421
29472
  ]
29422
29473
  );
@@ -29455,7 +29506,8 @@ function renderQfdRelationshipSymbol(strength, cx, cy, r6) {
29455
29506
  class: "sx-qfd-rel-weak"
29456
29507
  });
29457
29508
  }
29458
- function renderQfdAST(ast) {
29509
+ function renderQfdAST(ast, config) {
29510
+ const t = resolveMatrixTheme(config?.theme ?? "default");
29459
29511
  const qfd = ast.qfd ?? { whats: [], hows: [], relationships: [], roof: [], normalize: false };
29460
29512
  const lay = layoutQfd(ast);
29461
29513
  const importance = computeQfdImportance(qfd);
@@ -29645,7 +29697,7 @@ function renderQfdAST(ast) {
29645
29697
  desc(
29646
29698
  `QFD House of Quality \u2014 ${qfd.whats.length} customer requirement(s), ${qfd.hows.length} engineering characteristic(s), ${qfd.relationships.length} relationship(s); technical importance computed per column`
29647
29699
  ),
29648
- defs([el("style", {}, CSS2)]),
29700
+ defs([el("style", {}, buildMatrixCss(t))]),
29649
29701
  ...nodes
29650
29702
  ]
29651
29703
  );
@@ -29659,7 +29711,8 @@ function genotypeText(parent, genes) {
29659
29711
  return a === dom ? a + b : b === dom ? b + a : a + b;
29660
29712
  }).join("");
29661
29713
  }
29662
- function renderPunnettAST(ast) {
29714
+ function renderPunnettAST(ast, config) {
29715
+ const t = resolveMatrixTheme(config?.theme ?? "default");
29663
29716
  const pd = ast.punnett;
29664
29717
  const lay = layoutPunnett(ast);
29665
29718
  const svgWrap = (body, descText2) => svgRoot(
@@ -29675,7 +29728,7 @@ function renderPunnettAST(ast) {
29675
29728
  [
29676
29729
  title(ast.title ? `Punnett square \u2014 ${escapeXml(ast.title)}` : "Punnett square"),
29677
29730
  desc(descText2),
29678
- defs([el("style", {}, CSS2)]),
29731
+ defs([el("style", {}, buildMatrixCss(t))]),
29679
29732
  ...body
29680
29733
  ]
29681
29734
  );
@@ -29770,10 +29823,11 @@ function renderPunnettAST(ast) {
29770
29823
  const descText = `Punnett square \u2014 ${pd.genes.length === 1 ? "monohybrid" : pd.genes.length === 2 ? "dihybrid" : `${pd.genes.length}-gene`} cross ${genotypeText(pd.parent1, pd.genes)} \xD7 ${genotypeText(pd.parent2, pd.genes)}; phenotype ratio ${footer.phenotypeRatio}`;
29771
29824
  return svgWrap(nodes, descText);
29772
29825
  }
29773
- function renderMatrixAST(ast) {
29774
- if (ast.mode === "sipoc") return renderSipocAST(ast);
29775
- if (ast.mode === "qfd") return renderQfdAST(ast);
29776
- if (ast.mode === "punnett") return renderPunnettAST(ast);
29826
+ function renderMatrixAST(ast, config) {
29827
+ if (ast.mode === "sipoc") return renderSipocAST(ast, config);
29828
+ if (ast.mode === "qfd") return renderQfdAST(ast, config);
29829
+ if (ast.mode === "punnett") return renderPunnettAST(ast, config);
29830
+ const t = resolveMatrixTheme(config?.theme ?? "default");
29777
29831
  const lay = layoutMatrix(ast);
29778
29832
  const needsLegendSpace = lay.categories.length > 0 || ast.mode === "correlation";
29779
29833
  const extraWidth = needsLegendSpace && lay.plot.x0 + lay.plot.w + 140 > lay.canvasWidth ? 160 : 0;
@@ -29807,14 +29861,14 @@ function renderMatrixAST(ast) {
29807
29861
  desc(
29808
29862
  `Matrix diagram${ast.template ? ` (${ast.template} template)` : ""}, ${ast.mode} mode, ${ast.points.length} point(s)`
29809
29863
  ),
29810
- defs([el("style", {}, CSS2), axisArrow()]),
29864
+ defs([el("style", {}, buildMatrixCss(t)), axisArrow(t)]),
29811
29865
  ...body
29812
29866
  ]
29813
29867
  );
29814
29868
  }
29815
- function renderMatrix(text2) {
29869
+ function renderMatrix(text2, config) {
29816
29870
  const ast = parseMatrix(text2);
29817
- return renderMatrixAST(ast);
29871
+ return renderMatrixAST(ast, config);
29818
29872
  }
29819
29873
 
29820
29874
  // src/diagrams/matrix/index.ts
@@ -29825,8 +29879,8 @@ var matrix = {
29825
29879
  return first.startsWith("matrix");
29826
29880
  },
29827
29881
  parse: parseMatrix,
29828
- render(text2) {
29829
- return renderMatrix(text2);
29882
+ render(text2, config) {
29883
+ return renderMatrix(text2, config);
29830
29884
  }
29831
29885
  };
29832
29886
 
@@ -32026,7 +32080,7 @@ var WIRE_COLOR_MAP = {
32026
32080
  function buildCss12(t) {
32027
32081
  return `
32028
32082
  .lt-bb { font-family: system-ui, -apple-system, sans-serif; }
32029
- .lt-bb-title { font: 600 16px sans-serif; fill: ${t.text}; }
32083
+ .lt-bb-title { font: 700 16px sans-serif; fill: ${t.text}; }
32030
32084
  .lt-bb-substrate { fill: #e7d8b6; stroke: #b08c4f; stroke-width: 1.5; }
32031
32085
  .lt-bb-rail-pos { fill: #fde2e2; stroke: #dc2626; stroke-width: 0.6; }
32032
32086
  .lt-bb-rail-neg { fill: #dde7fa; stroke: #2563eb; stroke-width: 0.6; }
@@ -32173,7 +32227,7 @@ function renderBreadboardLayout(layout, config) {
32173
32227
  const theme = resolveBaseTheme(config?.theme ?? "default");
32174
32228
  const css = buildCss12(theme);
32175
32229
  const titleStr = layout.ast.title ?? "Breadboard";
32176
- const titleNode = layout.ast.title ? text({ x: BB_CONST.MARGIN, y: 22, class: "lt-bb-title" }, layout.ast.title) : "";
32230
+ const titleNode = layout.ast.title ? text({ x: layout.width / 2, y: 22, class: "lt-bb-title", "text-anchor": "middle" }, layout.ast.title) : "";
32177
32231
  const substrate = renderSubstrate(layout.substrate);
32178
32232
  const parts = layout.parts.map(renderPart).join("\n");
32179
32233
  const wires = layout.wires.map(renderWire).join("\n");
@@ -33036,26 +33090,19 @@ function routeMessageFlow(f, objCenter, poolByLabel) {
33036
33090
 
33037
33091
  // src/diagrams/bpmn/renderer.ts
33038
33092
  var FONT_FAMILY = "system-ui, -apple-system, 'Segoe UI', sans-serif";
33039
- var STROKE = "#1f2937";
33040
- var STROKE_LIGHT = "#94a3b8";
33041
- var POOL_FILL = "#ffffff";
33042
- var LANE_FILL = "#fafbfc";
33043
- var TASK_FILL = "#ffffff";
33044
- var GATEWAY_FILL = "#fffbeb";
33045
- var EVENT_FILL = "#ffffff";
33046
- var LABEL_BAND_FILL = "#f1f5f9";
33047
33093
  function renderBpmn(textInput, config) {
33048
33094
  const ast = parseBpmn(textInput);
33049
33095
  const layout = layoutBpmn(ast);
33050
- return renderBpmnLayout(layout);
33096
+ return renderBpmnLayout(layout, config);
33051
33097
  }
33052
- function renderBpmnLayout(layout, _config) {
33098
+ function renderBpmnLayout(layout, config) {
33053
33099
  const { width, height, ast } = layout;
33100
+ const t = resolveBpmnTheme(config?.theme ?? "default");
33054
33101
  const out = [];
33055
33102
  out.push(title(ast.title ?? "BPMN diagram"));
33056
33103
  out.push(desc(`BPMN ${ast.direction} \u2014 ${ast.pools.length} pool(s), ${layout.objects.length} flow object(s).`));
33057
- out.push(buildDefs());
33058
- for (const pl2 of layout.pools) out.push(renderPool(pl2));
33104
+ out.push(buildDefs(t));
33105
+ for (const pl2 of layout.pools) out.push(renderPool(pl2, t));
33059
33106
  for (const lan of layout.lanes) {
33060
33107
  out.push(
33061
33108
  group({ class: "schematex-bpmn-lane" }, [
@@ -33064,8 +33111,8 @@ function renderBpmnLayout(layout, _config) {
33064
33111
  y: lan.y,
33065
33112
  width: lan.width - lan.labelHeight,
33066
33113
  height: lan.height,
33067
- fill: LANE_FILL,
33068
- stroke: STROKE,
33114
+ fill: t.laneFill,
33115
+ stroke: t.bpmnStroke,
33069
33116
  "stroke-width": 1
33070
33117
  }),
33071
33118
  rect({
@@ -33073,8 +33120,8 @@ function renderBpmnLayout(layout, _config) {
33073
33120
  y: lan.y,
33074
33121
  width: lan.labelHeight,
33075
33122
  height: lan.height,
33076
- fill: LABEL_BAND_FILL,
33077
- stroke: STROKE,
33123
+ fill: t.labelBandFill,
33124
+ stroke: t.bpmnStroke,
33078
33125
  "stroke-width": 1
33079
33126
  }),
33080
33127
  text(
@@ -33086,15 +33133,15 @@ function renderBpmnLayout(layout, _config) {
33086
33133
  "dominant-baseline": "middle",
33087
33134
  "font-family": FONT_FAMILY,
33088
33135
  "font-size": 12,
33089
- fill: STROKE
33136
+ fill: t.bpmnText
33090
33137
  },
33091
33138
  lan.lane.label
33092
33139
  )
33093
33140
  ])
33094
33141
  );
33095
33142
  }
33096
- for (const fl of layout.flows) out.push(renderFlow2(fl));
33097
- for (const ol of layout.objects) out.push(renderObject(ol));
33143
+ for (const fl of layout.flows) out.push(renderFlow2(fl, t));
33144
+ for (const ol of layout.objects) out.push(renderObject(ol, t));
33098
33145
  return svgRoot(
33099
33146
  {
33100
33147
  width,
@@ -33105,7 +33152,7 @@ function renderBpmnLayout(layout, _config) {
33105
33152
  out
33106
33153
  );
33107
33154
  }
33108
- function buildDefs() {
33155
+ function buildDefs(t) {
33109
33156
  return defs([
33110
33157
  // Sequence flow — filled triangle.
33111
33158
  el(
@@ -33122,7 +33169,7 @@ function buildDefs() {
33122
33169
  [
33123
33170
  el("path", {
33124
33171
  d: "M 0 0 L 10 5 L 0 10 z",
33125
- fill: STROKE
33172
+ fill: t.flowStroke
33126
33173
  })
33127
33174
  ]
33128
33175
  ),
@@ -33141,8 +33188,8 @@ function buildDefs() {
33141
33188
  [
33142
33189
  el("path", {
33143
33190
  d: "M 0 0 L 10 5 L 0 10 z",
33144
- fill: "#ffffff",
33145
- stroke: STROKE,
33191
+ fill: t.poolFill,
33192
+ stroke: t.msgFlowStroke,
33146
33193
  "stroke-width": 1
33147
33194
  })
33148
33195
  ]
@@ -33160,12 +33207,12 @@ function buildDefs() {
33160
33207
  orient: "auto"
33161
33208
  },
33162
33209
  [
33163
- el("circle", { cx: 5, cy: 5, r: 3, fill: "#ffffff", stroke: STROKE })
33210
+ el("circle", { cx: 5, cy: 5, r: 3, fill: t.poolFill, stroke: t.msgFlowStroke })
33164
33211
  ]
33165
33212
  )
33166
33213
  ]);
33167
33214
  }
33168
- function renderPool(pl2) {
33215
+ function renderPool(pl2, t) {
33169
33216
  const labelCx = pl2.labelX + pl2.labelWidth / 2;
33170
33217
  const labelCy = pl2.labelY + pl2.height / 2;
33171
33218
  if (pl2.pool.blackbox) {
@@ -33175,8 +33222,8 @@ function renderPool(pl2) {
33175
33222
  y: pl2.y,
33176
33223
  width: pl2.width,
33177
33224
  height: pl2.height,
33178
- fill: POOL_FILL,
33179
- stroke: STROKE,
33225
+ fill: t.poolFill,
33226
+ stroke: t.bpmnStroke,
33180
33227
  "stroke-width": 1.5
33181
33228
  }),
33182
33229
  rect({
@@ -33184,8 +33231,8 @@ function renderPool(pl2) {
33184
33231
  y: pl2.y,
33185
33232
  width: pl2.labelWidth,
33186
33233
  height: pl2.height,
33187
- fill: LABEL_BAND_FILL,
33188
- stroke: STROKE,
33234
+ fill: t.labelBandFill,
33235
+ stroke: t.bpmnStroke,
33189
33236
  "stroke-width": 1
33190
33237
  }),
33191
33238
  text(
@@ -33198,7 +33245,7 @@ function renderPool(pl2) {
33198
33245
  "font-family": FONT_FAMILY,
33199
33246
  "font-size": 13,
33200
33247
  "font-weight": "bold",
33201
- fill: STROKE
33248
+ fill: t.bpmnText
33202
33249
  },
33203
33250
  pl2.pool.label
33204
33251
  )
@@ -33210,8 +33257,8 @@ function renderPool(pl2) {
33210
33257
  y: pl2.y,
33211
33258
  width: pl2.width,
33212
33259
  height: pl2.height,
33213
- fill: POOL_FILL,
33214
- stroke: STROKE,
33260
+ fill: t.poolFill,
33261
+ stroke: t.bpmnStroke,
33215
33262
  "stroke-width": 1.5
33216
33263
  }),
33217
33264
  rect({
@@ -33219,8 +33266,8 @@ function renderPool(pl2) {
33219
33266
  y: pl2.y,
33220
33267
  width: pl2.labelWidth,
33221
33268
  height: pl2.height,
33222
- fill: LABEL_BAND_FILL,
33223
- stroke: STROKE,
33269
+ fill: t.labelBandFill,
33270
+ stroke: t.bpmnStroke,
33224
33271
  "stroke-width": 1
33225
33272
  }),
33226
33273
  text(
@@ -33233,19 +33280,19 @@ function renderPool(pl2) {
33233
33280
  "font-family": FONT_FAMILY,
33234
33281
  "font-size": 13,
33235
33282
  "font-weight": "bold",
33236
- fill: STROKE
33283
+ fill: t.bpmnText
33237
33284
  },
33238
33285
  pl2.pool.label
33239
33286
  )
33240
33287
  ]);
33241
33288
  }
33242
- function renderObject(ol) {
33289
+ function renderObject(ol, t) {
33243
33290
  const o = ol.obj;
33244
- if ("gatewayKind" in o) return renderGateway(ol);
33245
- if ("marker" in o) return renderActivity(ol);
33246
- return renderEvent2(ol);
33291
+ if ("gatewayKind" in o) return renderGateway(ol, t);
33292
+ if ("marker" in o) return renderActivity(ol, t);
33293
+ return renderEvent2(ol, t);
33247
33294
  }
33248
- function renderActivity(ol) {
33295
+ function renderActivity(ol, t) {
33249
33296
  const a = ol.obj;
33250
33297
  const isSubproc = a.kind === "subprocess-collapsed";
33251
33298
  const cx = ol.x + ol.width / 2;
@@ -33258,8 +33305,8 @@ function renderActivity(ol) {
33258
33305
  height: ol.height,
33259
33306
  rx: 10,
33260
33307
  ry: 10,
33261
- fill: TASK_FILL,
33262
- stroke: STROKE,
33308
+ fill: t.taskFill,
33309
+ stroke: t.taskStroke,
33263
33310
  "stroke-width": 1.5
33264
33311
  }),
33265
33312
  multilineText(
@@ -33270,14 +33317,14 @@ function renderActivity(ol) {
33270
33317
  "dominant-baseline": "middle",
33271
33318
  "font-family": FONT_FAMILY,
33272
33319
  "font-size": 12,
33273
- fill: STROKE
33320
+ fill: t.bpmnText
33274
33321
  },
33275
33322
  a.label,
33276
33323
  14
33277
33324
  )
33278
33325
  ];
33279
33326
  if (a.kind === "task" && a.marker !== "abstract") {
33280
- children.push(taskMarker(ol.x + 6, ol.y + 6, a.marker));
33327
+ children.push(taskMarker(ol.x + 6, ol.y + 6, a.marker, t));
33281
33328
  }
33282
33329
  if (isSubproc) {
33283
33330
  const mx = cx;
@@ -33289,53 +33336,53 @@ function renderActivity(ol) {
33289
33336
  width: 12,
33290
33337
  height: 12,
33291
33338
  fill: "none",
33292
- stroke: STROKE,
33339
+ stroke: t.bpmnStroke,
33293
33340
  "stroke-width": 1
33294
33341
  }),
33295
33342
  el("path", {
33296
33343
  d: `M ${mx - 4} ${my} L ${mx + 4} ${my} M ${mx} ${my - 4} L ${mx} ${my + 4}`,
33297
- stroke: STROKE,
33344
+ stroke: t.bpmnStroke,
33298
33345
  "stroke-width": 1
33299
33346
  })
33300
33347
  );
33301
33348
  }
33302
33349
  return group({ class: `schematex-bpmn-task marker-${a.marker}` }, children);
33303
33350
  }
33304
- function taskMarker(x, y, marker) {
33351
+ function taskMarker(x, y, marker, t) {
33305
33352
  const cx = x + 7;
33306
33353
  const cy = y + 7;
33307
33354
  if (marker === "user") {
33308
33355
  return el("g", { class: "marker-user" }, [
33309
- el("circle", { cx, cy: cy - 1, r: 2.5, fill: "none", stroke: STROKE, "stroke-width": 1 }),
33356
+ el("circle", { cx, cy: cy - 1, r: 2.5, fill: "none", stroke: t.bpmnStroke, "stroke-width": 1 }),
33310
33357
  el("path", {
33311
33358
  d: `M ${cx - 5} ${cy + 6} Q ${cx} ${cy + 1} ${cx + 5} ${cy + 6}`,
33312
33359
  fill: "none",
33313
- stroke: STROKE,
33360
+ stroke: t.bpmnStroke,
33314
33361
  "stroke-width": 1
33315
33362
  })
33316
33363
  ]);
33317
33364
  }
33318
33365
  if (marker === "service") {
33319
33366
  return el("g", { class: "marker-service" }, [
33320
- el("circle", { cx, cy, r: 4, fill: "none", stroke: STROKE, "stroke-width": 1 }),
33321
- el("circle", { cx, cy, r: 1.5, fill: STROKE }),
33367
+ el("circle", { cx, cy, r: 4, fill: "none", stroke: t.bpmnStroke, "stroke-width": 1 }),
33368
+ el("circle", { cx, cy, r: 1.5, fill: t.bpmnStroke }),
33322
33369
  el("path", {
33323
33370
  d: `M ${cx} ${cy - 6} L ${cx} ${cy - 4} M ${cx} ${cy + 4} L ${cx} ${cy + 6} M ${cx - 6} ${cy} L ${cx - 4} ${cy} M ${cx + 4} ${cy} L ${cx + 6} ${cy}`,
33324
- stroke: STROKE,
33371
+ stroke: t.bpmnStroke,
33325
33372
  "stroke-width": 1
33326
33373
  })
33327
33374
  ]);
33328
33375
  }
33329
33376
  if (marker === "send") {
33330
33377
  return el("g", { class: "marker-send" }, [
33331
- el("rect", { x: cx - 5, y: cy - 3, width: 10, height: 7, fill: STROKE }),
33332
- el("path", { d: `M ${cx - 5} ${cy - 3} L ${cx} ${cy + 1} L ${cx + 5} ${cy - 3}`, stroke: "#ffffff", "stroke-width": 1, fill: "none" })
33378
+ el("rect", { x: cx - 5, y: cy - 3, width: 10, height: 7, fill: t.bpmnStroke }),
33379
+ el("path", { d: `M ${cx - 5} ${cy - 3} L ${cx} ${cy + 1} L ${cx + 5} ${cy - 3}`, stroke: t.poolFill, "stroke-width": 1, fill: "none" })
33333
33380
  ]);
33334
33381
  }
33335
33382
  if (marker === "receive") {
33336
33383
  return el("g", { class: "marker-receive" }, [
33337
- el("rect", { x: cx - 5, y: cy - 3, width: 10, height: 7, fill: "none", stroke: STROKE, "stroke-width": 1 }),
33338
- el("path", { d: `M ${cx - 5} ${cy - 3} L ${cx} ${cy + 1} L ${cx + 5} ${cy - 3}`, stroke: STROKE, "stroke-width": 1, fill: "none" })
33384
+ el("rect", { x: cx - 5, y: cy - 3, width: 10, height: 7, fill: "none", stroke: t.bpmnStroke, "stroke-width": 1 }),
33385
+ el("path", { d: `M ${cx - 5} ${cy - 3} L ${cx} ${cy + 1} L ${cx + 5} ${cy - 3}`, stroke: t.bpmnStroke, "stroke-width": 1, fill: "none" })
33339
33386
  ]);
33340
33387
  }
33341
33388
  if (marker === "manual") {
@@ -33343,7 +33390,7 @@ function taskMarker(x, y, marker) {
33343
33390
  el("path", {
33344
33391
  d: `M ${cx - 3} ${cy + 4} L ${cx - 3} ${cy} L ${cx - 1.5} ${cy} L ${cx - 1.5} ${cy - 4} L ${cx} ${cy - 4} L ${cx} ${cy} L ${cx + 1.5} ${cy} L ${cx + 1.5} ${cy + 1} L ${cx + 3} ${cy + 1} L ${cx + 3} ${cy + 4} z`,
33345
33392
  fill: "none",
33346
- stroke: STROKE,
33393
+ stroke: t.bpmnStroke,
33347
33394
  "stroke-width": 1
33348
33395
  })
33349
33396
  ]);
@@ -33353,19 +33400,19 @@ function taskMarker(x, y, marker) {
33353
33400
  el("path", {
33354
33401
  d: `M ${cx - 4} ${cy - 5} Q ${cx - 6} ${cy} ${cx - 4} ${cy + 5} L ${cx + 4} ${cy + 5} Q ${cx + 2} ${cy} ${cx + 4} ${cy - 5} z`,
33355
33402
  fill: "none",
33356
- stroke: STROKE,
33403
+ stroke: t.bpmnStroke,
33357
33404
  "stroke-width": 1
33358
33405
  }),
33359
33406
  el("path", {
33360
33407
  d: `M ${cx - 2} ${cy - 2} L ${cx + 2} ${cy - 2} M ${cx - 2} ${cy} L ${cx + 2} ${cy} M ${cx - 2} ${cy + 2} L ${cx + 2} ${cy + 2}`,
33361
- stroke: STROKE,
33408
+ stroke: t.bpmnStroke,
33362
33409
  "stroke-width": 0.8
33363
33410
  })
33364
33411
  ]);
33365
33412
  }
33366
33413
  return "";
33367
33414
  }
33368
- function renderGateway(ol) {
33415
+ function renderGateway(ol, t) {
33369
33416
  const g = ol.obj;
33370
33417
  const cx = ol.x + ol.width / 2;
33371
33418
  const cy = ol.y + ol.height / 2;
@@ -33377,7 +33424,7 @@ function renderGateway(ol) {
33377
33424
  inner.push(
33378
33425
  el("path", {
33379
33426
  d: `M ${cx - a} ${cy - a} L ${cx + a} ${cy + a} M ${cx + a} ${cy - a} L ${cx - a} ${cy + a}`,
33380
- stroke: STROKE,
33427
+ stroke: t.gatewayGlyph,
33381
33428
  "stroke-width": 2.5,
33382
33429
  "stroke-linecap": "round"
33383
33430
  })
@@ -33387,7 +33434,7 @@ function renderGateway(ol) {
33387
33434
  inner.push(
33388
33435
  el("path", {
33389
33436
  d: `M ${cx - a} ${cy} L ${cx + a} ${cy} M ${cx} ${cy - a} L ${cx} ${cy + a}`,
33390
- stroke: STROKE,
33437
+ stroke: t.gatewayGlyph,
33391
33438
  "stroke-width": 2.5,
33392
33439
  "stroke-linecap": "round"
33393
33440
  })
@@ -33399,7 +33446,7 @@ function renderGateway(ol) {
33399
33446
  cy,
33400
33447
  r: r6 * 0.45,
33401
33448
  fill: "none",
33402
- stroke: STROKE,
33449
+ stroke: t.gatewayGlyph,
33403
33450
  "stroke-width": 2
33404
33451
  })
33405
33452
  );
@@ -33410,7 +33457,7 @@ function renderGateway(ol) {
33410
33457
  cy,
33411
33458
  r: r6 * 0.55,
33412
33459
  fill: "none",
33413
- stroke: STROKE,
33460
+ stroke: t.gatewayGlyph,
33414
33461
  "stroke-width": 1
33415
33462
  })
33416
33463
  );
@@ -33424,7 +33471,7 @@ function renderGateway(ol) {
33424
33471
  el("polygon", {
33425
33472
  points: pts.join(" "),
33426
33473
  fill: "none",
33427
- stroke: STROKE,
33474
+ stroke: t.gatewayGlyph,
33428
33475
  "stroke-width": 1.2
33429
33476
  })
33430
33477
  );
@@ -33437,22 +33484,22 @@ function renderGateway(ol) {
33437
33484
  "text-anchor": "middle",
33438
33485
  "font-family": FONT_FAMILY,
33439
33486
  "font-size": 11,
33440
- fill: STROKE
33487
+ fill: t.bpmnText
33441
33488
  },
33442
33489
  labelStr
33443
33490
  ) : "";
33444
33491
  return group({ class: `schematex-bpmn-gateway kind-${g.gatewayKind}` }, [
33445
33492
  el("polygon", {
33446
33493
  points,
33447
- fill: GATEWAY_FILL,
33448
- stroke: STROKE,
33494
+ fill: t.gatewayFill,
33495
+ stroke: t.gatewayStroke,
33449
33496
  "stroke-width": 1.5
33450
33497
  }),
33451
33498
  ...inner,
33452
33499
  labelEl
33453
33500
  ]);
33454
33501
  }
33455
- function renderEvent2(ol) {
33502
+ function renderEvent2(ol, t) {
33456
33503
  const e = ol.obj;
33457
33504
  const cx = ol.x + ol.width / 2;
33458
33505
  const cy = ol.y + ol.height / 2;
@@ -33460,14 +33507,16 @@ function renderEvent2(ol) {
33460
33507
  const isEnd = e.kind === "end";
33461
33508
  const isIntermediate = e.kind === "intermediate";
33462
33509
  const strokeW = isEnd ? 3 : 1.2;
33510
+ const fill = isEnd ? t.endFill : isIntermediate ? t.intermediateFill : t.startFill;
33511
+ const ring = isEnd ? t.endStroke : isIntermediate ? t.intermediateStroke : t.startStroke;
33463
33512
  const children = [];
33464
33513
  children.push(
33465
33514
  el("circle", {
33466
33515
  cx,
33467
33516
  cy,
33468
33517
  r: r6,
33469
- fill: EVENT_FILL,
33470
- stroke: STROKE,
33518
+ fill,
33519
+ stroke: ring,
33471
33520
  "stroke-width": strokeW
33472
33521
  })
33473
33522
  );
@@ -33478,16 +33527,16 @@ function renderEvent2(ol) {
33478
33527
  cy,
33479
33528
  r: r6 - 3,
33480
33529
  fill: "none",
33481
- stroke: STROKE,
33530
+ stroke: ring,
33482
33531
  "stroke-width": 1.2
33483
33532
  })
33484
33533
  );
33485
33534
  }
33486
33535
  const filled = e.throwCatch === "throw" && (isIntermediate || isEnd);
33487
33536
  if (e.trigger === "message") {
33488
- children.push(messageGlyph(cx, cy, r6 * 0.55, filled));
33537
+ children.push(messageGlyph(cx, cy, r6 * 0.55, filled, t));
33489
33538
  } else if (e.trigger === "timer") {
33490
- children.push(timerGlyph(cx, cy, r6 * 0.55));
33539
+ children.push(timerGlyph(cx, cy, r6 * 0.55, t));
33491
33540
  }
33492
33541
  if (e.label) {
33493
33542
  children.push(
@@ -33498,7 +33547,7 @@ function renderEvent2(ol) {
33498
33547
  "text-anchor": "middle",
33499
33548
  "font-family": FONT_FAMILY,
33500
33549
  "font-size": 11,
33501
- fill: STROKE
33550
+ fill: t.bpmnText
33502
33551
  },
33503
33552
  e.label
33504
33553
  )
@@ -33506,7 +33555,7 @@ function renderEvent2(ol) {
33506
33555
  }
33507
33556
  return group({ class: `schematex-bpmn-event kind-${e.kind} trigger-${e.trigger}` }, children);
33508
33557
  }
33509
- function messageGlyph(cx, cy, size, filled) {
33558
+ function messageGlyph(cx, cy, size, filled, t) {
33510
33559
  const w = size;
33511
33560
  const h = size * 0.7;
33512
33561
  const x = cx - w / 2;
@@ -33517,19 +33566,19 @@ function messageGlyph(cx, cy, size, filled) {
33517
33566
  y,
33518
33567
  width: w,
33519
33568
  height: h,
33520
- fill: filled ? STROKE : "#ffffff",
33521
- stroke: STROKE,
33569
+ fill: filled ? t.bpmnStroke : t.poolFill,
33570
+ stroke: t.bpmnStroke,
33522
33571
  "stroke-width": 1
33523
33572
  }),
33524
33573
  el("path", {
33525
33574
  d: `M ${x} ${y} L ${cx} ${y + h * 0.55} L ${x + w} ${y}`,
33526
- stroke: filled ? "#ffffff" : STROKE,
33575
+ stroke: filled ? t.poolFill : t.bpmnStroke,
33527
33576
  "stroke-width": 1,
33528
33577
  fill: "none"
33529
33578
  })
33530
33579
  ]);
33531
33580
  }
33532
- function timerGlyph(cx, cy, size) {
33581
+ function timerGlyph(cx, cy, size, t) {
33533
33582
  const r6 = size / 2;
33534
33583
  const ticks = [];
33535
33584
  for (let k = 0; k < 12; k++) {
@@ -33541,18 +33590,18 @@ function timerGlyph(cx, cy, size) {
33541
33590
  ticks.push(`M ${t1x} ${t1y} L ${t2x} ${t2y}`);
33542
33591
  }
33543
33592
  return el("g", { class: "trigger-timer" }, [
33544
- el("circle", { cx, cy, r: r6, fill: "none", stroke: STROKE, "stroke-width": 1 }),
33545
- el("path", { d: ticks.join(" "), stroke: STROKE, "stroke-width": 0.8 }),
33593
+ el("circle", { cx, cy, r: r6, fill: "none", stroke: t.bpmnStroke, "stroke-width": 1 }),
33594
+ el("path", { d: ticks.join(" "), stroke: t.bpmnStroke, "stroke-width": 0.8 }),
33546
33595
  // Hands
33547
33596
  el("path", {
33548
33597
  d: `M ${cx} ${cy} L ${cx} ${cy - r6 * 0.7} M ${cx} ${cy} L ${cx + r6 * 0.5} ${cy}`,
33549
- stroke: STROKE,
33598
+ stroke: t.bpmnStroke,
33550
33599
  "stroke-width": 1.2,
33551
33600
  "stroke-linecap": "round"
33552
33601
  })
33553
33602
  ]);
33554
33603
  }
33555
- function renderFlow2(fl) {
33604
+ function renderFlow2(fl, t) {
33556
33605
  const f = fl.flow;
33557
33606
  const isMessage = f.kind === "message";
33558
33607
  const dasharray = isMessage ? "6 4" : void 0;
@@ -33562,7 +33611,7 @@ function renderFlow2(fl) {
33562
33611
  path({
33563
33612
  d: fl.path,
33564
33613
  fill: "none",
33565
- stroke: isMessage ? STROKE_LIGHT : STROKE,
33614
+ stroke: isMessage ? t.msgFlowStroke : t.flowStroke,
33566
33615
  "stroke-width": 1.4,
33567
33616
  "stroke-dasharray": dasharray,
33568
33617
  "marker-start": markerStart,
@@ -33575,8 +33624,8 @@ function renderFlow2(fl) {
33575
33624
  children.push(
33576
33625
  el("polygon", {
33577
33626
  points: diamondPoints(head.x, head.y, 5),
33578
- fill: "#ffffff",
33579
- stroke: STROKE,
33627
+ fill: t.poolFill,
33628
+ stroke: t.flowStroke,
33580
33629
  "stroke-width": 1
33581
33630
  })
33582
33631
  );
@@ -33588,7 +33637,7 @@ function renderFlow2(fl) {
33588
33637
  children.push(
33589
33638
  el("path", {
33590
33639
  d: `M ${head.x - 4} ${head.y + 4} L ${head.x + 4} ${head.y - 4}`,
33591
- stroke: STROKE,
33640
+ stroke: t.flowStroke,
33592
33641
  "stroke-width": 1.5
33593
33642
  })
33594
33643
  );
@@ -33603,7 +33652,7 @@ function renderFlow2(fl) {
33603
33652
  "text-anchor": "middle",
33604
33653
  "font-family": FONT_FAMILY,
33605
33654
  "font-size": 10,
33606
- fill: STROKE
33655
+ fill: t.bpmnText
33607
33656
  },
33608
33657
  f.label
33609
33658
  )
@@ -33635,7 +33684,7 @@ var bpmn = {
33635
33684
  },
33636
33685
  parse: parseBpmn,
33637
33686
  render(text2, config) {
33638
- return renderBpmn(text2);
33687
+ return renderBpmn(text2, config);
33639
33688
  }
33640
33689
  };
33641
33690
 
@@ -35922,6 +35971,2508 @@ var sfc = {
35922
35971
  }
35923
35972
  };
35924
35973
 
35974
+ // src/diagrams/floorplan/catalog.ts
35975
+ var CHAIR_W = 0.44;
35976
+ var CHAIR_D = 0.38;
35977
+ var CHAIR_GAP = 0.27;
35978
+ var CHAIR_OVERHANG = 0.5;
35979
+ var RING = 0.45;
35980
+ function chairAt(px, cx, cy, deg) {
35981
+ const body = rect({
35982
+ class: "sx-fp-chair",
35983
+ x: px(-CHAIR_W / 2),
35984
+ y: px(-CHAIR_D / 2),
35985
+ width: px(CHAIR_W),
35986
+ height: px(CHAIR_D),
35987
+ rx: px(0.09)
35988
+ });
35989
+ const rot = Math.round(deg * 10) / 10;
35990
+ return el("g", { transform: `translate(${px(cx)},${px(cy)}) rotate(${rot})` }, [body]);
35991
+ }
35992
+ function edgeChairs(c, top, bottom) {
35993
+ const n = Math.max(1, Math.round(c.w / 0.65));
35994
+ const out = [];
35995
+ for (let i = 0; i < n; i++) {
35996
+ const cx = (i + 0.5) / n * c.w;
35997
+ if (top) out.push(chairAt(c.px, cx, -CHAIR_GAP, 0));
35998
+ out.push(chairAt(c.px, cx, c.h + CHAIR_GAP, 180));
35999
+ }
36000
+ return out.join("");
36001
+ }
36002
+ function box(c, cls = "sx-fp-furn", rx = 0) {
36003
+ return rect({ class: cls, x: 0, y: 0, width: c.px(c.w), height: c.px(c.h), rx: rx ? c.px(rx) : void 0 });
36004
+ }
36005
+ function glyphText(c, label) {
36006
+ return text(
36007
+ {
36008
+ class: "sx-fp-furn-text",
36009
+ x: c.px(c.w / 2),
36010
+ y: c.px(c.h / 2),
36011
+ "text-anchor": "middle",
36012
+ "dominant-baseline": "central",
36013
+ "font-size": c.px(Math.min(0.24, c.h * 0.4))
36014
+ },
36015
+ label
36016
+ );
36017
+ }
36018
+ function bedDraw(pillows) {
36019
+ return (c) => {
36020
+ const parts = [box(c, "sx-fp-furn", 0.05)];
36021
+ const pw = pillows === 2 ? c.w / 2 - 0.18 : c.w - 0.24;
36022
+ parts.push(rect({ class: "sx-fp-furn", x: c.px(0.12), y: c.px(0.1), width: c.px(pw), height: c.px(0.42), rx: c.px(0.06) }));
36023
+ if (pillows === 2) {
36024
+ parts.push(rect({ class: "sx-fp-furn", x: c.px(c.w / 2 + 0.06), y: c.px(0.1), width: c.px(pw), height: c.px(0.42), rx: c.px(0.06) }));
36025
+ }
36026
+ parts.push(line({ class: "sx-fp-furn-line", x1: 0, y1: c.px(0.72), x2: c.px(c.w), y2: c.px(0.72) }));
36027
+ return parts.join("");
36028
+ };
36029
+ }
36030
+ function sofaDraw(cushions) {
36031
+ return (c) => {
36032
+ const parts = [box(c, "sx-fp-furn", 0.08)];
36033
+ parts.push(rect({ class: "sx-fp-furn", x: c.px(0.15), y: 0, width: c.px(c.w - 0.3), height: c.px(0.18), rx: c.px(0.05) }));
36034
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(0.15), y1: c.px(0.18), x2: c.px(0.15), y2: c.px(c.h - 0.05) }));
36035
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(c.w - 0.15), y1: c.px(0.18), x2: c.px(c.w - 0.15), y2: c.px(c.h - 0.05) }));
36036
+ for (let i = 1; i < cushions; i++) {
36037
+ const x = c.px(0.15 + (c.w - 0.3) * i / cushions);
36038
+ parts.push(line({ class: "sx-fp-furn-line", x1: x, y1: c.px(0.18), x2: x, y2: c.px(c.h - 0.08) }));
36039
+ }
36040
+ return parts.join("");
36041
+ };
36042
+ }
36043
+ function shelfDraw(c) {
36044
+ const parts = [box(c)];
36045
+ const n = Math.max(2, Math.round(c.w / 0.15));
36046
+ for (let i = 1; i < n; i++) {
36047
+ const x = c.px(c.w * i / n);
36048
+ parts.push(line({ class: "sx-fp-furn-line", x1: x, y1: 0, x2: x, y2: c.px(c.h) }));
36049
+ }
36050
+ return parts.join("");
36051
+ }
36052
+ function boardDraw(c) {
36053
+ return [
36054
+ rect({ class: "sx-fp-furn-solid", x: 0, y: 0, width: c.px(c.w), height: c.px(c.h) }),
36055
+ rect({ class: "sx-fp-board-inner", x: c.px(0.08), y: c.px(c.h * 0.2), width: c.px(c.w - 0.16), height: c.px(c.h * 0.6) })
36056
+ ].join("");
36057
+ }
36058
+ function applianceDraw(label, drum) {
36059
+ return (c) => {
36060
+ const parts = [box(c)];
36061
+ if (drum) {
36062
+ parts.push(circle({ class: "sx-fp-furn-line", cx: c.px(c.w / 2), cy: c.px(c.h / 2), r: c.px(Math.min(c.w, c.h) * 0.32) }));
36063
+ } else {
36064
+ parts.push(line({ class: "sx-fp-furn-line", x1: 0, y1: c.px(0.12), x2: c.px(c.w), y2: c.px(0.12) }));
36065
+ }
36066
+ parts.push(glyphText(c, label));
36067
+ return parts.join("");
36068
+ };
36069
+ }
36070
+ function roundTable(seats, diaM) {
36071
+ const nominal = diaM + 2 * RING;
36072
+ return {
36073
+ w: nominal,
36074
+ h: nominal,
36075
+ draw: (c) => {
36076
+ const half = Math.min(c.w, c.h) / 2;
36077
+ const ring = Math.min(RING, half * 0.37);
36078
+ const r6 = half - ring;
36079
+ const cx = c.w / 2;
36080
+ const cy = c.h / 2;
36081
+ const parts = [circle({ class: "sx-fp-furn", cx: c.px(cx), cy: c.px(cy), r: c.px(r6) })];
36082
+ for (let i = 0; i < seats; i++) {
36083
+ const a = i / seats * 2 * Math.PI - Math.PI / 2;
36084
+ const px0 = cx + (r6 + ring * 0.55) * Math.cos(a);
36085
+ const py0 = cy + (r6 + ring * 0.55) * Math.sin(a);
36086
+ parts.push(chairAt(c.px, px0, py0, a * 180 / Math.PI + 90));
36087
+ }
36088
+ return parts.join("");
36089
+ }
36090
+ };
36091
+ }
36092
+ function tableDraw(top, bottom) {
36093
+ return (c) => box(c) + edgeChairs(c, top);
36094
+ }
36095
+ var TREAD = 0.28;
36096
+ function treadLines(c, vert, fixed0, fixed1, from, to, dashedFrom) {
36097
+ const parts = [];
36098
+ const dir = to >= from ? 1 : -1;
36099
+ for (let d = from; dir > 0 ? d <= to : d >= to; d += TREAD * dir) {
36100
+ const dashed = dashedFrom !== void 0 && (dir > 0 ? d > dashedFrom : d < dashedFrom);
36101
+ const cls = dashed ? "sx-fp-furn-dash" : "sx-fp-furn-line";
36102
+ if (vert) parts.push(line({ class: cls, x1: c.px(fixed0), y1: c.px(d), x2: c.px(fixed1), y2: c.px(d) }));
36103
+ else parts.push(line({ class: cls, x1: c.px(d), y1: c.px(fixed0), x2: c.px(d), y2: c.px(fixed1) }));
36104
+ }
36105
+ return parts.join("");
36106
+ }
36107
+ function breakLine(c, vert, lo, hi, at) {
36108
+ const span = hi - lo;
36109
+ const dz = Math.min(0.12, span * 0.18);
36110
+ const tilt = span * 0.18;
36111
+ const pts = vert ? [
36112
+ [lo, at + tilt],
36113
+ [lo + span * 0.4, at + tilt * 0.2],
36114
+ [lo + span * 0.5 - dz, at + tilt * 0.2 + dz],
36115
+ [lo + span * 0.5 + dz, at - tilt * 0.2 - dz],
36116
+ [lo + span * 0.6, at - tilt * 0.2],
36117
+ [hi, at - tilt]
36118
+ ] : [
36119
+ [at + tilt, lo],
36120
+ [at + tilt * 0.2, lo + span * 0.4],
36121
+ [at + tilt * 0.2 + dz, lo + span * 0.5 - dz],
36122
+ [at - tilt * 0.2 - dz, lo + span * 0.5 + dz],
36123
+ [at - tilt * 0.2, lo + span * 0.6],
36124
+ [at - tilt, hi]
36125
+ ];
36126
+ const d = pts.map(([a, b], i) => `${i === 0 ? "M" : "L"} ${c.px(vert ? a : a)} ${c.px(vert ? b : b)}`).join(" ");
36127
+ return path({ class: "sx-fp-stair-break", d });
36128
+ }
36129
+ function dirArrow(c, points, labelText2) {
36130
+ const parts = [];
36131
+ const [sx, sy] = points[0];
36132
+ parts.push(circle({ class: "sx-fp-furn-line", cx: c.px(sx), cy: c.px(sy), r: c.px(0.05) }));
36133
+ const d = points.map(([x, y], i) => `${i === 0 ? "M" : "L"} ${c.px(x)} ${c.px(y)}`).join(" ");
36134
+ parts.push(path({ class: "sx-fp-furn-line", d }));
36135
+ const [ex, ey] = points[points.length - 1];
36136
+ const [px2, py2] = points[points.length - 2];
36137
+ const ang = Math.atan2(ey - py2, ex - px2);
36138
+ const hs = 0.11;
36139
+ const a1 = ang + Math.PI - 0.5;
36140
+ const a2 = ang + Math.PI + 0.5;
36141
+ parts.push(
36142
+ polygon({
36143
+ class: "sx-fp-furn-dot",
36144
+ points: `${c.px(ex)},${c.px(ey)} ${c.px(ex + hs * Math.cos(a1))},${c.px(ey + hs * Math.sin(a1))} ${c.px(ex + hs * Math.cos(a2))},${c.px(ey + hs * Math.sin(a2))}`
36145
+ })
36146
+ );
36147
+ const [nx, ny] = points[1];
36148
+ const segLen = Math.hypot(nx - sx, ny - sy) || 1;
36149
+ const ux = (nx - sx) / segLen;
36150
+ const uy = (ny - sy) / segLen;
36151
+ const lx = sx + ux * 0.42 - uy * 0.16;
36152
+ const ly = sy + uy * 0.42 + ux * 0.16;
36153
+ parts.push(
36154
+ text(
36155
+ {
36156
+ class: "sx-fp-furn-text",
36157
+ x: c.px(lx),
36158
+ y: c.px(ly),
36159
+ "text-anchor": "middle",
36160
+ "dominant-baseline": "central",
36161
+ "font-size": c.px(0.18)
36162
+ },
36163
+ labelText2
36164
+ )
36165
+ );
36166
+ return parts.join("");
36167
+ }
36168
+ function straightStairs(c) {
36169
+ const vert = c.h >= c.w;
36170
+ const len = vert ? c.h : c.w;
36171
+ const breakAt = Math.min(7 * TREAD, len * 0.62);
36172
+ const parts = [box(c, "sx-fp-furn-nofill")];
36173
+ if (vert) {
36174
+ parts.push(treadLines(c, true, 0, c.w, c.h - TREAD, 0.02, c.h - breakAt));
36175
+ parts.push(breakLine(c, true, 0, c.w, c.h - breakAt));
36176
+ parts.push(dirArrow(c, [[c.w / 2, c.h - 0.18], [c.w / 2, c.h - breakAt + 0.28]], c.label ?? "UP"));
36177
+ } else {
36178
+ parts.push(treadLines(c, false, 0, c.h, TREAD, c.w - 0.02, breakAt));
36179
+ parts.push(breakLine(c, false, 0, c.h, breakAt));
36180
+ parts.push(dirArrow(c, [[0.18, c.h / 2], [breakAt - 0.28, c.h / 2]], c.label ?? "UP"));
36181
+ }
36182
+ return parts.join("");
36183
+ }
36184
+ function lStairs(c) {
36185
+ const rw = Math.min(c.w, c.h) * 0.45;
36186
+ const parts = [
36187
+ path({
36188
+ class: "sx-fp-furn-nofill",
36189
+ d: `M 0 0 L ${c.px(c.w)} 0 L ${c.px(c.w)} ${c.px(rw)} L ${c.px(rw)} ${c.px(rw)} L ${c.px(rw)} ${c.px(c.h)} L 0 ${c.px(c.h)} Z`
36190
+ })
36191
+ ];
36192
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(rw), y1: 0, x2: c.px(rw), y2: c.px(rw) }));
36193
+ parts.push(treadLines(c, true, 0, rw, c.h - TREAD, rw + 0.05));
36194
+ const breakAt = rw + (c.w - rw) * 0.55;
36195
+ parts.push(treadLines(c, false, 0, rw, rw + TREAD, c.w - 0.02, breakAt));
36196
+ parts.push(breakLine(c, false, 0, rw, breakAt));
36197
+ parts.push(
36198
+ dirArrow(
36199
+ c,
36200
+ [
36201
+ [rw / 2, c.h - 0.18],
36202
+ [rw / 2, rw / 2],
36203
+ [breakAt - 0.24, rw / 2]
36204
+ ],
36205
+ c.label ?? "UP"
36206
+ )
36207
+ );
36208
+ return parts.join("");
36209
+ }
36210
+ function uStairs(c) {
36211
+ const lh = Math.min(c.h * 0.3, Math.max(0.9, c.w / 2));
36212
+ const mid = c.w / 2;
36213
+ const parts = [box(c, "sx-fp-furn-nofill")];
36214
+ parts.push(line({ class: "sx-fp-furn-line", x1: 0, y1: c.px(lh), x2: c.px(c.w), y2: c.px(lh) }));
36215
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(mid), y1: c.px(lh), x2: c.px(mid), y2: c.px(c.h) }));
36216
+ parts.push(treadLines(c, true, mid, c.w, c.h - TREAD, lh + 0.05));
36217
+ const breakAt = lh + (c.h - lh) * 0.4;
36218
+ parts.push(treadLines(c, true, 0, mid, lh + TREAD, c.h - 0.02, breakAt));
36219
+ parts.push(breakLine(c, true, 0, mid, breakAt));
36220
+ parts.push(
36221
+ dirArrow(
36222
+ c,
36223
+ [
36224
+ [mid + mid / 2, c.h - 0.18],
36225
+ [mid + mid / 2, lh / 2],
36226
+ [mid / 2, lh / 2],
36227
+ [mid / 2, breakAt - 0.24]
36228
+ ],
36229
+ c.label ?? "UP"
36230
+ )
36231
+ );
36232
+ return parts.join("");
36233
+ }
36234
+ function spiralStairs(c) {
36235
+ const r6 = Math.min(c.w, c.h) / 2 - 0.02;
36236
+ const cx = c.w / 2;
36237
+ const cy = c.h / 2;
36238
+ const parts = [
36239
+ circle({ class: "sx-fp-furn", cx: c.px(cx), cy: c.px(cy), r: c.px(r6) }),
36240
+ circle({ class: "sx-fp-furn-line", cx: c.px(cx), cy: c.px(cy), r: c.px(0.08) })
36241
+ ];
36242
+ for (let i = 0; i < 12; i++) {
36243
+ const a = i / 12 * 2 * Math.PI;
36244
+ parts.push(
36245
+ line({
36246
+ class: "sx-fp-furn-line",
36247
+ x1: c.px(cx + 0.08 * Math.cos(a)),
36248
+ y1: c.px(cy + 0.08 * Math.sin(a)),
36249
+ x2: c.px(cx + r6 * Math.cos(a)),
36250
+ y2: c.px(cy + r6 * Math.sin(a))
36251
+ })
36252
+ );
36253
+ }
36254
+ const wr = r6 * 0.62;
36255
+ const a0 = Math.PI * 0.75;
36256
+ const a1 = Math.PI * 1.9;
36257
+ const steps = 5;
36258
+ const pts = [];
36259
+ for (let i = 0; i <= steps; i++) {
36260
+ const a = a0 + (a1 - a0) * i / steps;
36261
+ pts.push([cx + wr * Math.cos(a), cy + wr * Math.sin(a)]);
36262
+ }
36263
+ parts.push(dirArrow(c, pts, c.label ?? "UP"));
36264
+ return parts.join("");
36265
+ }
36266
+ var FLOORPLAN_SYMBOLS = {
36267
+ // ── residential / living ──
36268
+ "bed-double": { w: 1.6, h: 2, draw: bedDraw(2) },
36269
+ "bed-single": { w: 0.9, h: 2, draw: bedDraw(1) },
36270
+ "bed-queen": { w: 1.55, h: 2.05, draw: bedDraw(2) },
36271
+ "bed-king": { w: 1.95, h: 2.05, draw: bedDraw(2) },
36272
+ sofa: { w: 2.2, h: 0.9, draw: sofaDraw(3) },
36273
+ loveseat: { w: 1.5, h: 0.9, draw: sofaDraw(2) },
36274
+ armchair: { w: 0.9, h: 0.9, draw: sofaDraw(1) },
36275
+ "coffee-table": { w: 1, h: 0.5, draw: (c) => box(c, "sx-fp-furn", 0.06) },
36276
+ tv: { w: 1.4, h: 0.15, draw: (c) => rect({ class: "sx-fp-furn-solid", x: 0, y: 0, width: c.px(c.w), height: c.px(c.h) }) },
36277
+ rug: {
36278
+ w: 2,
36279
+ h: 1.4,
36280
+ underlay: true,
36281
+ draw: (c) => rect({ class: "sx-fp-rug", x: 0, y: 0, width: c.px(c.w), height: c.px(c.h), rx: c.px(0.1) })
36282
+ },
36283
+ wardrobe: {
36284
+ w: 1.8,
36285
+ h: 0.6,
36286
+ draw: (c) => {
36287
+ const parts = [box(c)];
36288
+ if (c.w >= c.h) {
36289
+ parts.push(line({ class: "sx-fp-furn-line", x1: 0, y1: c.px(c.h / 2), x2: c.px(c.w), y2: c.px(c.h / 2) }));
36290
+ const n = Math.max(2, Math.round(c.w / 0.15));
36291
+ for (let i = 0; i < n; i++) {
36292
+ const x = c.px((i + 0.5) / n * c.w);
36293
+ parts.push(line({ class: "sx-fp-furn-line", x1: x, y1: c.px(c.h / 2 - 0.1), x2: x, y2: c.px(c.h / 2 + 0.1) }));
36294
+ }
36295
+ } else {
36296
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(c.w / 2), y1: 0, x2: c.px(c.w / 2), y2: c.px(c.h) }));
36297
+ const n = Math.max(2, Math.round(c.h / 0.15));
36298
+ for (let i = 0; i < n; i++) {
36299
+ const y = c.px((i + 0.5) / n * c.h);
36300
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(c.w / 2 - 0.1), y1: y, x2: c.px(c.w / 2 + 0.1), y2: y }));
36301
+ }
36302
+ }
36303
+ return parts.join("");
36304
+ }
36305
+ },
36306
+ dresser: {
36307
+ w: 1.2,
36308
+ h: 0.5,
36309
+ draw: (c) => [
36310
+ box(c),
36311
+ line({ class: "sx-fp-furn-line", x1: 0, y1: c.px(c.h / 2), x2: c.px(c.w), y2: c.px(c.h / 2) }),
36312
+ circle({ class: "sx-fp-furn-line", cx: c.px(c.w * 0.3), cy: c.px(c.h * 0.25), r: c.px(0.03) }),
36313
+ circle({ class: "sx-fp-furn-line", cx: c.px(c.w * 0.7), cy: c.px(c.h * 0.25), r: c.px(0.03) })
36314
+ ].join("")
36315
+ },
36316
+ nightstand: {
36317
+ w: 0.5,
36318
+ h: 0.4,
36319
+ draw: (c) => box(c) + circle({ class: "sx-fp-furn-line", cx: c.px(c.w / 2), cy: c.px(c.h / 2), r: c.px(0.08) })
36320
+ },
36321
+ bookshelf: { w: 0.9, h: 0.3, draw: shelfDraw },
36322
+ plant: {
36323
+ w: 0.5,
36324
+ h: 0.5,
36325
+ draw: (c) => {
36326
+ const r6 = Math.min(c.w, c.h) / 2;
36327
+ const parts = [circle({ class: "sx-fp-furn", cx: c.px(c.w / 2), cy: c.px(c.h / 2), r: c.px(r6) })];
36328
+ for (const a of [0, 60, 120, 180, 240, 300]) {
36329
+ const rad = a * Math.PI / 180;
36330
+ parts.push(
36331
+ line({
36332
+ class: "sx-fp-furn-line",
36333
+ x1: c.px(c.w / 2),
36334
+ y1: c.px(c.h / 2),
36335
+ x2: c.px(c.w / 2 + (r6 - 0.03) * Math.cos(rad)),
36336
+ y2: c.px(c.h / 2 + (r6 - 0.03) * Math.sin(rad))
36337
+ })
36338
+ );
36339
+ }
36340
+ return parts.join("");
36341
+ }
36342
+ },
36343
+ "dining-table": { w: 1.6, h: 0.9, envelope: [CHAIR_OVERHANG, 0, CHAIR_OVERHANG, 0], draw: tableDraw(true) },
36344
+ sectional: {
36345
+ w: 2.6,
36346
+ h: 2,
36347
+ draw: (c) => {
36348
+ const d = 0.9 * Math.min(c.h / 2, 1);
36349
+ const parts = [
36350
+ path({
36351
+ class: "sx-fp-furn",
36352
+ d: `M 0 0 L ${c.px(c.w)} 0 L ${c.px(c.w)} ${c.px(d)} L ${c.px(d)} ${c.px(d)} L ${c.px(d)} ${c.px(c.h)} L 0 ${c.px(c.h)} Z`
36353
+ }),
36354
+ rect({ class: "sx-fp-furn", x: c.px(0.12), y: 0, width: c.px(c.w - 0.24), height: c.px(0.16), rx: c.px(0.05) }),
36355
+ rect({ class: "sx-fp-furn", x: 0, y: c.px(0.12), width: c.px(0.16), height: c.px(c.h - 0.24), rx: c.px(0.05) })
36356
+ ];
36357
+ for (const fx of [0.33, 0.66]) {
36358
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(d + (c.w - d) * fx), y1: c.px(0.16), x2: c.px(d + (c.w - d) * fx), y2: c.px(d - 0.05) }));
36359
+ }
36360
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(0.16), y1: c.px(c.h / 2 + d / 4), x2: c.px(d - 0.05), y2: c.px(c.h / 2 + d / 4) }));
36361
+ return parts.join("");
36362
+ }
36363
+ },
36364
+ "side-table": { w: 0.5, h: 0.5, draw: (c) => box(c, "sx-fp-furn", 0.06) },
36365
+ "tv-stand": {
36366
+ w: 1.6,
36367
+ h: 0.45,
36368
+ underlay: true,
36369
+ // a surface — the TV sits on it
36370
+ draw: (c) => box(c) + line({ class: "sx-fp-furn-line", x1: 0, y1: c.px(c.h / 2), x2: c.px(c.w), y2: c.px(c.h / 2) })
36371
+ },
36372
+ fireplace: {
36373
+ w: 1.5,
36374
+ h: 0.5,
36375
+ draw: (c) => {
36376
+ const inset = Math.min(0.18, c.w * 0.15);
36377
+ return [
36378
+ box(c),
36379
+ // firebox opening toward the room (south edge)
36380
+ rect({
36381
+ class: "sx-fp-furn-line",
36382
+ x: c.px(inset),
36383
+ y: c.px(c.h * 0.3),
36384
+ width: c.px(c.w - 2 * inset),
36385
+ height: c.px(c.h * 0.7 - 0.04)
36386
+ }),
36387
+ line({ class: "sx-fp-furn-line", x1: 0, y1: c.px(c.h * 0.3), x2: c.px(inset), y2: 0 }),
36388
+ line({ class: "sx-fp-furn-line", x1: c.px(c.w), y1: c.px(c.h * 0.3), x2: c.px(c.w - inset), y2: 0 })
36389
+ ].join("");
36390
+ }
36391
+ },
36392
+ "floor-lamp": {
36393
+ w: 0.35,
36394
+ h: 0.35,
36395
+ draw: (c) => {
36396
+ const r6 = Math.min(c.w, c.h) / 2;
36397
+ return [
36398
+ circle({ class: "sx-fp-furn", cx: c.px(c.w / 2), cy: c.px(c.h / 2), r: c.px(r6) }),
36399
+ line({ class: "sx-fp-furn-line", x1: c.px(c.w / 2 - r6), y1: c.px(c.h / 2), x2: c.px(c.w / 2 + r6), y2: c.px(c.h / 2) }),
36400
+ line({ class: "sx-fp-furn-line", x1: c.px(c.w / 2), y1: c.px(c.h / 2 - r6), x2: c.px(c.w / 2), y2: c.px(c.h / 2 + r6) })
36401
+ ].join("");
36402
+ }
36403
+ },
36404
+ ottoman: { w: 0.6, h: 0.45, draw: (c) => box(c, "sx-fp-furn", 0.12) },
36405
+ piano: {
36406
+ w: 1.5,
36407
+ h: 1.7,
36408
+ draw: (c) => {
36409
+ const X = (v) => c.px(v * (c.w / 1.5));
36410
+ const Y = (v) => c.px(v * (c.h / 1.7));
36411
+ const body = [
36412
+ `M ${X(0)} ${Y(0)}`,
36413
+ `L ${X(1.5)} ${Y(0)}`,
36414
+ `L ${X(1.5)} ${Y(0.75)}`,
36415
+ `C ${X(1.5)} ${Y(1.25)} ${X(1.25)} ${Y(1.7)} ${X(0.8)} ${Y(1.7)}`,
36416
+ `C ${X(0.45)} ${Y(1.7)} ${X(0.28)} ${Y(1.45)} ${X(0.28)} ${Y(1.1)}`,
36417
+ `C ${X(0.28)} ${Y(0.85)} ${X(0.14)} ${Y(0.72)} ${X(0)} ${Y(0.68)}`,
36418
+ "Z"
36419
+ ].join(" ");
36420
+ return path({ class: "sx-fp-furn", d: body }) + line({ class: "sx-fp-furn-line", x1: X(0), y1: Y(0.14), x2: X(1.5), y2: Y(0.14) }) + chairAt(c.px, c.w / 1.5 * 0.75, -0.25, 180);
36421
+ },
36422
+ envelope: [CHAIR_OVERHANG, 0, 0, 0]
36423
+ },
36424
+ "piano-upright": {
36425
+ w: 1.5,
36426
+ h: 0.6,
36427
+ envelope: [0, 0, CHAIR_OVERHANG, 0],
36428
+ draw: (c) => box(c) + line({ class: "sx-fp-furn-line", x1: 0, y1: c.px(c.h * 0.55), x2: c.px(c.w), y2: c.px(c.h * 0.55) }) + chairAt(c.px, c.w / 2, c.h + CHAIR_GAP, 180)
36429
+ },
36430
+ "pool-table": {
36431
+ w: 2.54,
36432
+ h: 1.27,
36433
+ draw: (c) => {
36434
+ const rail = Math.min(0.12, c.h * 0.1);
36435
+ const parts = [
36436
+ box(c, "sx-fp-furn", 0.06),
36437
+ rect({ class: "sx-fp-furn-line", x: c.px(rail), y: c.px(rail), width: c.px(c.w - 2 * rail), height: c.px(c.h - 2 * rail) })
36438
+ ];
36439
+ for (const [px0, py0] of [
36440
+ [rail, rail],
36441
+ [c.w / 2, rail],
36442
+ [c.w - rail, rail],
36443
+ [rail, c.h - rail],
36444
+ [c.w / 2, c.h - rail],
36445
+ [c.w - rail, c.h - rail]
36446
+ ]) {
36447
+ parts.push(circle({ class: "sx-fp-furn-dot", cx: c.px(px0), cy: c.px(py0), r: c.px(0.045) }));
36448
+ }
36449
+ return parts.join("");
36450
+ }
36451
+ },
36452
+ crib: {
36453
+ w: 0.7,
36454
+ h: 1.3,
36455
+ draw: (c) => {
36456
+ const parts = [box(c, "sx-fp-furn", 0.05), rect({ class: "sx-fp-furn-line", x: c.px(0.08), y: c.px(0.08), width: c.px(c.w - 0.16), height: c.px(c.h - 0.16), rx: c.px(0.04) })];
36457
+ const n = Math.max(3, Math.round(c.h / 0.18));
36458
+ for (let i = 1; i < n; i++) {
36459
+ const y = c.px(0.08 + (c.h - 0.16) * i / n);
36460
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(0.08), y1: y, x2: c.px(c.w - 0.08), y2: y }));
36461
+ }
36462
+ return parts.join("");
36463
+ }
36464
+ },
36465
+ "bunk-bed": {
36466
+ w: 0.95,
36467
+ h: 2,
36468
+ draw: (c) => bedDraw(1)(c) + line({ class: "sx-fp-furn-dash", x1: c.px(0.07), y1: c.px(0.07), x2: c.px(c.w - 0.07), y2: c.px(0.07) }) + [0.25, 0.45, 0.65].map((f) => line({ class: "sx-fp-furn-line", x1: c.px(c.w - 0.16), y1: c.px(c.h * f), x2: c.px(c.w), y2: c.px(c.h * f) })).join("")
36469
+ },
36470
+ "ceiling-fan": {
36471
+ w: 0.9,
36472
+ h: 0.9,
36473
+ underlay: true,
36474
+ // overhead fixture — never collides with floor furniture
36475
+ draw: (c) => {
36476
+ const r6 = Math.min(c.w, c.h) / 2;
36477
+ const cx = c.w / 2;
36478
+ const cy = c.h / 2;
36479
+ const parts = [
36480
+ circle({ class: "sx-fp-furn-dash", cx: c.px(cx), cy: c.px(cy), r: c.px(r6) }),
36481
+ circle({ class: "sx-fp-furn", cx: c.px(cx), cy: c.px(cy), r: c.px(r6 * 0.18) })
36482
+ ];
36483
+ for (const a of [20, 110, 200, 290]) {
36484
+ const rad = a * Math.PI / 180;
36485
+ parts.push(
36486
+ el("ellipse", {
36487
+ class: "sx-fp-furn-line",
36488
+ cx: c.px(cx + r6 * 0.55 * Math.cos(rad)),
36489
+ cy: c.px(cy + r6 * 0.55 * Math.sin(rad)),
36490
+ rx: c.px(r6 * 0.42),
36491
+ ry: c.px(r6 * 0.15),
36492
+ transform: `rotate(${a} ${c.px(cx + r6 * 0.55 * Math.cos(rad))} ${c.px(cy + r6 * 0.55 * Math.sin(rad))})`
36493
+ })
36494
+ );
36495
+ }
36496
+ return parts.join("");
36497
+ }
36498
+ },
36499
+ // ── kitchen / bath ──
36500
+ counter: {
36501
+ w: 2,
36502
+ h: 0.6,
36503
+ underlay: true,
36504
+ draw: (c) => box(c) + line({
36505
+ class: "sx-fp-furn-dash",
36506
+ x1: c.px(0.05),
36507
+ y1: c.px(c.h - 0.06),
36508
+ x2: c.px(c.w - 0.05),
36509
+ y2: c.px(c.h - 0.06)
36510
+ })
36511
+ },
36512
+ "kitchen-sink": {
36513
+ w: 0.8,
36514
+ h: 0.6,
36515
+ draw: (c) => [
36516
+ box(c),
36517
+ rect({ class: "sx-fp-furn-line", x: c.px(0.07), y: c.px(0.1), width: c.px(c.w / 2 - 0.11), height: c.px(c.h - 0.2), rx: c.px(0.04) }),
36518
+ rect({ class: "sx-fp-furn-line", x: c.px(c.w / 2 + 0.04), y: c.px(0.1), width: c.px(c.w / 2 - 0.11), height: c.px(c.h - 0.2), rx: c.px(0.04) }),
36519
+ circle({ class: "sx-fp-furn-dot", cx: c.px(c.w / 2), cy: c.px(0.06), r: c.px(0.024) })
36520
+ ].join("")
36521
+ },
36522
+ stove: {
36523
+ w: 0.6,
36524
+ h: 0.6,
36525
+ draw: (c) => {
36526
+ const parts = [box(c)];
36527
+ const inset = 0.18;
36528
+ for (const [bx, by] of [
36529
+ [inset, inset],
36530
+ [c.w - inset, inset],
36531
+ [inset, c.h - inset],
36532
+ [c.w - inset, c.h - inset]
36533
+ ]) {
36534
+ parts.push(circle({ class: "sx-fp-furn-line", cx: c.px(bx), cy: c.px(by), r: c.px(0.085) }));
36535
+ }
36536
+ return parts.join("");
36537
+ }
36538
+ },
36539
+ fridge: { w: 0.7, h: 0.7, draw: applianceDraw("REF", false) },
36540
+ dishwasher: { w: 0.6, h: 0.6, draw: applianceDraw("DW", false) },
36541
+ island: {
36542
+ w: 1.8,
36543
+ h: 0.9,
36544
+ underlay: true,
36545
+ draw: (c) => box(c) + rect({ class: "sx-fp-furn-line", x: c.px(0.08), y: c.px(0.08), width: c.px(c.w - 0.16), height: c.px(c.h - 0.16) })
36546
+ },
36547
+ toilet: {
36548
+ w: 0.5,
36549
+ h: 0.7,
36550
+ draw: (c) => [
36551
+ rect({ class: "sx-fp-furn", x: 0, y: 0, width: c.px(c.w), height: c.px(0.2), rx: c.px(0.04) }),
36552
+ el("ellipse", {
36553
+ class: "sx-fp-furn",
36554
+ cx: c.px(c.w / 2),
36555
+ cy: c.px(0.2 + (c.h - 0.24) / 2),
36556
+ rx: c.px(c.w / 2 - 0.04),
36557
+ ry: c.px((c.h - 0.28) / 2)
36558
+ })
36559
+ ].join("")
36560
+ },
36561
+ sink: {
36562
+ w: 0.55,
36563
+ h: 0.45,
36564
+ draw: (c) => [
36565
+ box(c, "sx-fp-furn", 0.05),
36566
+ el("ellipse", {
36567
+ class: "sx-fp-furn-line",
36568
+ cx: c.px(c.w / 2),
36569
+ cy: c.px(c.h / 2 + 0.02),
36570
+ rx: c.px(c.w / 2 - 0.08),
36571
+ ry: c.px(c.h / 2 - 0.1)
36572
+ }),
36573
+ circle({ class: "sx-fp-furn-dot", cx: c.px(c.w / 2), cy: c.px(0.07), r: c.px(0.024) })
36574
+ ].join("")
36575
+ },
36576
+ bathtub: {
36577
+ w: 0.8,
36578
+ h: 1.7,
36579
+ draw: (c) => [
36580
+ box(c, "sx-fp-furn", 0.08),
36581
+ rect({ class: "sx-fp-furn-line", x: c.px(0.09), y: c.px(0.09), width: c.px(c.w - 0.18), height: c.px(c.h - 0.18), rx: c.px(0.22) }),
36582
+ circle({ class: "sx-fp-furn-dot", cx: c.px(c.w / 2), cy: c.px(0.26), r: c.px(0.035) })
36583
+ ].join("")
36584
+ },
36585
+ shower: {
36586
+ w: 0.9,
36587
+ h: 0.9,
36588
+ draw: (c) => [
36589
+ box(c),
36590
+ line({ class: "sx-fp-furn-line", x1: 0, y1: 0, x2: c.px(c.w), y2: c.px(c.h) }),
36591
+ line({ class: "sx-fp-furn-line", x1: c.px(c.w), y1: 0, x2: 0, y2: c.px(c.h) }),
36592
+ circle({ class: "sx-fp-furn", cx: c.px(0.15), cy: c.px(0.15), r: c.px(0.06) })
36593
+ ].join("")
36594
+ },
36595
+ washer: { w: 0.6, h: 0.6, draw: applianceDraw("W", true) },
36596
+ dryer: { w: 0.6, h: 0.6, draw: applianceDraw("D", true) },
36597
+ "wall-cabinet": {
36598
+ w: 0.9,
36599
+ h: 0.35,
36600
+ underlay: true,
36601
+ // above the cut plane — drawn dashed over the base run
36602
+ draw: (c) => rect({ class: "sx-fp-furn-dash", x: 0, y: 0, width: c.px(c.w), height: c.px(c.h) })
36603
+ },
36604
+ "range-hood": {
36605
+ w: 0.8,
36606
+ h: 0.55,
36607
+ underlay: true,
36608
+ // above the cut plane
36609
+ draw: (c) => el("g", {}, [
36610
+ polygon({
36611
+ class: "sx-fp-furn-dash",
36612
+ points: `${c.px(0.08)},0 ${c.px(c.w - 0.08)},0 ${c.px(c.w)},${c.px(c.h)} 0,${c.px(c.h)}`
36613
+ })
36614
+ ])
36615
+ },
36616
+ "bar-stool": {
36617
+ w: 0.35,
36618
+ h: 0.35,
36619
+ draw: (c) => circle({ class: "sx-fp-chair", cx: c.px(c.w / 2), cy: c.px(c.h / 2), r: c.px(Math.min(c.w, c.h) / 2) })
36620
+ },
36621
+ vanity: {
36622
+ w: 1.5,
36623
+ h: 0.55,
36624
+ draw: (c) => {
36625
+ const parts = [box(c)];
36626
+ for (const fx of [0.27, 0.73]) {
36627
+ parts.push(
36628
+ el("ellipse", {
36629
+ class: "sx-fp-furn-line",
36630
+ cx: c.px(c.w * fx),
36631
+ cy: c.px(c.h / 2 + 0.02),
36632
+ rx: c.px(Math.min(0.22, c.w * 0.16)),
36633
+ ry: c.px(c.h / 2 - 0.12)
36634
+ })
36635
+ );
36636
+ parts.push(circle({ class: "sx-fp-furn-dot", cx: c.px(c.w * fx), cy: c.px(0.07), r: c.px(0.022) }));
36637
+ }
36638
+ return parts.join("");
36639
+ }
36640
+ },
36641
+ bidet: {
36642
+ w: 0.4,
36643
+ h: 0.6,
36644
+ draw: (c) => rect({ class: "sx-fp-furn", x: c.px(c.w * 0.15), y: 0, width: c.px(c.w * 0.7), height: c.px(0.12), rx: c.px(0.03) }) + el("ellipse", {
36645
+ class: "sx-fp-furn",
36646
+ cx: c.px(c.w / 2),
36647
+ cy: c.px(0.12 + (c.h - 0.16) / 2),
36648
+ rx: c.px(c.w / 2 - 0.03),
36649
+ ry: c.px((c.h - 0.18) / 2)
36650
+ }) + circle({ class: "sx-fp-furn-line", cx: c.px(c.w / 2), cy: c.px(c.h * 0.45), r: c.px(0.035) })
36651
+ },
36652
+ urinal: {
36653
+ w: 0.4,
36654
+ h: 0.35,
36655
+ draw: (c) => rect({ class: "sx-fp-furn", x: 0, y: 0, width: c.px(c.w), height: c.px(0.08) }) + path({
36656
+ class: "sx-fp-furn",
36657
+ d: `M ${c.px(0.05)} ${c.px(0.08)} L ${c.px(c.w - 0.05)} ${c.px(0.08)} C ${c.px(c.w - 0.05)} ${c.px(c.h * 0.75)} ${c.px(c.w * 0.7)} ${c.px(c.h)} ${c.px(c.w / 2)} ${c.px(c.h)} C ${c.px(c.w * 0.3)} ${c.px(c.h)} ${c.px(0.05)} ${c.px(c.h * 0.75)} ${c.px(0.05)} ${c.px(0.08)} Z`
36658
+ })
36659
+ },
36660
+ // ── stairs & vertical circulation ──
36661
+ stairs: { w: 1, h: 3, consumesLabel: true, draw: straightStairs },
36662
+ "stairs-l": { w: 2.2, h: 2.2, consumesLabel: true, draw: lStairs },
36663
+ "stairs-u": { w: 2, h: 3, consumesLabel: true, draw: uStairs },
36664
+ "spiral-stairs": { w: 1.5, h: 1.5, consumesLabel: true, draw: spiralStairs },
36665
+ elevator: {
36666
+ w: 1.6,
36667
+ h: 1.5,
36668
+ draw: (c) => box(c) + line({ class: "sx-fp-furn-line", x1: 0, y1: 0, x2: c.px(c.w), y2: c.px(c.h) }) + line({ class: "sx-fp-furn-line", x1: c.px(c.w), y1: 0, x2: 0, y2: c.px(c.h) })
36669
+ },
36670
+ // ── structural ──
36671
+ column: {
36672
+ w: 0.4,
36673
+ h: 0.4,
36674
+ draw: (c) => rect({ class: "sx-fp-furn-solid", x: 0, y: 0, width: c.px(c.w), height: c.px(c.h) })
36675
+ },
36676
+ // ── classroom / office ──
36677
+ "desk-chair": {
36678
+ w: 0.6,
36679
+ h: 0.75,
36680
+ draw: (c) => [
36681
+ rect({ class: "sx-fp-furn", x: 0, y: 0, width: c.px(c.w), height: c.px(c.h * 0.58) }),
36682
+ rect({
36683
+ class: "sx-fp-chair",
36684
+ x: c.px(c.w / 2 - c.w * 0.3),
36685
+ y: c.px(c.h * 0.66),
36686
+ width: c.px(c.w * 0.6),
36687
+ height: c.px(c.h * 0.32),
36688
+ rx: c.px(0.07)
36689
+ })
36690
+ ].join("")
36691
+ },
36692
+ desk: {
36693
+ w: 1.4,
36694
+ h: 0.7,
36695
+ envelope: [0, 0, CHAIR_OVERHANG, 0],
36696
+ draw: (c) => box(c) + chairAt(c.px, c.w / 2, c.h + CHAIR_GAP, 180)
36697
+ },
36698
+ chair: {
36699
+ w: 0.45,
36700
+ h: 0.45,
36701
+ draw: (c) => rect({ class: "sx-fp-chair", x: 0, y: 0, width: c.px(c.w), height: c.px(c.h), rx: c.px(0.1) }) + line({ class: "sx-fp-furn-line", x1: 0, y1: c.px(0.06), x2: 0, y2: c.px(c.h - 0.06) })
36702
+ },
36703
+ whiteboard: { w: 3, h: 0.12, draw: boardDraw },
36704
+ smartboard: { w: 2, h: 0.12, draw: boardDraw },
36705
+ bookcase: { w: 0.9, h: 0.3, draw: shelfDraw },
36706
+ "desk-l": {
36707
+ w: 1.6,
36708
+ h: 1.6,
36709
+ draw: (c) => {
36710
+ const d = 0.7 * Math.min(c.w / 1.6, c.h / 1.6);
36711
+ return path({
36712
+ class: "sx-fp-furn",
36713
+ d: `M 0 0 L ${c.px(c.w)} 0 L ${c.px(c.w)} ${c.px(d)} L ${c.px(d)} ${c.px(d)} L ${c.px(d)} ${c.px(c.h)} L 0 ${c.px(c.h)} Z`
36714
+ }) + chairAt(c.px, d + 0.32, d + 0.32, 315);
36715
+ }
36716
+ },
36717
+ "filing-cabinet": {
36718
+ w: 0.5,
36719
+ h: 0.6,
36720
+ draw: (c) => box(c) + line({ class: "sx-fp-furn-line", x1: 0, y1: c.px(c.h / 3), x2: c.px(c.w), y2: c.px(c.h / 3) }) + line({ class: "sx-fp-furn-line", x1: 0, y1: c.px(2 * c.h / 3), x2: c.px(c.w), y2: c.px(2 * c.h / 3) })
36721
+ },
36722
+ lockers: {
36723
+ w: 1.8,
36724
+ h: 0.45,
36725
+ draw: (c) => {
36726
+ const parts = [box(c)];
36727
+ const n = Math.max(2, Math.round(c.w / 0.3));
36728
+ for (let i = 1; i < n; i++) {
36729
+ const x = c.px(c.w * i / n);
36730
+ parts.push(line({ class: "sx-fp-furn-line", x1: x, y1: 0, x2: x, y2: c.px(c.h) }));
36731
+ }
36732
+ for (let i = 0; i < n; i++) {
36733
+ parts.push(circle({ class: "sx-fp-furn-dot", cx: c.px((i + 0.78) / n * c.w), cy: c.px(c.h / 2), r: c.px(0.02) }));
36734
+ }
36735
+ return parts.join("");
36736
+ }
36737
+ },
36738
+ cubbies: {
36739
+ w: 2,
36740
+ h: 0.4,
36741
+ draw: (c) => {
36742
+ const parts = [box(c)];
36743
+ const n = Math.max(2, Math.round(c.w / 0.3));
36744
+ for (let i = 1; i < n; i++) {
36745
+ const x = c.px(c.w * i / n);
36746
+ parts.push(line({ class: "sx-fp-furn-line", x1: x, y1: 0, x2: x, y2: c.px(c.h) }));
36747
+ }
36748
+ return parts.join("");
36749
+ }
36750
+ },
36751
+ "kidney-table": {
36752
+ w: 1.8,
36753
+ h: 1.2,
36754
+ draw: (c) => {
36755
+ const sx = c.w / 1.8;
36756
+ const sy = c.h / 1.2;
36757
+ const X = (v) => c.px(v * sx);
36758
+ const Y = (v) => c.px(v * sy);
36759
+ const d = [
36760
+ `M ${X(0)} ${Y(0.55)}`,
36761
+ `C ${X(0)} ${Y(0.15)} ${X(0.35)} ${Y(0)} ${X(0.9)} ${Y(0)}`,
36762
+ `C ${X(1.45)} ${Y(0)} ${X(1.8)} ${Y(0.15)} ${X(1.8)} ${Y(0.55)}`,
36763
+ `C ${X(1.8)} ${Y(0.95)} ${X(1.5)} ${Y(1.2)} ${X(1.18)} ${Y(1.2)}`,
36764
+ `Q ${X(0.9)} ${Y(0.82)} ${X(0.62)} ${Y(1.2)}`,
36765
+ `C ${X(0.3)} ${Y(1.2)} ${X(0)} ${Y(0.95)} ${X(0)} ${Y(0.55)}`,
36766
+ "Z"
36767
+ ].join(" ");
36768
+ return path({ class: "sx-fp-furn", d });
36769
+ }
36770
+ },
36771
+ "round-table-4": roundTable(4, 1.52),
36772
+ "round-table-6": roundTable(6, 1.52),
36773
+ "round-table-8": roundTable(8, 1.52),
36774
+ "round-table-10": roundTable(10, 1.83),
36775
+ "conference-table": { w: 2.4, h: 1.2, envelope: [CHAIR_OVERHANG, 0, CHAIR_OVERHANG, 0], draw: tableDraw(true) },
36776
+ // ── event / banquet ──
36777
+ "banquet-table": { w: 2.44, h: 0.76, envelope: [CHAIR_OVERHANG, 0, CHAIR_OVERHANG, 0], draw: tableDraw(true) },
36778
+ "head-table": { w: 3.7, h: 0.76, envelope: [0, 0, CHAIR_OVERHANG, 0], draw: tableDraw(false) },
36779
+ stage: {
36780
+ w: 4,
36781
+ h: 2,
36782
+ draw: (c) => box(c) + rect({
36783
+ class: "sx-fp-furn-dash",
36784
+ x: c.px(0.1),
36785
+ y: c.px(0.1),
36786
+ width: c.px(c.w - 0.2),
36787
+ height: c.px(c.h - 0.2)
36788
+ })
36789
+ },
36790
+ "dance-floor": {
36791
+ w: 4,
36792
+ h: 4,
36793
+ underlay: true,
36794
+ draw: (c) => {
36795
+ const parts = [];
36796
+ for (let d = 0.5; d < c.w + c.h; d += 0.5) {
36797
+ const x1 = Math.max(0, d - c.h);
36798
+ const y1 = Math.min(d, c.h);
36799
+ const x2 = Math.min(d, c.w);
36800
+ const y2 = Math.max(0, d - c.w);
36801
+ parts.push(line({ class: "sx-fp-hatch", x1: c.px(x1), y1: c.px(y1), x2: c.px(x2), y2: c.px(y2) }));
36802
+ }
36803
+ parts.push(rect({ class: "sx-fp-furn-nofill", x: 0, y: 0, width: c.px(c.w), height: c.px(c.h) }));
36804
+ return parts.join("");
36805
+ }
36806
+ },
36807
+ bar: {
36808
+ w: 3,
36809
+ h: 0.7,
36810
+ draw: (c) => box(c) + line({ class: "sx-fp-furn-line", x1: 0, y1: c.px(c.h - 0.15), x2: c.px(c.w), y2: c.px(c.h - 0.15) })
36811
+ },
36812
+ "dj-booth": { w: 1.2, h: 0.8, draw: (c) => box(c) + glyphText(c, "DJ") },
36813
+ "cocktail-table": {
36814
+ w: 0.76,
36815
+ h: 0.76,
36816
+ draw: (c) => circle({ class: "sx-fp-furn", cx: c.px(c.w / 2), cy: c.px(c.h / 2), r: c.px(Math.min(c.w, c.h) / 2) }) + circle({ class: "sx-fp-furn-line", cx: c.px(c.w / 2), cy: c.px(c.h / 2), r: c.px(Math.min(c.w, c.h) / 6) })
36817
+ },
36818
+ podium: {
36819
+ w: 0.6,
36820
+ h: 0.5,
36821
+ draw: (c) => polygon({
36822
+ class: "sx-fp-furn",
36823
+ points: `${c.px(c.w * 0.15)},0 ${c.px(c.w * 0.85)},0 ${c.px(c.w)},${c.px(c.h)} 0,${c.px(c.h)}`
36824
+ })
36825
+ },
36826
+ "row-chairs": {
36827
+ w: 2.2,
36828
+ h: 0.5,
36829
+ draw: (c) => {
36830
+ const n = Math.max(1, Math.floor(c.w / 0.55 + 1e-6));
36831
+ const parts = [];
36832
+ for (let i = 0; i < n; i++) {
36833
+ parts.push(chairAt(c.px, (i + 0.5) / n * c.w, c.h / 2, 0));
36834
+ }
36835
+ return parts.join("");
36836
+ }
36837
+ },
36838
+ // ── retail ──
36839
+ // Gondola run: a long fixture with a back-to-back spine and product bays.
36840
+ shelving: {
36841
+ w: 1.8,
36842
+ h: 0.6,
36843
+ draw: (c) => {
36844
+ const parts = [box(c)];
36845
+ parts.push(line({ class: "sx-fp-furn-line", x1: 0, y1: c.px(c.h / 2), x2: c.px(c.w), y2: c.px(c.h / 2) }));
36846
+ const n = Math.max(2, Math.round(c.w / 0.45));
36847
+ for (let i = 1; i < n; i++) {
36848
+ const x = c.px(c.w * i / n);
36849
+ parts.push(line({ class: "sx-fp-furn-line", x1: x, y1: 0, x2: x, y2: c.px(c.h) }));
36850
+ }
36851
+ return parts.join("");
36852
+ }
36853
+ },
36854
+ // POS counter with a register block and a belt line.
36855
+ checkout: {
36856
+ w: 1.6,
36857
+ h: 0.7,
36858
+ draw: (c) => [
36859
+ box(c),
36860
+ rect({ class: "sx-fp-furn-solid", x: c.px(c.w - 0.5), y: c.px(0.12), width: c.px(0.34), height: c.px(0.3), rx: c.px(0.04) }),
36861
+ line({ class: "sx-fp-furn-line", x1: c.px(0.12), y1: c.px(c.h * 0.6), x2: c.px(c.w - 0.62), y2: c.px(c.h * 0.6) })
36862
+ ].join("")
36863
+ },
36864
+ // Round garment rack: rail circle with radial hanger ticks.
36865
+ "clothing-rack": {
36866
+ w: 1,
36867
+ h: 1,
36868
+ draw: (c) => {
36869
+ const r6 = Math.min(c.w, c.h) / 2;
36870
+ const cx = c.w / 2;
36871
+ const cy = c.h / 2;
36872
+ const parts = [
36873
+ circle({ class: "sx-fp-furn-nofill", cx: c.px(cx), cy: c.px(cy), r: c.px(r6) }),
36874
+ circle({ class: "sx-fp-furn-dot", cx: c.px(cx), cy: c.px(cy), r: c.px(0.04) })
36875
+ ];
36876
+ for (const a of [0, 45, 90, 135, 180, 225, 270, 315]) {
36877
+ const rad = a * Math.PI / 180;
36878
+ parts.push(
36879
+ line({
36880
+ class: "sx-fp-furn-line",
36881
+ x1: c.px(cx + (r6 - 0.09) * Math.cos(rad)),
36882
+ y1: c.px(cy + (r6 - 0.09) * Math.sin(rad)),
36883
+ x2: c.px(cx + r6 * Math.cos(rad)),
36884
+ y2: c.px(cy + r6 * Math.sin(rad))
36885
+ })
36886
+ );
36887
+ }
36888
+ return parts.join("");
36889
+ }
36890
+ },
36891
+ // Changing booth: bench at the back, a mirror strip, a dashed curtain at the opening.
36892
+ "fitting-room": {
36893
+ w: 1.1,
36894
+ h: 1.1,
36895
+ draw: (c) => [
36896
+ rect({ class: "sx-fp-furn-nofill", x: 0, y: 0, width: c.px(c.w), height: c.px(c.h) }),
36897
+ rect({ class: "sx-fp-furn", x: c.px(0.12), y: c.px(0.12), width: c.px(c.w - 0.24), height: c.px(0.28), rx: c.px(0.04) }),
36898
+ line({ class: "sx-fp-furn-dash", x1: 0, y1: c.px(c.h), x2: c.px(c.w), y2: c.px(c.h) }),
36899
+ rect({ class: "sx-fp-furn-solid", x: c.px(c.w - 0.06), y: c.px(c.h * 0.5), width: c.px(0.04), height: c.px(c.h * 0.35) })
36900
+ ].join("")
36901
+ },
36902
+ // ── warehouse / industrial ──
36903
+ // Pallet racking: an open frame with bay dividers and cross-bracing.
36904
+ "pallet-rack": {
36905
+ w: 2.7,
36906
+ h: 1.1,
36907
+ draw: (c) => {
36908
+ const parts = [rect({ class: "sx-fp-furn-nofill", x: 0, y: 0, width: c.px(c.w), height: c.px(c.h) })];
36909
+ const bays = Math.max(2, Math.round(c.w / 1.35));
36910
+ for (let i = 0; i < bays; i++) {
36911
+ const x0 = c.w * i / bays;
36912
+ const x1 = c.w * (i + 1) / bays;
36913
+ if (i > 0) parts.push(line({ class: "sx-fp-furn-line", x1: c.px(x0), y1: 0, x2: c.px(x0), y2: c.px(c.h) }));
36914
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(x0), y1: 0, x2: c.px(x1), y2: c.px(c.h) }));
36915
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(x1), y1: 0, x2: c.px(x0), y2: c.px(c.h) }));
36916
+ }
36917
+ return parts.join("");
36918
+ }
36919
+ },
36920
+ // Dock door: roll-up door segments with two bumpers at the outer face.
36921
+ "loading-dock": {
36922
+ w: 3,
36923
+ h: 0.6,
36924
+ draw: (c) => {
36925
+ const parts = [box(c)];
36926
+ const n = Math.max(3, Math.round(c.w / 0.5));
36927
+ for (let i = 1; i < n; i++) {
36928
+ const x = c.px(c.w * i / n);
36929
+ parts.push(line({ class: "sx-fp-furn-line", x1: x, y1: 0, x2: x, y2: c.px(c.h) }));
36930
+ }
36931
+ parts.push(rect({ class: "sx-fp-furn-solid", x: c.px(0.12), y: c.px(c.h - 0.12), width: c.px(0.3), height: c.px(0.1) }));
36932
+ parts.push(rect({ class: "sx-fp-furn-solid", x: c.px(c.w - 0.42), y: c.px(c.h - 0.12), width: c.px(0.3), height: c.px(0.1) }));
36933
+ return parts.join("");
36934
+ }
36935
+ },
36936
+ // Counterbalance forklift silhouette: body, mast forks at the front, operator seat.
36937
+ forklift: {
36938
+ w: 1.2,
36939
+ h: 2.2,
36940
+ draw: (c) => [
36941
+ rect({ class: "sx-fp-furn", x: c.px(0.15), y: c.px(0.42), width: c.px(c.w - 0.3), height: c.px(c.h - 0.72), rx: c.px(0.06) }),
36942
+ rect({ class: "sx-fp-furn-solid", x: c.px(0.25), y: 0, width: c.px(0.12), height: c.px(0.42) }),
36943
+ rect({ class: "sx-fp-furn-solid", x: c.px(c.w - 0.37), y: 0, width: c.px(0.12), height: c.px(0.42) }),
36944
+ circle({ class: "sx-fp-furn-line", cx: c.px(c.w / 2), cy: c.px(c.h - 0.62), r: c.px(0.18) })
36945
+ ].join("")
36946
+ },
36947
+ // ── salon / spa ──
36948
+ // Styling station: back counter with a mirror strip and a chair facing it.
36949
+ "salon-chair": {
36950
+ w: 0.8,
36951
+ h: 1.4,
36952
+ draw: (c) => [
36953
+ rect({ class: "sx-fp-furn", x: 0, y: 0, width: c.px(c.w), height: c.px(0.32), rx: c.px(0.03) }),
36954
+ rect({ class: "sx-fp-furn-solid", x: c.px(0.08), y: c.px(0.05), width: c.px(c.w - 0.16), height: c.px(0.05) }),
36955
+ circle({ class: "sx-fp-furn", cx: c.px(c.w / 2), cy: c.px(c.h * 0.64), r: c.px(Math.min(c.w, 0.62) / 2) }),
36956
+ circle({ class: "sx-fp-furn-line", cx: c.px(c.w / 2), cy: c.px(c.h * 0.64), r: c.px(0.08) })
36957
+ ].join("")
36958
+ },
36959
+ // Backwash unit: a reclining chair with a wash basin at the head end.
36960
+ "shampoo-bowl": {
36961
+ w: 0.9,
36962
+ h: 1.5,
36963
+ draw: (c) => [
36964
+ rect({ class: "sx-fp-furn", x: c.px(0.1), y: c.px(0.5), width: c.px(c.w - 0.2), height: c.px(c.h - 0.6), rx: c.px(0.08) }),
36965
+ circle({ class: "sx-fp-furn", cx: c.px(c.w / 2), cy: c.px(0.4), r: c.px(0.32) }),
36966
+ circle({ class: "sx-fp-furn-line", cx: c.px(c.w / 2), cy: c.px(0.4), r: c.px(0.12) }),
36967
+ circle({ class: "sx-fp-furn-dot", cx: c.px(c.w / 2), cy: c.px(0.12), r: c.px(0.04) })
36968
+ ].join("")
36969
+ },
36970
+ // Manicure table: a small table with a client and a technician chair.
36971
+ "manicure-table": {
36972
+ w: 1,
36973
+ h: 0.5,
36974
+ envelope: [CHAIR_GAP + CHAIR_D, 0, CHAIR_GAP + CHAIR_D, 0],
36975
+ draw: (c) => [box(c), chairAt(c.px, c.w / 2, -CHAIR_GAP, 0), chairAt(c.px, c.w / 2, c.h + CHAIR_GAP, 180)].join("")
36976
+ },
36977
+ // ── gym / fitness ──
36978
+ // Treadmill: a deck with a running belt and a console at the front.
36979
+ treadmill: {
36980
+ w: 0.9,
36981
+ h: 2,
36982
+ draw: (c) => [
36983
+ box(c, "sx-fp-furn", 0.05),
36984
+ rect({ class: "sx-fp-furn-line", x: c.px(0.12), y: c.px(0.5), width: c.px(c.w - 0.24), height: c.px(c.h - 0.65), rx: c.px(0.04) }),
36985
+ rect({ class: "sx-fp-furn-solid", x: c.px(0.1), y: c.px(0.08), width: c.px(c.w - 0.2), height: c.px(0.16), rx: c.px(0.03) })
36986
+ ].join("")
36987
+ },
36988
+ // Flat bench with upright posts and a loaded barbell crossing it.
36989
+ "weight-bench": {
36990
+ w: 0.6,
36991
+ h: 1.8,
36992
+ envelope: [0, 0.35, 0, 0.35],
36993
+ draw: (c) => [
36994
+ rect({ class: "sx-fp-furn", x: c.px(c.w / 2 - 0.12), y: c.px(0.3), width: c.px(0.24), height: c.px(c.h - 0.4), rx: c.px(0.05) }),
36995
+ rect({ class: "sx-fp-furn-solid", x: c.px(0.06), y: c.px(0.18), width: c.px(0.1), height: c.px(0.1) }),
36996
+ rect({ class: "sx-fp-furn-solid", x: c.px(c.w - 0.16), y: c.px(0.18), width: c.px(0.1), height: c.px(0.1) }),
36997
+ line({ class: "sx-fp-furn-line", x1: c.px(-0.3), y1: c.px(0.23), x2: c.px(c.w + 0.3), y2: c.px(0.23) })
36998
+ ].join("")
36999
+ },
37000
+ // Power rack: a square frame with four corner posts and a barbell.
37001
+ "power-rack": {
37002
+ w: 1.4,
37003
+ h: 1.4,
37004
+ envelope: [0, 0.3, 0, 0.3],
37005
+ draw: (c) => {
37006
+ const post = 0.14;
37007
+ const corners = [
37008
+ [0, 0],
37009
+ [c.w - post, 0],
37010
+ [0, c.h - post],
37011
+ [c.w - post, c.h - post]
37012
+ ];
37013
+ const parts = [rect({ class: "sx-fp-furn-nofill", x: 0, y: 0, width: c.px(c.w), height: c.px(c.h) })];
37014
+ for (const [px0, py0] of corners) {
37015
+ parts.push(rect({ class: "sx-fp-furn-solid", x: c.px(px0), y: c.px(py0), width: c.px(post), height: c.px(post) }));
37016
+ }
37017
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(-0.28), y1: c.px(c.h * 0.4), x2: c.px(c.w + 0.28), y2: c.px(c.h * 0.4) }));
37018
+ return parts.join("");
37019
+ }
37020
+ },
37021
+ // Exercise mat — an underlay surface, like a rug.
37022
+ "yoga-mat": {
37023
+ w: 0.6,
37024
+ h: 1.8,
37025
+ underlay: true,
37026
+ draw: (c) => rect({ class: "sx-fp-furn-dash", x: 0, y: 0, width: c.px(c.w), height: c.px(c.h), rx: c.px(0.08) })
37027
+ },
37028
+ // ── site / outdoor ──
37029
+ // Tree in plan: a canopy disc with a foliage ring and a trunk dot.
37030
+ tree: {
37031
+ w: 2,
37032
+ h: 2,
37033
+ draw: (c) => {
37034
+ const r6 = Math.min(c.w, c.h) / 2;
37035
+ return [
37036
+ circle({ class: "sx-fp-furn", cx: c.px(c.w / 2), cy: c.px(c.h / 2), r: c.px(r6) }),
37037
+ circle({ class: "sx-fp-furn-line", cx: c.px(c.w / 2), cy: c.px(c.h / 2), r: c.px(r6 * 0.6) }),
37038
+ circle({ class: "sx-fp-furn-dot", cx: c.px(c.w / 2), cy: c.px(c.h / 2), r: c.px(0.07) })
37039
+ ].join("");
37040
+ }
37041
+ },
37042
+ // Car in plan (parking-stall footprint): body, glazing lines, four wheels.
37043
+ car: {
37044
+ w: 1.8,
37045
+ h: 4.4,
37046
+ draw: (c) => {
37047
+ const parts = [rect({ class: "sx-fp-furn", x: c.px(0.12), y: c.px(0.1), width: c.px(c.w - 0.24), height: c.px(c.h - 0.2), rx: c.px(0.35) })];
37048
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(0.3), y1: c.px(c.h * 0.26), x2: c.px(c.w - 0.3), y2: c.px(c.h * 0.26) }));
37049
+ parts.push(line({ class: "sx-fp-furn-line", x1: c.px(0.3), y1: c.px(c.h * 0.72), x2: c.px(c.w - 0.3), y2: c.px(c.h * 0.72) }));
37050
+ for (const wy of [c.h * 0.3, c.h * 0.7]) {
37051
+ parts.push(rect({ class: "sx-fp-furn-solid", x: 0, y: c.px(wy - 0.18), width: c.px(0.14), height: c.px(0.36), rx: c.px(0.03) }));
37052
+ parts.push(rect({ class: "sx-fp-furn-solid", x: c.px(c.w - 0.14), y: c.px(wy - 0.18), width: c.px(0.14), height: c.px(0.36), rx: c.px(0.03) }));
37053
+ }
37054
+ return parts.join("");
37055
+ }
37056
+ }
37057
+ };
37058
+ var FURNITURE_TYPES = Object.keys(FLOORPLAN_SYMBOLS);
37059
+
37060
+ // src/diagrams/floorplan/parser.ts
37061
+ var FloorplanParseError = class extends Error {
37062
+ line;
37063
+ constructor(message, line2) {
37064
+ super(`line ${line2}: ${message}`);
37065
+ this.name = "FloorplanParseError";
37066
+ this.line = line2;
37067
+ }
37068
+ };
37069
+ var isStr = (t) => t !== void 0 && "str" in t;
37070
+ var isWord = (t, w) => t !== void 0 && "word" in t && (w === void 0 || t.word === w);
37071
+ function normalizeQuotes3(line2) {
37072
+ return line2.replace(/[“”「」『』]/g, '"').replace(/[‘’]/g, "'");
37073
+ }
37074
+ function tokenize7(line2) {
37075
+ const out = [];
37076
+ const re = /"([^"]*)"|(\S+)/g;
37077
+ let m;
37078
+ while (m = re.exec(line2)) {
37079
+ if (m[1] !== void 0) out.push({ str: m[1] });
37080
+ else out.push({ word: m[2] });
37081
+ }
37082
+ return out;
37083
+ }
37084
+ function parseNum(t, what, ln) {
37085
+ if (!isWord(t)) throw new FloorplanParseError(`expected a number for ${what}`, ln);
37086
+ const v = Number(t.word);
37087
+ if (!Number.isFinite(v)) throw new FloorplanParseError(`expected a number for ${what}, got "${t.word}"`, ln);
37088
+ return v;
37089
+ }
37090
+ function parseCoord2(t, what, ln) {
37091
+ if (!isWord(t)) throw new FloorplanParseError(`expected "x,y" for ${what}`, ln);
37092
+ const m = /^(-?\d*\.?\d+),(-?\d*\.?\d+)$/.exec(t.word);
37093
+ if (!m) throw new FloorplanParseError(`expected "x,y" for ${what}, got "${t.word}"`, ln);
37094
+ return { x: Number(m[1]), y: Number(m[2]) };
37095
+ }
37096
+ function parseDims(t, what, ln) {
37097
+ if (!isWord(t)) throw new FloorplanParseError(`expected "WxH" for ${what}`, ln);
37098
+ const m = /^(\d*\.?\d+)x(\d*\.?\d+)$/i.exec(t.word);
37099
+ if (!m) throw new FloorplanParseError(`expected "WxH" for ${what}, got "${t.word}"`, ln);
37100
+ return { w: Number(m[1]), h: Number(m[2]) };
37101
+ }
37102
+ function parsePct(t, ln) {
37103
+ if (!isWord(t)) throw new FloorplanParseError(`expected a percentage for "at"`, ln);
37104
+ const v = Number(t.word.replace(/%$/, ""));
37105
+ if (!Number.isFinite(v)) throw new FloorplanParseError(`expected a percentage for "at", got "${t.word}"`, ln);
37106
+ return v;
37107
+ }
37108
+ function parseId(t, what, ln) {
37109
+ if (!isWord(t)) throw new FloorplanParseError(`expected ${what}`, ln);
37110
+ return t.word;
37111
+ }
37112
+ var SIDES = ["north", "south", "east", "west"];
37113
+ var REL_HOW = ["right-of", "left-of", "above", "below"];
37114
+ function parseFurnitureType(t, ln) {
37115
+ const word = isWord(t) ? t.word : "";
37116
+ if (!FURNITURE_TYPES.includes(word)) {
37117
+ throw new FloorplanParseError(
37118
+ `unknown furniture type "${word}". Valid types: ${FURNITURE_TYPES.join(", ")}`,
37119
+ ln
37120
+ );
37121
+ }
37122
+ return word;
37123
+ }
37124
+ function parseHeader3(tok, ast, ln) {
37125
+ while (tok.length) {
37126
+ const t = tok.shift();
37127
+ if (isStr(t)) ast.title = t.str;
37128
+ else if (t.word === "unit") {
37129
+ const u = parseId(tok.shift(), "unit (m|ft)", ln);
37130
+ if (u !== "m" && u !== "ft") throw new FloorplanParseError(`unit must be "m" or "ft", got "${u}"`, ln);
37131
+ ast.unit = u;
37132
+ } else throw new FloorplanParseError(`floorplan: unexpected token "${t.word}"`, ln);
37133
+ }
37134
+ }
37135
+ function parseRoom(tok, ast, ln) {
37136
+ const id = parseId(tok.shift(), "a room id", ln);
37137
+ if (ast.rooms.some((r6) => r6.id === id)) {
37138
+ throw new FloorplanParseError(`duplicate room id "${id}"`, ln);
37139
+ }
37140
+ const room = { id, label: id, w: 4, h: 3, line: ln };
37141
+ while (tok.length) {
37142
+ const t = tok.shift();
37143
+ if (isStr(t)) room.label = t.str;
37144
+ else if (t.word === "at") room.at = parseCoord2(tok.shift(), "at", ln);
37145
+ else if (REL_HOW.includes(t.word)) {
37146
+ room.rel = {
37147
+ how: t.word,
37148
+ ref: parseId(tok.shift(), `a room id after "${t.word}"`, ln),
37149
+ offset: void 0,
37150
+ align: void 0
37151
+ };
37152
+ } else if (t.word === "offset") {
37153
+ if (!room.rel) throw new FloorplanParseError(`"offset" requires a relative placement (right-of/left-of/above/below)`, ln);
37154
+ room.rel.offset = parseNum(tok.shift(), "offset", ln);
37155
+ } else if (t.word === "align") {
37156
+ if (!room.rel) throw new FloorplanParseError(`"align" requires a relative placement (right-of/left-of/above/below)`, ln);
37157
+ const a = parseId(tok.shift(), "align (start|center|end)", ln);
37158
+ if (a !== "start" && a !== "center" && a !== "end") {
37159
+ throw new FloorplanParseError(`align must be start|center|end, got "${a}"`, ln);
37160
+ }
37161
+ room.rel.align = a;
37162
+ } else if (t.word === "size") {
37163
+ const d = parseDims(tok.shift(), "size", ln);
37164
+ room.w = d.w;
37165
+ room.h = d.h;
37166
+ } else if (t.word === "fill") room.fill = parseId(tok.shift(), "a fill color", ln);
37167
+ else if (t.word === "nolabel") room.nolabel = true;
37168
+ else throw new FloorplanParseError(`room: unexpected token "${t.word}"`, ln);
37169
+ }
37170
+ ast.rooms.push(room);
37171
+ }
37172
+ function parseExtend(tok, ast, ln) {
37173
+ const room = parseId(tok.shift(), "a room id", ln);
37174
+ const ext = { room, w: 2, h: 2, line: ln };
37175
+ while (tok.length) {
37176
+ const t = tok.shift();
37177
+ if (!isWord(t)) throw new FloorplanParseError(`extend: unexpected string "${t.str}"`, ln);
37178
+ else if (t.word === "at") ext.at = parseCoord2(tok.shift(), "at", ln);
37179
+ else if (REL_HOW.includes(t.word)) {
37180
+ ext.rel = {
37181
+ how: t.word,
37182
+ ref: parseId(tok.shift(), `a room id after "${t.word}"`, ln),
37183
+ offset: void 0,
37184
+ align: void 0
37185
+ };
37186
+ } else if (t.word === "offset") {
37187
+ if (!ext.rel) throw new FloorplanParseError(`"offset" requires a relative placement (right-of/left-of/above/below)`, ln);
37188
+ ext.rel.offset = parseNum(tok.shift(), "offset", ln);
37189
+ } else if (t.word === "align") {
37190
+ if (!ext.rel) throw new FloorplanParseError(`"align" requires a relative placement (right-of/left-of/above/below)`, ln);
37191
+ const a = parseId(tok.shift(), "align (start|center|end)", ln);
37192
+ if (a !== "start" && a !== "center" && a !== "end") {
37193
+ throw new FloorplanParseError(`align must be start|center|end, got "${a}"`, ln);
37194
+ }
37195
+ ext.rel.align = a;
37196
+ } else if (t.word === "size") {
37197
+ const d = parseDims(tok.shift(), "size", ln);
37198
+ ext.w = d.w;
37199
+ ext.h = d.h;
37200
+ } else throw new FloorplanParseError(`extend: unexpected token "${t.word}"`, ln);
37201
+ }
37202
+ ast.extensions.push(ext);
37203
+ }
37204
+ var DOOR_TYPES = ["single", "double", "sliding", "pocket", "bifold"];
37205
+ var WINDOW_TYPES = ["fixed", "sliding", "casement", "bay"];
37206
+ function parseOpening(kind, tok, ast, ln) {
37207
+ const op = {
37208
+ kind,
37209
+ pct: 50,
37210
+ width: 0,
37211
+ // resolved after the form is known
37212
+ hinge: "left",
37213
+ swing: "in",
37214
+ doorType: "single",
37215
+ windowType: "fixed",
37216
+ line: ln
37217
+ };
37218
+ const t0 = tok.shift();
37219
+ if (isWord(t0, "between")) {
37220
+ op.between = [parseId(tok.shift(), "a room id", ln), parseId(tok.shift(), "a second room id", ln)];
37221
+ } else {
37222
+ op.room = parseId(t0, `a room id or "between"`, ln);
37223
+ const side = parseId(tok.shift(), "a wall side", ln);
37224
+ if (!SIDES.includes(side)) {
37225
+ throw new FloorplanParseError(`expected a wall side north|south|east|west, got "${side}"`, ln);
37226
+ }
37227
+ op.side = side;
37228
+ }
37229
+ while (tok.length) {
37230
+ const t = tok.shift();
37231
+ if (!isWord(t)) throw new FloorplanParseError(`${kind}: unexpected string "${t.str}"`, ln);
37232
+ else if (t.word === "at") op.pct = parsePct(tok.shift(), ln);
37233
+ else if (t.word === "width") op.width = parseNum(tok.shift(), "width", ln);
37234
+ else if (t.word === "hinge") {
37235
+ const h = parseId(tok.shift(), "hinge (left|right)", ln);
37236
+ if (h !== "left" && h !== "right") throw new FloorplanParseError(`hinge must be left|right, got "${h}"`, ln);
37237
+ op.hinge = h;
37238
+ } else if (t.word === "swing") {
37239
+ const s = parseId(tok.shift(), "swing (in|out)", ln);
37240
+ if (s !== "in" && s !== "out") throw new FloorplanParseError(`swing must be in|out, got "${s}"`, ln);
37241
+ op.swing = s;
37242
+ } else if (t.word === "type") {
37243
+ const d = parseId(tok.shift(), `${kind} type`, ln);
37244
+ if (kind === "window") {
37245
+ if (!WINDOW_TYPES.includes(d)) {
37246
+ throw new FloorplanParseError(`window type must be ${WINDOW_TYPES.join("|")}, got "${d}"`, ln);
37247
+ }
37248
+ op.windowType = d;
37249
+ } else {
37250
+ if (!DOOR_TYPES.includes(d)) {
37251
+ throw new FloorplanParseError(`door type must be ${DOOR_TYPES.join("|")}, got "${d}"`, ln);
37252
+ }
37253
+ op.doorType = d;
37254
+ }
37255
+ } else throw new FloorplanParseError(`${kind}: unexpected token "${t.word}"`, ln);
37256
+ }
37257
+ if (op.width === 0) {
37258
+ op.width = kind === "window" ? 1.2 : kind === "opening" ? 1 : op.between ? 0.8 : 0.9;
37259
+ }
37260
+ ast.openings.push(op);
37261
+ }
37262
+ function parseFurniture(tok, ast, ln) {
37263
+ const type = parseFurnitureType(tok.shift(), ln);
37264
+ const f = { type, x: 0, y: 0, rotate: 0, line: ln };
37265
+ while (tok.length) {
37266
+ const t = tok.shift();
37267
+ if (isStr(t)) f.label = t.str;
37268
+ else if (t.word === "in") f.room = parseId(tok.shift(), `a room id after "in"`, ln);
37269
+ else if (t.word === "at") {
37270
+ const c = parseCoord2(tok.shift(), "at", ln);
37271
+ f.x = c.x;
37272
+ f.y = c.y;
37273
+ } else if (t.word === "size") f.size = parseDims(tok.shift(), "size", ln);
37274
+ else if (t.word === "rotate") f.rotate = parseNum(tok.shift(), "rotate", ln);
37275
+ else throw new FloorplanParseError(`furniture: unexpected token "${t.word}"`, ln);
37276
+ }
37277
+ ast.furniture.push(f);
37278
+ }
37279
+ function parseArray(mode, tok, ast, ln) {
37280
+ const type = parseFurnitureType(tok.shift(), ln);
37281
+ const a = {
37282
+ mode,
37283
+ type,
37284
+ rows: 1,
37285
+ cols: 1,
37286
+ count: Infinity,
37287
+ rotate: 0,
37288
+ line: ln
37289
+ };
37290
+ while (tok.length) {
37291
+ const t = tok.shift();
37292
+ if (!isWord(t)) throw new FloorplanParseError(`${mode}: unexpected string "${t.str}"`, ln);
37293
+ else if (t.word === "in") a.room = parseId(tok.shift(), `a room id after "in"`, ln);
37294
+ else if (t.word === "rows") a.rows = parseNum(tok.shift(), "rows", ln);
37295
+ else if (t.word === "cols") a.cols = parseNum(tok.shift(), "cols", ln);
37296
+ else if (t.word === "count") a.count = parseNum(tok.shift(), "count", ln);
37297
+ else if (t.word === "area") {
37298
+ a.p1 = parseCoord2(tok.shift(), "area p1", ln);
37299
+ a.p2 = parseCoord2(tok.shift(), "area p2", ln);
37300
+ } else if (t.word === "itemsize") a.itemsize = parseDims(tok.shift(), "itemsize", ln);
37301
+ else if (t.word === "rotate") a.rotate = parseNum(tok.shift(), "rotate", ln);
37302
+ else if (t.word === "center") a.center = parseCoord2(tok.shift(), "center", ln);
37303
+ else if (t.word === "radius") a.radius = parseNum(tok.shift(), "radius", ln);
37304
+ else if (t.word === "from") a.fromDeg = parseNum(tok.shift(), "from", ln);
37305
+ else if (t.word === "to") a.toDeg = parseNum(tok.shift(), "to", ln);
37306
+ else throw new FloorplanParseError(`${mode}: unexpected token "${t.word}"`, ln);
37307
+ }
37308
+ ast.arrays.push(a);
37309
+ }
37310
+ function parseFloorplan(text2) {
37311
+ const ast = {
37312
+ type: "floorplan",
37313
+ title: "Floor Plan",
37314
+ unit: "m",
37315
+ rooms: [],
37316
+ extensions: [],
37317
+ openings: [],
37318
+ furniture: [],
37319
+ arrays: []
37320
+ };
37321
+ let sawHeader = false;
37322
+ const lines = text2.split(/\r?\n/);
37323
+ for (let i = 0; i < lines.length; i++) {
37324
+ const ln = i + 1;
37325
+ const raw = normalizeQuotes3(lines[i]).trim();
37326
+ if (!raw) continue;
37327
+ const all = tokenize7(raw);
37328
+ const tok = [];
37329
+ for (let k = 0; k < all.length; k++) {
37330
+ const t = all[k];
37331
+ if (isWord(t) && (t.word.startsWith("#") || t.word.startsWith("//"))) {
37332
+ const prev = all[k - 1];
37333
+ if (t.word.startsWith("#") && isWord(prev, "fill")) {
37334
+ tok.push(t);
37335
+ continue;
37336
+ }
37337
+ break;
37338
+ }
37339
+ tok.push(t);
37340
+ }
37341
+ if (tok.length === 0) continue;
37342
+ const head = tok.shift();
37343
+ if (!isWord(head)) throw new FloorplanParseError(`unexpected string at line start`, ln);
37344
+ const kw = head.word.toLowerCase();
37345
+ if (kw === "floorplan") {
37346
+ parseHeader3(tok, ast, ln);
37347
+ sawHeader = true;
37348
+ } else if (!sawHeader) {
37349
+ throw new FloorplanParseError(`the first statement must be the "floorplan" header`, ln);
37350
+ } else if (kw === "room") parseRoom(tok, ast, ln);
37351
+ else if (kw === "north") {
37352
+ ast.north = tok.length ? parseNum(tok.shift(), "north rotation (degrees)", ln) : 0;
37353
+ if (tok.length) throw new FloorplanParseError(`north: unexpected trailing tokens`, ln);
37354
+ } else if (kw === "extend") parseExtend(tok, ast, ln);
37355
+ else if (kw === "door" || kw === "window" || kw === "opening") parseOpening(kw, tok, ast, ln);
37356
+ else if (kw === "furniture") parseFurniture(tok, ast, ln);
37357
+ else if (kw === "grid" || kw === "row" || kw === "arc") parseArray(kw, tok, ast, ln);
37358
+ else {
37359
+ throw new FloorplanParseError(
37360
+ `unknown keyword "${kw}". Expected: floorplan, room, extend, door, window, opening, furniture, grid, row, arc`,
37361
+ ln
37362
+ );
37363
+ }
37364
+ }
37365
+ return ast;
37366
+ }
37367
+
37368
+ // src/diagrams/floorplan/layout.ts
37369
+ var FT = 0.3048;
37370
+ var FLOORPLAN_CONST = {
37371
+ /** Wall band thickness, meters (§5). */
37372
+ wallT: 0.2,
37373
+ /** Default render scale, px per meter (§4.1). */
37374
+ scale: 55,
37375
+ /** Jamb margin an opening keeps from the segment ends, meters (§4.3). */
37376
+ jamb: 0.05,
37377
+ /** Dimension band depth outside the plan, meters. */
37378
+ dimBand: 1,
37379
+ /** Outer padding, meters. */
37380
+ pad: 0.45,
37381
+ /** Offsets of the major / minor dimension rows from the plan edge, meters. */
37382
+ dimMajorOff: 0.62,
37383
+ dimMinorOff: 0.3
37384
+ };
37385
+ function formatLength(m, unit) {
37386
+ if (unit === "m") {
37387
+ const v = Math.round(m * 100) / 100;
37388
+ return `${v} m`;
37389
+ }
37390
+ const ftv = m / FT;
37391
+ let f = Math.floor(ftv + 1e-6);
37392
+ let inches = Math.round((ftv - f) * 12);
37393
+ if (inches >= 12) {
37394
+ f += 1;
37395
+ inches = 0;
37396
+ }
37397
+ return inches ? `${f}'${inches}"` : `${f}'`;
37398
+ }
37399
+ function formatArea(areaM2, unit) {
37400
+ if (unit === "m") return `${areaM2.toFixed(1)} m\xB2`;
37401
+ return `${Math.round(areaM2 / (FT * FT))} sq ft`;
37402
+ }
37403
+ var fmtNum = (v) => String(Math.round(v * 100) / 100);
37404
+ var snap = (v) => Math.round(v * 1e6) / 1e6;
37405
+ var ADJ_EPS = 0.051;
37406
+ var MIN_OVERLAP = 0.3;
37407
+ function rectSharedEdge(a, b) {
37408
+ if (Math.abs(a.x + a.w - b.x) < ADJ_EPS || Math.abs(b.x + b.w - a.x) < ADJ_EPS) {
37409
+ const along = Math.abs(a.x + a.w - b.x) < ADJ_EPS ? a.x + a.w : b.x + b.w;
37410
+ const lo = Math.max(a.y, b.y);
37411
+ const hi = Math.min(a.y + a.h, b.y + b.h);
37412
+ if (hi - lo >= MIN_OVERLAP) return { vertical: true, along, lo, hi };
37413
+ }
37414
+ if (Math.abs(a.y + a.h - b.y) < ADJ_EPS || Math.abs(b.y + b.h - a.y) < ADJ_EPS) {
37415
+ const along = Math.abs(a.y + a.h - b.y) < ADJ_EPS ? a.y + a.h : b.y + b.h;
37416
+ const lo = Math.max(a.x, b.x);
37417
+ const hi = Math.min(a.x + a.w, b.x + b.w);
37418
+ if (hi - lo >= MIN_OVERLAP) return { vertical: false, along, lo, hi };
37419
+ }
37420
+ return null;
37421
+ }
37422
+ function roomSharedEdge(a, b) {
37423
+ let best = null;
37424
+ for (const pa of a.parts) {
37425
+ for (const pb of b.parts) {
37426
+ const e = rectSharedEdge(pa, pb);
37427
+ if (e && (!best || e.hi - e.lo > best.edge.hi - best.edge.lo)) {
37428
+ best = { edge: e, aPart: pa, bPart: pb };
37429
+ }
37430
+ }
37431
+ }
37432
+ return best;
37433
+ }
37434
+ function rectOverlap(a, b) {
37435
+ return {
37436
+ ox: Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x),
37437
+ oy: Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y)
37438
+ };
37439
+ }
37440
+ function obbCorners(x, y, w, h, rotDeg, margins) {
37441
+ const [mt, mr, mb, ml] = margins;
37442
+ const cx = x + w / 2;
37443
+ const cy = y + h / 2;
37444
+ const corners = [
37445
+ [x - ml, y - mt],
37446
+ [x + w + mr, y - mt],
37447
+ [x + w + mr, y + h + mb],
37448
+ [x - ml, y + h + mb]
37449
+ ];
37450
+ if (!rotDeg) return corners;
37451
+ const rad = rotDeg * Math.PI / 180;
37452
+ const cos = Math.cos(rad);
37453
+ const sin = Math.sin(rad);
37454
+ return corners.map(([px, py]) => {
37455
+ const dx = px - cx;
37456
+ const dy = py - cy;
37457
+ return [cx + dx * cos - dy * sin, cy + dx * sin + dy * cos];
37458
+ });
37459
+ }
37460
+ function rotatedAabb(x, y, w, h, rotDeg, margins) {
37461
+ const corners = obbCorners(x, y, w, h, rotDeg, margins);
37462
+ let minX = Infinity;
37463
+ let minY = Infinity;
37464
+ let maxX = -Infinity;
37465
+ let maxY = -Infinity;
37466
+ for (const [px, py] of corners) {
37467
+ minX = Math.min(minX, px);
37468
+ minY = Math.min(minY, py);
37469
+ maxX = Math.max(maxX, px);
37470
+ maxY = Math.max(maxY, py);
37471
+ }
37472
+ return { minX, minY, maxX, maxY };
37473
+ }
37474
+ function obbPenetration(a, b) {
37475
+ let minPen = Infinity;
37476
+ for (const poly of [a, b]) {
37477
+ for (let i = 0; i < 4; i++) {
37478
+ const j = (i + 1) % 4;
37479
+ let ax = poly[j][1] - poly[i][1];
37480
+ let ay = poly[i][0] - poly[j][0];
37481
+ const len = Math.hypot(ax, ay);
37482
+ if (len < 1e-12) continue;
37483
+ ax /= len;
37484
+ ay /= len;
37485
+ let aLo = Infinity;
37486
+ let aHi = -Infinity;
37487
+ for (const [px, py] of a) {
37488
+ const v = px * ax + py * ay;
37489
+ aLo = Math.min(aLo, v);
37490
+ aHi = Math.max(aHi, v);
37491
+ }
37492
+ let bLo = Infinity;
37493
+ let bHi = -Infinity;
37494
+ for (const [px, py] of b) {
37495
+ const v = px * ax + py * ay;
37496
+ bLo = Math.min(bLo, v);
37497
+ bHi = Math.max(bHi, v);
37498
+ }
37499
+ const pen = Math.min(aHi, bHi) - Math.max(aLo, bLo);
37500
+ if (pen <= 0) return 0;
37501
+ minPen = Math.min(minPen, pen);
37502
+ }
37503
+ }
37504
+ return minPen === Infinity ? 0 : minPen;
37505
+ }
37506
+ function resolvePlacement(p, w, h, byId, rooms, u, who, errors) {
37507
+ if (p.at) return { x: snap(p.at.x * u), y: snap(p.at.y * u) };
37508
+ if (p.rel) {
37509
+ const refIdx = byId.get(p.rel.ref);
37510
+ if (refIdx === void 0) {
37511
+ errors.push(`${who}: unknown reference room "${p.rel.ref}" \u2014 declare it first`);
37512
+ return null;
37513
+ }
37514
+ const ref = rooms[refIdx];
37515
+ const off = (p.rel.offset ?? 0) * u;
37516
+ const alignPos = (refStart, refLen, len) => {
37517
+ if (p.rel.align === "center") return refStart + (refLen - len) / 2 + off;
37518
+ if (p.rel.align === "end") return refStart + refLen - len + off;
37519
+ return refStart + off;
37520
+ };
37521
+ let x = 0;
37522
+ let y = 0;
37523
+ switch (p.rel.how) {
37524
+ case "right-of":
37525
+ x = ref.x + ref.w;
37526
+ y = alignPos(ref.y, ref.h, h);
37527
+ break;
37528
+ case "left-of":
37529
+ x = ref.x - w;
37530
+ y = alignPos(ref.y, ref.h, h);
37531
+ break;
37532
+ case "below":
37533
+ y = ref.y + ref.h;
37534
+ x = alignPos(ref.x, ref.w, w);
37535
+ break;
37536
+ case "above":
37537
+ y = ref.y - h;
37538
+ x = alignPos(ref.x, ref.w, w);
37539
+ break;
37540
+ }
37541
+ return { x: snap(x), y: snap(y) };
37542
+ }
37543
+ return { x: 0, y: 0 };
37544
+ }
37545
+ function refreshRoomBounds(room, unit) {
37546
+ let minX = Infinity;
37547
+ let minY = Infinity;
37548
+ let maxX = -Infinity;
37549
+ let maxY = -Infinity;
37550
+ let area = 0;
37551
+ for (const p of room.parts) {
37552
+ minX = Math.min(minX, p.x);
37553
+ minY = Math.min(minY, p.y);
37554
+ maxX = Math.max(maxX, p.x + p.w);
37555
+ maxY = Math.max(maxY, p.y + p.h);
37556
+ area += p.w * p.h;
37557
+ }
37558
+ room.x = snap(minX);
37559
+ room.y = snap(minY);
37560
+ room.w = snap(maxX - minX);
37561
+ room.h = snap(maxY - minY);
37562
+ room.areaM2 = area;
37563
+ room.areaText = formatArea(area, unit);
37564
+ }
37565
+ function subtractIntervals(lo, hi, cuts) {
37566
+ let pieces = [[lo, hi]];
37567
+ for (const [cLo, cHi] of cuts) {
37568
+ const next = [];
37569
+ for (const [pLo, pHi] of pieces) {
37570
+ if (cHi <= pLo + 1e-9 || cLo >= pHi - 1e-9) {
37571
+ next.push([pLo, pHi]);
37572
+ continue;
37573
+ }
37574
+ if (cLo > pLo + 1e-9) next.push([pLo, Math.min(cLo, pHi)]);
37575
+ if (cHi < pHi - 1e-9) next.push([Math.max(cHi, pLo), pHi]);
37576
+ }
37577
+ pieces = next;
37578
+ }
37579
+ return pieces.filter(([a, b]) => b - a > 1e-9);
37580
+ }
37581
+ function sideSegments(room, side) {
37582
+ const segs = [];
37583
+ for (const p of room.parts) {
37584
+ const vertical = side === "west" || side === "east";
37585
+ const along = side === "north" ? p.y : side === "south" ? p.y + p.h : side === "west" ? p.x : p.x + p.w;
37586
+ const lo = vertical ? p.y : p.x;
37587
+ const hi = vertical ? p.y + p.h : p.x + p.w;
37588
+ const cuts = [];
37589
+ for (const q of room.parts) {
37590
+ if (q === p) continue;
37591
+ const qNear = side === "north" ? q.y + q.h : side === "south" ? q.y : side === "west" ? q.x + q.w : q.x;
37592
+ if (Math.abs(qNear - along) >= ADJ_EPS) continue;
37593
+ const cLo = vertical ? Math.max(lo, q.y) : Math.max(lo, q.x);
37594
+ const cHi = vertical ? Math.min(hi, q.y + q.h) : Math.min(hi, q.x + q.w);
37595
+ if (cHi > cLo) cuts.push([cLo, cHi]);
37596
+ }
37597
+ for (const [sLo, sHi] of subtractIntervals(lo, hi, cuts)) {
37598
+ if (sHi - sLo >= MIN_OVERLAP) segs.push({ along, lo: sLo, hi: sHi });
37599
+ }
37600
+ }
37601
+ return segs.sort((a, b) => a.lo - b.lo || a.along - b.along);
37602
+ }
37603
+ function layoutFloorplan(ast) {
37604
+ const u = ast.unit === "ft" ? FT : 1;
37605
+ const errors = [];
37606
+ const warnings = [];
37607
+ const rooms = [];
37608
+ const byId = /* @__PURE__ */ new Map();
37609
+ const stmts = [
37610
+ ...ast.rooms.map((room) => ({ line: room.line ?? 0, room })),
37611
+ ...ast.extensions.map((ext) => ({ line: ext.line ?? 0, ext }))
37612
+ ].sort((a, b) => a.line - b.line);
37613
+ for (const stmt of stmts) {
37614
+ if (stmt.room) {
37615
+ const r6 = stmt.room;
37616
+ const w = r6.w * u;
37617
+ const h = r6.h * u;
37618
+ const pos = resolvePlacement(r6, w, h, byId, rooms, u, `room "${r6.id}"`, errors) ?? { x: 0, y: 0 };
37619
+ const part = { x: pos.x, y: pos.y, w, h };
37620
+ const room = {
37621
+ id: r6.id,
37622
+ label: r6.label,
37623
+ x: part.x,
37624
+ y: part.y,
37625
+ w,
37626
+ h,
37627
+ parts: [part],
37628
+ areaM2: 0,
37629
+ areaText: "",
37630
+ fill: r6.fill,
37631
+ nolabel: r6.nolabel ?? false
37632
+ };
37633
+ refreshRoomBounds(room, ast.unit);
37634
+ byId.set(r6.id, rooms.length);
37635
+ rooms.push(room);
37636
+ } else if (stmt.ext) {
37637
+ const e = stmt.ext;
37638
+ const idx = byId.get(e.room);
37639
+ if (idx === void 0) {
37640
+ errors.push(`extend: unknown room "${e.room}" \u2014 declare it first`);
37641
+ continue;
37642
+ }
37643
+ const room = rooms[idx];
37644
+ const w = e.w * u;
37645
+ const h = e.h * u;
37646
+ const pos = resolvePlacement(e, w, h, byId, rooms, u, `extend "${e.room}"`, errors);
37647
+ if (!pos) continue;
37648
+ const part = { x: pos.x, y: pos.y, w, h };
37649
+ let touches = false;
37650
+ let overlaps = false;
37651
+ for (const p of room.parts) {
37652
+ const { ox, oy } = rectOverlap(p, part);
37653
+ if (ox > ADJ_EPS && oy > ADJ_EPS) overlaps = true;
37654
+ if (rectSharedEdge(p, part)) touches = true;
37655
+ }
37656
+ if (overlaps) {
37657
+ errors.push(`extend "${e.room}": extension overlaps the room's existing area \u2014 place it edge-to-edge`);
37658
+ continue;
37659
+ }
37660
+ if (!touches) {
37661
+ errors.push(`extend "${e.room}": extension does not touch the room \u2014 extensions must share an edge`);
37662
+ continue;
37663
+ }
37664
+ room.parts.push(part);
37665
+ refreshRoomBounds(room, ast.unit);
37666
+ }
37667
+ }
37668
+ for (let i = 0; i < rooms.length; i++) {
37669
+ for (let j = i + 1; j < rooms.length; j++) {
37670
+ const a = rooms[i];
37671
+ const b = rooms[j];
37672
+ let worst = null;
37673
+ for (const pa of a.parts) {
37674
+ for (const pb of b.parts) {
37675
+ const { ox, oy } = rectOverlap(pa, pb);
37676
+ if (ox > ADJ_EPS && oy > ADJ_EPS && (!worst || ox * oy > worst.ox * worst.oy)) {
37677
+ worst = { ox, oy };
37678
+ }
37679
+ }
37680
+ }
37681
+ if (worst) {
37682
+ errors.push(
37683
+ `rooms "${a.id}" and "${b.id}" overlap by ${worst.ox.toFixed(2)}\xD7${worst.oy.toFixed(2)} m \u2014 move "${b.id}" right-of "${a.id}" or shrink size`
37684
+ );
37685
+ }
37686
+ }
37687
+ }
37688
+ const seams = [];
37689
+ for (let ri = 0; ri < rooms.length; ri++) {
37690
+ const parts = rooms[ri].parts;
37691
+ for (let i = 0; i < parts.length; i++) {
37692
+ for (let j = i + 1; j < parts.length; j++) {
37693
+ const e = rectSharedEdge(parts[i], parts[j]);
37694
+ if (e) seams.push({ vertical: e.vertical, along: e.along, lo: e.lo, hi: e.hi, room: ri });
37695
+ }
37696
+ }
37697
+ }
37698
+ const openings = [];
37699
+ for (const op of ast.openings) {
37700
+ const geom = resolveOpening(op, rooms, byId, u, ast.unit, errors, warnings);
37701
+ if (geom) openings.push(geom);
37702
+ }
37703
+ const items = [];
37704
+ const seqByType = /* @__PURE__ */ new Map();
37705
+ const place = (type, roomIdx, localX, localY, w, h, rotate, label) => {
37706
+ const room = rooms[roomIdx];
37707
+ const seq = (seqByType.get(type) ?? 0) + 1;
37708
+ seqByType.set(type, seq);
37709
+ items.push({ type, x: room.x + localX, y: room.y + localY, w, h, rotate, label, roomId: room.id, seq });
37710
+ };
37711
+ const roomIdxOf = (stmt, roomId, line2) => {
37712
+ if (!roomId) {
37713
+ errors.push(`${stmt}${line2 ? ` (line ${line2})` : ""}: missing "in <room>"`);
37714
+ return void 0;
37715
+ }
37716
+ const idx = byId.get(roomId);
37717
+ if (idx === void 0) {
37718
+ errors.push(`${stmt}: unknown room "${roomId}"`);
37719
+ return void 0;
37720
+ }
37721
+ return idx;
37722
+ };
37723
+ for (const f of ast.furniture) {
37724
+ const def = FLOORPLAN_SYMBOLS[f.type];
37725
+ const idx = roomIdxOf(`furniture ${f.type}`, f.room, f.line);
37726
+ if (idx === void 0) continue;
37727
+ const w = f.size ? f.size.w * u : def.w;
37728
+ const h = f.size ? f.size.h * u : def.h;
37729
+ place(f.type, idx, f.x * u, f.y * u, w, h, f.rotate, f.label);
37730
+ }
37731
+ for (const a of ast.arrays) {
37732
+ const def = FLOORPLAN_SYMBOLS[a.type];
37733
+ const idx = roomIdxOf(`${a.mode} ${a.type}`, a.room, a.line);
37734
+ if (idx === void 0) continue;
37735
+ const room = rooms[idx];
37736
+ const iw = a.itemsize ? a.itemsize.w * u : def.w;
37737
+ const ih = a.itemsize ? a.itemsize.h * u : def.h;
37738
+ const p1 = a.p1 ? { x: a.p1.x * u, y: a.p1.y * u } : { x: 0.5 * u, y: 0.5 * u };
37739
+ const p2 = a.p2 ? { x: a.p2.x * u, y: a.p2.y * u } : { x: room.w - 0.5 * u, y: room.h - 0.5 * u };
37740
+ if (a.mode === "grid" || a.mode === "row") {
37741
+ const nRows = a.mode === "row" ? 1 : Math.max(1, Math.round(a.rows));
37742
+ const nCols = Math.max(1, Math.round(a.cols));
37743
+ const cap2 = Number.isFinite(a.count) ? a.count : nRows * nCols;
37744
+ const spanW = p2.x - p1.x;
37745
+ const spanH = p2.y - p1.y;
37746
+ let placed = 0;
37747
+ for (let r6 = 0; r6 < nRows && placed < cap2; r6++) {
37748
+ for (let col = 0; col < nCols && placed < cap2; col++) {
37749
+ const cx = p1.x + (nCols === 1 ? spanW / 2 : col * spanW / (nCols - 1));
37750
+ const cy = p1.y + (nRows === 1 ? spanH / 2 : r6 * spanH / (nRows - 1));
37751
+ place(a.type, idx, cx - iw / 2, cy - ih / 2, iw, ih, a.rotate);
37752
+ placed++;
37753
+ }
37754
+ }
37755
+ } else {
37756
+ const n = Math.max(1, Number.isFinite(a.count) ? a.count : Math.max(1, Math.round(a.cols)));
37757
+ const cen = a.center ? { x: a.center.x * u, y: a.center.y * u } : { x: room.w / 2, y: room.h * 0.6 };
37758
+ const radius = a.radius !== void 0 ? a.radius * u : Math.min(room.w, room.h) / 3;
37759
+ const a0 = (a.fromDeg ?? 200) * Math.PI / 180;
37760
+ const a1 = (a.toDeg ?? 340) * Math.PI / 180;
37761
+ for (let i = 0; i < n; i++) {
37762
+ const th = a0 + (a1 - a0) * i / Math.max(n - 1, 1);
37763
+ const cx = cen.x + radius * Math.cos(th);
37764
+ const cy = cen.y + radius * Math.sin(th);
37765
+ const facing = th * 180 / Math.PI + 270;
37766
+ place(a.type, idx, cx - iw / 2, cy - ih / 2, iw, ih, facing + a.rotate);
37767
+ }
37768
+ }
37769
+ }
37770
+ const roomOf = /* @__PURE__ */ new Map();
37771
+ for (const r6 of rooms) roomOf.set(r6.id, r6);
37772
+ const warnItems = /* @__PURE__ */ new Set();
37773
+ for (const it of items) {
37774
+ const room = roomOf.get(it.roomId);
37775
+ if (!room) continue;
37776
+ const bb = rotatedAabb(it.x, it.y, it.w, it.h, it.rotate, [0, 0, 0, 0]);
37777
+ const over = Math.max(
37778
+ room.x - bb.minX,
37779
+ bb.maxX - (room.x + room.w),
37780
+ room.y - bb.minY,
37781
+ bb.maxY - (room.y + room.h)
37782
+ );
37783
+ if (over > 0.011) {
37784
+ errors.push(
37785
+ `furniture ${it.type} #${it.seq} extends ${fmtNum(over)} m outside room "${it.roomId}" \u2014 move it or shrink size`
37786
+ );
37787
+ continue;
37788
+ }
37789
+ if (room.parts.length > 1) {
37790
+ const bw = bb.maxX - bb.minX;
37791
+ const bh = bb.maxY - bb.minY;
37792
+ let covered = 0;
37793
+ for (const p of room.parts) {
37794
+ const ox = Math.min(p.x + p.w, bb.maxX) - Math.max(p.x, bb.minX);
37795
+ const oy = Math.min(p.y + p.h, bb.maxY) - Math.max(p.y, bb.minY);
37796
+ if (ox > 0 && oy > 0) covered += ox * oy;
37797
+ }
37798
+ const uncovered = bw * bh - covered;
37799
+ if (uncovered > 0.01) {
37800
+ errors.push(
37801
+ `furniture ${it.type} #${it.seq} sits outside room "${it.roomId}"'s L-shape (${fmtNum(uncovered)} m\xB2 past the notch) \u2014 move it onto a room part`
37802
+ );
37803
+ }
37804
+ }
37805
+ }
37806
+ const envelopes = items.map((it) => {
37807
+ const def = FLOORPLAN_SYMBOLS[it.type];
37808
+ return obbCorners(it.x, it.y, it.w, it.h, it.rotate, def.envelope ?? [0, 0, 0, 0]);
37809
+ });
37810
+ for (let i = 0; i < items.length; i++) {
37811
+ const a = items[i];
37812
+ if (FLOORPLAN_SYMBOLS[a.type].underlay) continue;
37813
+ for (let j = i + 1; j < items.length; j++) {
37814
+ const b = items[j];
37815
+ if (FLOORPLAN_SYMBOLS[b.type].underlay) continue;
37816
+ const pen = obbPenetration(envelopes[i], envelopes[j]);
37817
+ if (pen > 0.011) {
37818
+ warnings.push(
37819
+ `${a.type} #${a.seq} overlaps ${b.type} #${b.seq} by ${fmtNum(pen)} m \u2014 increase spacing or reduce cols`
37820
+ );
37821
+ warnItems.add(i);
37822
+ warnItems.add(j);
37823
+ }
37824
+ }
37825
+ }
37826
+ let minX = Infinity;
37827
+ let minY = Infinity;
37828
+ let maxX = -Infinity;
37829
+ let maxY = -Infinity;
37830
+ for (const r6 of rooms) {
37831
+ minX = Math.min(minX, r6.x);
37832
+ minY = Math.min(minY, r6.y);
37833
+ maxX = Math.max(maxX, r6.x + r6.w);
37834
+ maxY = Math.max(maxY, r6.y + r6.h);
37835
+ }
37836
+ if (rooms.length === 0) {
37837
+ minX = minY = 0;
37838
+ maxX = maxY = 1;
37839
+ errors.push('no rooms defined \u2014 declare at least one: room id "Label" at 0,0 size 4x3');
37840
+ }
37841
+ const dims = [];
37842
+ if (rooms.length > 0) {
37843
+ dims.push({
37844
+ vertical: false,
37845
+ at: minY - FLOORPLAN_CONST.dimMajorOff,
37846
+ lo: minX,
37847
+ hi: maxX,
37848
+ label: formatLength(maxX - minX, ast.unit),
37849
+ minor: false
37850
+ });
37851
+ dims.push({
37852
+ vertical: true,
37853
+ at: minX - FLOORPLAN_CONST.dimMajorOff,
37854
+ lo: minY,
37855
+ hi: maxY,
37856
+ label: formatLength(maxY - minY, ast.unit),
37857
+ minor: false
37858
+ });
37859
+ const topSegs = [];
37860
+ for (const r6 of rooms) {
37861
+ for (const sg of sideSegments(r6, "north")) {
37862
+ if (Math.abs(sg.along - minY) < 0.01) topSegs.push({ lo: sg.lo, hi: sg.hi });
37863
+ }
37864
+ }
37865
+ if (topSegs.length > 1) {
37866
+ for (const sg of topSegs.sort((a, b) => a.lo - b.lo)) {
37867
+ dims.push({
37868
+ vertical: false,
37869
+ at: minY - FLOORPLAN_CONST.dimMinorOff,
37870
+ lo: sg.lo,
37871
+ hi: sg.hi,
37872
+ label: formatLength(sg.hi - sg.lo, ast.unit),
37873
+ minor: true
37874
+ });
37875
+ }
37876
+ }
37877
+ const leftSegs = [];
37878
+ for (const r6 of rooms) {
37879
+ for (const sg of sideSegments(r6, "west")) {
37880
+ if (Math.abs(sg.along - minX) < 0.01) leftSegs.push({ lo: sg.lo, hi: sg.hi });
37881
+ }
37882
+ }
37883
+ if (leftSegs.length > 1) {
37884
+ for (const sg of leftSegs.sort((a, b) => a.lo - b.lo)) {
37885
+ dims.push({
37886
+ vertical: true,
37887
+ at: minX - FLOORPLAN_CONST.dimMinorOff,
37888
+ lo: sg.lo,
37889
+ hi: sg.hi,
37890
+ label: formatLength(sg.hi - sg.lo, ast.unit),
37891
+ minor: true
37892
+ });
37893
+ }
37894
+ }
37895
+ }
37896
+ return {
37897
+ title: ast.title,
37898
+ unit: ast.unit,
37899
+ north: ast.north,
37900
+ rooms,
37901
+ seams,
37902
+ openings,
37903
+ items,
37904
+ dims,
37905
+ bounds: { minX, minY, maxX, maxY },
37906
+ wallT: FLOORPLAN_CONST.wallT,
37907
+ totalAreaM2: rooms.reduce((s, r6) => s + r6.areaM2, 0),
37908
+ errors,
37909
+ warnings,
37910
+ warnItems: [...warnItems]
37911
+ };
37912
+ }
37913
+ function resolveOpening(op, rooms, byId, u, unit, errors, warnings) {
37914
+ let seg = null;
37915
+ let owner = -1;
37916
+ let negRoom;
37917
+ let posRoom;
37918
+ let inward = 1;
37919
+ if (op.between) {
37920
+ const ia = byId.get(op.between[0]);
37921
+ const ib = byId.get(op.between[1]);
37922
+ if (ia === void 0 || ib === void 0) {
37923
+ errors.push(`${op.kind}: unknown room "${ia === void 0 ? op.between[0] : op.between[1]}"`);
37924
+ return null;
37925
+ }
37926
+ const a = rooms[ia];
37927
+ const b = rooms[ib];
37928
+ const found = roomSharedEdge(a, b);
37929
+ if (!found) {
37930
+ const gapX = Math.max(a.x, b.x) - Math.min(a.x + a.w, b.x + b.w);
37931
+ const gapY = Math.max(a.y, b.y) - Math.min(a.y + a.h, b.y + b.h);
37932
+ const axis = gapX >= gapY ? "x" : "y";
37933
+ const gap = Math.max(gapX, gapY);
37934
+ errors.push(
37935
+ gap > 0 ? `${op.kind} between "${a.id}" and "${b.id}": rooms share no wall (gap ${fmtNum(gap)} m on ${axis}-axis)` : `${op.kind} between "${a.id}" and "${b.id}": rooms share no wall`
37936
+ );
37937
+ return null;
37938
+ }
37939
+ seg = found.edge;
37940
+ owner = ia;
37941
+ if (seg.vertical) {
37942
+ const aIsNeg = found.aPart.x + found.aPart.w / 2 < seg.along;
37943
+ negRoom = aIsNeg ? ia : ib;
37944
+ posRoom = aIsNeg ? ib : ia;
37945
+ inward = aIsNeg ? -1 : 1;
37946
+ } else {
37947
+ const aIsNeg = found.aPart.y + found.aPart.h / 2 < seg.along;
37948
+ negRoom = aIsNeg ? ia : ib;
37949
+ posRoom = aIsNeg ? ib : ia;
37950
+ inward = aIsNeg ? -1 : 1;
37951
+ }
37952
+ } else {
37953
+ const idx = byId.get(op.room);
37954
+ if (idx === void 0) {
37955
+ errors.push(`${op.kind}: unknown room "${op.room}"`);
37956
+ return null;
37957
+ }
37958
+ owner = idx;
37959
+ const r6 = rooms[idx];
37960
+ const side = op.side;
37961
+ const segs = sideSegments(r6, side);
37962
+ if (segs.length === 0) {
37963
+ errors.push(`${op.kind} on "${r6.id}" ${side}: that side has no exterior wall segment`);
37964
+ return null;
37965
+ }
37966
+ const total = segs.reduce((s, sg) => s + (sg.hi - sg.lo), 0);
37967
+ const pct2 = Math.min(100, Math.max(0, op.pct));
37968
+ let target = total * pct2 / 100;
37969
+ let chosen = segs[segs.length - 1];
37970
+ for (const sg of segs) {
37971
+ const len = sg.hi - sg.lo;
37972
+ if (target <= len) {
37973
+ chosen = sg;
37974
+ break;
37975
+ }
37976
+ target -= len;
37977
+ }
37978
+ const within = Math.min(1, Math.max(0, target / (chosen.hi - chosen.lo)));
37979
+ op = { ...op, pct: within * 100 };
37980
+ seg = { vertical: side === "west" || side === "east", along: chosen.along, lo: chosen.lo, hi: chosen.hi };
37981
+ switch (side) {
37982
+ case "north":
37983
+ inward = 1;
37984
+ posRoom = idx;
37985
+ break;
37986
+ case "south":
37987
+ inward = -1;
37988
+ negRoom = idx;
37989
+ break;
37990
+ case "west":
37991
+ inward = 1;
37992
+ posRoom = idx;
37993
+ break;
37994
+ case "east":
37995
+ inward = -1;
37996
+ negRoom = idx;
37997
+ break;
37998
+ }
37999
+ }
38000
+ const jamb = FLOORPLAN_CONST.jamb;
38001
+ const segLen = seg.hi - seg.lo;
38002
+ const avail = segLen - 2 * jamb;
38003
+ let wd = op.width * u;
38004
+ if (wd > avail) {
38005
+ warnings.push(
38006
+ `${op.kind}${op.room ? ` on "${op.room}" ${op.side}` : op.between ? ` between "${op.between[0]}" and "${op.between[1]}"` : ""}: width ${formatLength(wd, unit)} clamped to ${formatLength(avail, unit)} to fit the wall segment`
38007
+ );
38008
+ wd = avail;
38009
+ }
38010
+ const pct = Math.min(100, Math.max(0, op.pct));
38011
+ const c = seg.lo + segLen * pct / 100;
38012
+ const lo = Math.max(seg.lo + jamb, Math.min(c - wd / 2, seg.hi - jamb - wd));
38013
+ const hi = lo + wd;
38014
+ const arcDir = op.kind === "door" && op.swing === "out" ? inward === 1 ? -1 : 1 : inward;
38015
+ return {
38016
+ kind: op.kind,
38017
+ doorType: op.doorType,
38018
+ windowType: op.windowType,
38019
+ vertical: seg.vertical,
38020
+ along: seg.along,
38021
+ lo,
38022
+ hi,
38023
+ inward: arcDir,
38024
+ hinge: op.hinge,
38025
+ negRoom,
38026
+ posRoom,
38027
+ owner
38028
+ };
38029
+ }
38030
+
38031
+ // src/diagrams/floorplan/renderer.ts
38032
+ var r24 = (n) => Math.round(n * 100) / 100;
38033
+ function buildCss13(t) {
38034
+ return `
38035
+ .sx-fp { font-family: ${DEFAULT_FONT_FAMILY}; }
38036
+ .sx-fp-title { font: ${TITLE.weight} ${TITLE.size}px sans-serif; fill: ${t.text}; }
38037
+ .sx-fp-wall { fill: ${t.wallFill}; stroke: none; }
38038
+ .sx-fp-furn { fill: ${t.furnFill}; stroke: ${t.furnStroke}; stroke-width: 1.2; }
38039
+ .sx-fp-furn-nofill { fill: none; stroke: ${t.furnStroke}; stroke-width: 1.2; }
38040
+ .sx-fp-furn-line { fill: none; stroke: ${t.furnStroke}; stroke-width: 1; }
38041
+ .sx-fp-furn-dash { fill: none; stroke: ${t.furnStroke}; stroke-width: 1; stroke-dasharray: 4 3; }
38042
+ .sx-fp-furn-dot { fill: ${t.furnStroke}; stroke: none; }
38043
+ .sx-fp-furn-solid { fill: ${t.furnSolid}; stroke: none; }
38044
+ .sx-fp-board-inner { fill: ${t.boardInner}; stroke: none; }
38045
+ .sx-fp-chair { fill: ${t.chairFill}; stroke: ${t.furnStroke}; stroke-width: 1; }
38046
+ .sx-fp-rug { fill: none; stroke: ${t.rugStroke}; stroke-width: 1.2; stroke-dasharray: 5 4; }
38047
+ .sx-fp-hatch { fill: none; stroke: ${t.hatchStroke}; stroke-width: 1; }
38048
+ .sx-fp-furn-text { font-weight: 600; font-family: sans-serif; fill: ${t.furnLabel}; paint-order: stroke; stroke: ${t.floorFill}; stroke-width: 2.5px; stroke-linejoin: round; }
38049
+ .sx-fp-furn-label { font: 11px sans-serif; fill: ${t.furnLabel}; paint-order: stroke; stroke: ${t.floorFill}; stroke-width: 3px; stroke-linejoin: round; }
38050
+ .sx-fp-door-leaf { fill: none; stroke: ${t.doorLeaf}; stroke-width: 1.6; }
38051
+ .sx-fp-door-arc { fill: none; stroke: ${t.doorArc}; stroke-width: 1; }
38052
+ .sx-fp-window { fill: none; stroke: ${t.windowStroke}; stroke-width: 1.3; }
38053
+ .sx-fp-jamb { fill: none; stroke: ${t.furnStroke}; stroke-width: 1.2; }
38054
+ .sx-fp-room-name { font: 600 13.5px sans-serif; fill: ${t.roomName}; paint-order: stroke; stroke: ${t.floorFill}; stroke-width: 3px; stroke-linejoin: round; }
38055
+ .sx-fp-room-area { font: 11px sans-serif; fill: ${t.roomArea}; paint-order: stroke; stroke: ${t.floorFill}; stroke-width: 3px; stroke-linejoin: round; }
38056
+ .sx-fp-stair-break { fill: none; stroke: ${t.furnStroke}; stroke-width: 1.6; }
38057
+ .sx-fp-compass { fill: none; stroke: ${t.dimStroke}; stroke-width: 1.2; }
38058
+ .sx-fp-compass-n { font: 700 11px sans-serif; fill: ${t.dimText}; }
38059
+ .sx-fp-dim { fill: none; stroke: ${t.dimStroke}; stroke-width: 1; }
38060
+ .sx-fp-dim-text { font: 10.5px sans-serif; fill: ${t.dimText}; }
38061
+ .sx-fp-dim-text-minor { font: 9px sans-serif; fill: ${t.dimText}; }
38062
+ .sx-fp-warn-item { fill: none; stroke: ${t.negative}; stroke-width: 1.5; stroke-dasharray: 4 3; }
38063
+ .sx-fp-warn { font: 11px ui-monospace, Menlo, monospace; fill: ${t.warn}; }
38064
+ .sx-fp-error-box { fill: ${t.bg}; stroke: ${t.negative}; stroke-width: 1.5; }
38065
+ .sx-fp-error-title { font: 700 13px ui-monospace, Menlo, monospace; fill: ${t.negative}; }
38066
+ .sx-fp-error-line { font: 12px ui-monospace, Menlo, monospace; fill: ${t.negative}; }
38067
+ `.trim();
38068
+ }
38069
+ function renderErrorPanel(lay, t) {
38070
+ const lines = lay.errors;
38071
+ const w = Math.max(560, ...lines.map((l) => l.length * 6.6 + 48));
38072
+ const h = 56 + lines.length * 19;
38073
+ return svgRoot(
38074
+ { viewBox: `0 0 ${r24(w)} ${h}`, class: "sx-fp", role: "img" },
38075
+ [
38076
+ title(lay.title),
38077
+ desc(`Floor plan validation failed with ${lines.length} error${lines.length === 1 ? "" : "s"}.`),
38078
+ el("style", {}, buildCss13(t)),
38079
+ rect({ class: "sx-fp-error-box", x: 1, y: 1, width: r24(w - 2), height: h - 2, rx: 6 }),
38080
+ text({ class: "sx-fp-error-title", x: 16, y: 26 }, `floorplan: ${lines.length} validation error${lines.length === 1 ? "" : "s"}`),
38081
+ ...lines.map((e, i) => text({ class: "sx-fp-error-line", x: 16, y: 50 + i * 19 }, `\u26A0 ${e}`))
38082
+ ]
38083
+ );
38084
+ }
38085
+ function gapFill(lay, t, roomIdx) {
38086
+ if (roomIdx === void 0) return t.bg;
38087
+ return lay.rooms[roomIdx]?.fill ?? t.floorFill;
38088
+ }
38089
+ function punchGap(o, lay, c) {
38090
+ const tpx = c.px(c.wallT);
38091
+ const negFill = gapFill(lay, c.t, o.negRoom);
38092
+ const posFill = gapFill(lay, c.t, o.posRoom);
38093
+ if (o.vertical) {
38094
+ const x2 = c.X(o.along);
38095
+ const y2 = c.Y(o.lo);
38096
+ const h = c.px(o.hi - o.lo);
38097
+ return rect({ class: "sx-fp-gap", fill: negFill, x: r24(x2 - tpx / 2 - 0.5), y: y2, width: r24(tpx / 2 + 0.5), height: h }) + rect({ class: "sx-fp-gap", fill: posFill, x: x2, y: y2, width: r24(tpx / 2 + 0.5), height: h });
38098
+ }
38099
+ const y = c.Y(o.along);
38100
+ const x = c.X(o.lo);
38101
+ const w = c.px(o.hi - o.lo);
38102
+ return rect({ class: "sx-fp-gap", fill: negFill, x, y: r24(y - tpx / 2 - 0.5), width: w, height: r24(tpx / 2 + 0.5) }) + rect({ class: "sx-fp-gap", fill: posFill, x, y, width: w, height: r24(tpx / 2 + 0.5) });
38103
+ }
38104
+ function windowSymbol(o, c) {
38105
+ const tpx = c.px(c.wallT);
38106
+ const parts = [];
38107
+ const outward = o.inward === 1 ? -1 : 1;
38108
+ if (o.vertical) {
38109
+ const x = c.X(o.along);
38110
+ const mid = c.Y((o.lo + o.hi) / 2);
38111
+ if (o.windowType === "sliding") {
38112
+ parts.push(line({ class: "sx-fp-window", x1: r24(x - tpx * 0.22), y1: c.Y(o.lo), x2: r24(x - tpx * 0.22), y2: mid }));
38113
+ parts.push(line({ class: "sx-fp-window", x1: r24(x + tpx * 0.22), y1: mid, x2: r24(x + tpx * 0.22), y2: c.Y(o.hi) }));
38114
+ } else {
38115
+ for (const k of [-1, 0, 1]) {
38116
+ parts.push(line({ class: "sx-fp-window", x1: r24(x + k * tpx * 0.38), y1: c.Y(o.lo), x2: r24(x + k * tpx * 0.38), y2: c.Y(o.hi) }));
38117
+ }
38118
+ }
38119
+ for (const yy of [o.lo, o.hi]) {
38120
+ parts.push(line({ class: "sx-fp-window", x1: r24(x - tpx / 2), y1: c.Y(yy), x2: r24(x + tpx / 2), y2: c.Y(yy) }));
38121
+ }
38122
+ if (o.windowType === "casement") {
38123
+ const wd = c.px(o.hi - o.lo);
38124
+ const leafX = r24(x + outward * wd);
38125
+ const sweep = outward === 1 ? 1 : 0;
38126
+ parts.push(line({ class: "sx-fp-door-leaf", x1: x, y1: c.Y(o.lo), x2: leafX, y2: c.Y(o.lo) }));
38127
+ parts.push(path({ class: "sx-fp-door-arc", d: `M ${leafX} ${c.Y(o.lo)} A ${wd} ${wd} 0 0 ${sweep} ${x} ${r24(c.Y(o.lo) + wd)}` }));
38128
+ }
38129
+ if (o.windowType === "bay") {
38130
+ const proj = c.px(0.45) * outward;
38131
+ const splay = c.px(0.3);
38132
+ const x0 = r24(x + outward * tpx / 2);
38133
+ parts.push(
38134
+ path({
38135
+ class: "sx-fp-window",
38136
+ d: `M ${x0} ${c.Y(o.lo)} L ${r24(x0 + proj)} ${r24(c.Y(o.lo) + splay)} L ${r24(x0 + proj)} ${r24(c.Y(o.hi) - splay)} L ${x0} ${c.Y(o.hi)}`
38137
+ })
38138
+ );
38139
+ }
38140
+ } else {
38141
+ const y = c.Y(o.along);
38142
+ const mid = c.X((o.lo + o.hi) / 2);
38143
+ if (o.windowType === "sliding") {
38144
+ parts.push(line({ class: "sx-fp-window", x1: c.X(o.lo), y1: r24(y - tpx * 0.22), x2: mid, y2: r24(y - tpx * 0.22) }));
38145
+ parts.push(line({ class: "sx-fp-window", x1: mid, y1: r24(y + tpx * 0.22), x2: c.X(o.hi), y2: r24(y + tpx * 0.22) }));
38146
+ } else {
38147
+ for (const k of [-1, 0, 1]) {
38148
+ parts.push(line({ class: "sx-fp-window", x1: c.X(o.lo), y1: r24(y + k * tpx * 0.38), x2: c.X(o.hi), y2: r24(y + k * tpx * 0.38) }));
38149
+ }
38150
+ }
38151
+ for (const xx of [o.lo, o.hi]) {
38152
+ parts.push(line({ class: "sx-fp-window", x1: c.X(xx), y1: r24(y - tpx / 2), x2: c.X(xx), y2: r24(y + tpx / 2) }));
38153
+ }
38154
+ if (o.windowType === "casement") {
38155
+ const wd = c.px(o.hi - o.lo);
38156
+ const leafY = r24(y + outward * wd);
38157
+ const sweep = outward === 1 ? 0 : 1;
38158
+ parts.push(line({ class: "sx-fp-door-leaf", x1: c.X(o.lo), y1: y, x2: c.X(o.lo), y2: leafY }));
38159
+ parts.push(path({ class: "sx-fp-door-arc", d: `M ${c.X(o.lo)} ${leafY} A ${wd} ${wd} 0 0 ${sweep} ${r24(c.X(o.lo) + wd)} ${y}` }));
38160
+ }
38161
+ if (o.windowType === "bay") {
38162
+ const proj = c.px(0.45) * outward;
38163
+ const splay = c.px(0.3);
38164
+ const y0 = r24(y + outward * tpx / 2);
38165
+ parts.push(
38166
+ path({
38167
+ class: "sx-fp-window",
38168
+ d: `M ${c.X(o.lo)} ${y0} L ${r24(c.X(o.lo) + splay)} ${r24(y0 + proj)} L ${r24(c.X(o.hi) - splay)} ${r24(y0 + proj)} L ${c.X(o.hi)} ${y0}`
38169
+ })
38170
+ );
38171
+ }
38172
+ }
38173
+ return parts.join("");
38174
+ }
38175
+ function jambLines(o, c) {
38176
+ const tpx = c.px(c.wallT);
38177
+ if (o.vertical) {
38178
+ const x = c.X(o.along);
38179
+ return [o.lo, o.hi].map((yy) => line({ class: "sx-fp-jamb", x1: r24(x - tpx / 2), y1: c.Y(yy), x2: r24(x + tpx / 2), y2: c.Y(yy) })).join("");
38180
+ }
38181
+ const y = c.Y(o.along);
38182
+ return [o.lo, o.hi].map((xx) => line({ class: "sx-fp-jamb", x1: c.X(xx), y1: r24(y - tpx / 2), x2: c.X(xx), y2: r24(y + tpx / 2) })).join("");
38183
+ }
38184
+ function swingLeaf(o, c, hingeAtLo, widthM) {
38185
+ const dir = o.inward;
38186
+ const rpx = c.px(widthM);
38187
+ if (o.vertical) {
38188
+ const x = c.X(o.along);
38189
+ const hy = hingeAtLo ? c.Y(o.lo) : c.Y(o.hi);
38190
+ const sy = hingeAtLo ? 1 : -1;
38191
+ const leafX = r24(x + dir * rpx);
38192
+ const sweep2 = dir === 1 === hingeAtLo ? 1 : 0;
38193
+ return line({ class: "sx-fp-door-leaf", x1: x, y1: hy, x2: leafX, y2: hy }) + path({ class: "sx-fp-door-arc", d: `M ${leafX} ${hy} A ${rpx} ${rpx} 0 0 ${sweep2} ${x} ${r24(hy + sy * rpx)}` });
38194
+ }
38195
+ const y = c.Y(o.along);
38196
+ const hx = hingeAtLo ? c.X(o.lo) : c.X(o.hi);
38197
+ const sx = hingeAtLo ? 1 : -1;
38198
+ const leafY = r24(y + dir * rpx);
38199
+ const sweep = dir === 1 !== hingeAtLo ? 1 : 0;
38200
+ return line({ class: "sx-fp-door-leaf", x1: hx, y1: y, x2: hx, y2: leafY }) + path({ class: "sx-fp-door-arc", d: `M ${hx} ${leafY} A ${rpx} ${rpx} 0 0 ${sweep} ${r24(hx + sx * rpx)} ${y}` });
38201
+ }
38202
+ function doorSymbol(o, c) {
38203
+ const wd = o.hi - o.lo;
38204
+ if (o.doorType === "double") {
38205
+ const half = { ...o };
38206
+ const mid = (o.lo + o.hi) / 2;
38207
+ const left = { ...half, hi: mid };
38208
+ const right = { ...half, lo: mid };
38209
+ return jambLines(o, c) + swingLeaf(left, c, true, wd / 2) + swingLeaf(right, c, false, wd / 2);
38210
+ }
38211
+ if (o.doorType === "bifold") {
38212
+ const peak = c.px((o.hi - o.lo) * 0.22) * o.inward;
38213
+ const q1 = o.lo + (o.hi - o.lo) * 0.25;
38214
+ const mid = (o.lo + o.hi) / 2;
38215
+ const q3 = o.lo + (o.hi - o.lo) * 0.75;
38216
+ const parts = [jambLines(o, c)];
38217
+ if (o.vertical) {
38218
+ const x = c.X(o.along);
38219
+ parts.push(
38220
+ path({
38221
+ class: "sx-fp-door-leaf",
38222
+ d: `M ${x} ${c.Y(o.lo)} L ${r24(x + peak)} ${c.Y(q1)} L ${x} ${c.Y(mid)} L ${r24(x + peak)} ${c.Y(q3)} L ${x} ${c.Y(o.hi)}`
38223
+ })
38224
+ );
38225
+ } else {
38226
+ const y = c.Y(o.along);
38227
+ parts.push(
38228
+ path({
38229
+ class: "sx-fp-door-leaf",
38230
+ d: `M ${c.X(o.lo)} ${y} L ${c.X(q1)} ${r24(y + peak)} L ${c.X(mid)} ${y} L ${c.X(q3)} ${r24(y + peak)} L ${c.X(o.hi)} ${y}`
38231
+ })
38232
+ );
38233
+ }
38234
+ return parts.join("");
38235
+ }
38236
+ if (o.doorType === "sliding" || o.doorType === "pocket") {
38237
+ const off = c.px(c.wallT) * 0.28;
38238
+ const mid = (o.lo + o.hi) / 2;
38239
+ const parts = [jambLines(o, c)];
38240
+ if (o.vertical) {
38241
+ const x = c.X(o.along);
38242
+ parts.push(line({ class: "sx-fp-door-leaf", x1: r24(x - off), y1: c.Y(o.lo), x2: r24(x - off), y2: c.Y(mid) }));
38243
+ if (o.doorType === "sliding") {
38244
+ parts.push(line({ class: "sx-fp-door-leaf", x1: r24(x + off), y1: c.Y(mid), x2: r24(x + off), y2: c.Y(o.hi) }));
38245
+ }
38246
+ } else {
38247
+ const y = c.Y(o.along);
38248
+ parts.push(line({ class: "sx-fp-door-leaf", x1: c.X(o.lo), y1: r24(y - off), x2: c.X(mid), y2: r24(y - off) }));
38249
+ if (o.doorType === "sliding") {
38250
+ parts.push(line({ class: "sx-fp-door-leaf", x1: c.X(mid), y1: r24(y + off), x2: c.X(o.hi), y2: r24(y + off) }));
38251
+ }
38252
+ }
38253
+ return parts.join("");
38254
+ }
38255
+ return jambLines(o, c) + swingLeaf(o, c, o.hinge !== "right", wd);
38256
+ }
38257
+ function renderDim(d, c) {
38258
+ const cls = d.minor ? "sx-fp-dim-text-minor" : "sx-fp-dim-text";
38259
+ const tick = 3.6;
38260
+ if (!d.vertical) {
38261
+ const y = c.Y(d.at);
38262
+ const x1 = c.X(d.lo);
38263
+ const x2 = c.X(d.hi);
38264
+ return line({ class: "sx-fp-dim", x1, y1: y, x2, y2: y }) + line({ class: "sx-fp-dim", x1: r24(x1 - tick), y1: r24(y + tick), x2: r24(x1 + tick), y2: r24(y - tick) }) + line({ class: "sx-fp-dim", x1: r24(x2 - tick), y1: r24(y + tick), x2: r24(x2 + tick), y2: r24(y - tick) }) + text({ class: cls, x: r24((x1 + x2) / 2), y: r24(y - 4), "text-anchor": "middle" }, d.label);
38265
+ }
38266
+ const x = c.X(d.at);
38267
+ const y1 = c.Y(d.lo);
38268
+ const y2 = c.Y(d.hi);
38269
+ const mid = r24((y1 + y2) / 2);
38270
+ return line({ class: "sx-fp-dim", x1: x, y1, x2: x, y2 }) + line({ class: "sx-fp-dim", x1: r24(x - tick), y1: r24(y1 + tick), x2: r24(x + tick), y2: r24(y1 - tick) }) + line({ class: "sx-fp-dim", x1: r24(x - tick), y1: r24(y2 + tick), x2: r24(x + tick), y2: r24(y2 - tick) }) + text(
38271
+ { class: cls, x: r24(x - 5), y: mid, "text-anchor": "middle", transform: `rotate(-90 ${r24(x - 5)} ${mid})` },
38272
+ d.label
38273
+ );
38274
+ }
38275
+ function renderFloorplanLayout(lay, config) {
38276
+ const t = resolveFloorplanTheme(config?.theme ?? "default");
38277
+ if (lay.errors.length > 0) return renderErrorPanel(lay, t);
38278
+ const scale = FLOORPLAN_CONST.scale;
38279
+ const px = (m) => r24(m * scale);
38280
+ const band = FLOORPLAN_CONST.dimBand + FLOORPLAN_CONST.pad;
38281
+ let outward = 0;
38282
+ for (const o of lay.openings) {
38283
+ if (o.kind !== "window") continue;
38284
+ if (o.windowType === "casement") outward = Math.max(outward, o.hi - o.lo + 0.1);
38285
+ if (o.windowType === "bay") outward = Math.max(outward, 0.6);
38286
+ }
38287
+ const tail = FLOORPLAN_CONST.pad + lay.wallT + outward;
38288
+ const ox = -lay.bounds.minX + band;
38289
+ const oy = -lay.bounds.minY + band;
38290
+ const X = (m) => px(m + ox);
38291
+ const Y = (m) => px(m + oy);
38292
+ const ctx = { X, Y, px, t, wallT: lay.wallT };
38293
+ const titleH = TITLE.bandH;
38294
+ const warnH = lay.warnings.length ? lay.warnings.length * 17 + 10 : 0;
38295
+ const W2 = px(lay.bounds.maxX - lay.bounds.minX + band + tail);
38296
+ const H2 = px(lay.bounds.maxY - lay.bounds.minY + band + tail) + titleH + warnH;
38297
+ const titleX = r24(X((lay.bounds.minX + lay.bounds.maxX) / 2));
38298
+ const floors = [];
38299
+ const furniture = [];
38300
+ const walls = [];
38301
+ const openings = [];
38302
+ const labels = [];
38303
+ const dims = [];
38304
+ for (const r6 of lay.rooms) {
38305
+ floors.push(
38306
+ group(
38307
+ { class: "sx-fp-floor", "data-room": r6.id },
38308
+ r6.parts.map(
38309
+ (p) => rect({
38310
+ fill: r6.fill ?? t.floorFill,
38311
+ x: X(p.x),
38312
+ y: Y(p.y),
38313
+ width: px(p.w),
38314
+ height: px(p.h)
38315
+ })
38316
+ )
38317
+ )
38318
+ );
38319
+ if (!r6.nolabel) {
38320
+ const main = r6.parts.reduce((a, b) => b.w * b.h > a.w * a.h ? b : a);
38321
+ const cx = X(main.x + main.w / 2);
38322
+ const cy = Y(main.y + main.h / 2);
38323
+ labels.push(text({ class: "sx-fp-room-name", x: cx, y: r24(cy - 3), "text-anchor": "middle" }, r6.label));
38324
+ labels.push(text({ class: "sx-fp-room-area", x: cx, y: r24(cy + 13), "text-anchor": "middle" }, r6.areaText));
38325
+ }
38326
+ }
38327
+ const warnSet = new Set(lay.warnItems);
38328
+ lay.items.forEach((it, idx) => {
38329
+ const def = FLOORPLAN_SYMBOLS[it.type];
38330
+ const wpx = px(it.w);
38331
+ const hpx = px(it.h);
38332
+ const cx = r24(X(it.x) + wpx / 2);
38333
+ const cy = r24(Y(it.y) + hpx / 2);
38334
+ const rot = Math.round(it.rotate * 10) / 10;
38335
+ const children = [def.draw({ w: it.w, h: it.h, px, label: it.label })];
38336
+ if (warnSet.has(idx)) {
38337
+ children.push(rect({ class: "sx-fp-warn-item", x: -1, y: -1, width: r24(wpx + 2), height: r24(hpx + 2) }));
38338
+ }
38339
+ furniture.push(
38340
+ group(
38341
+ {
38342
+ class: "sx-fp-item",
38343
+ "data-furniture": it.type,
38344
+ transform: `translate(${cx},${cy})${rot ? ` rotate(${rot})` : ""} translate(${r24(-wpx / 2)},${r24(-hpx / 2)})`
38345
+ },
38346
+ children
38347
+ )
38348
+ );
38349
+ if (it.label && !def.consumesLabel) {
38350
+ labels.push(text({ class: "sx-fp-furn-label", x: cx, y: r24(cy + 4), "text-anchor": "middle" }, it.label));
38351
+ }
38352
+ });
38353
+ const tw = lay.wallT;
38354
+ for (const r6 of lay.rooms) {
38355
+ for (const p of r6.parts) {
38356
+ walls.push(rect({ class: "sx-fp-wall", x: r24(X(p.x) - px(tw / 2)), y: r24(Y(p.y) - px(tw / 2)), width: r24(px(p.w + tw)), height: px(tw) }));
38357
+ walls.push(rect({ class: "sx-fp-wall", x: r24(X(p.x) - px(tw / 2)), y: r24(Y(p.y + p.h) - px(tw / 2)), width: r24(px(p.w + tw)), height: px(tw) }));
38358
+ walls.push(rect({ class: "sx-fp-wall", x: r24(X(p.x) - px(tw / 2)), y: r24(Y(p.y) - px(tw / 2)), width: px(tw), height: r24(px(p.h + tw)) }));
38359
+ walls.push(rect({ class: "sx-fp-wall", x: r24(X(p.x + p.w) - px(tw / 2)), y: r24(Y(p.y) - px(tw / 2)), width: px(tw), height: r24(px(p.h + tw)) }));
38360
+ }
38361
+ }
38362
+ for (const s of lay.seams) {
38363
+ const fill = lay.rooms[s.room]?.fill ?? t.floorFill;
38364
+ const tpx = px(tw);
38365
+ if (s.vertical) {
38366
+ openings.push(
38367
+ rect({ class: "sx-fp-gap", fill, x: r24(X(s.along) - tpx / 2 - 0.5), y: Y(s.lo), width: r24(tpx + 1), height: px(s.hi - s.lo) })
38368
+ );
38369
+ } else {
38370
+ openings.push(
38371
+ rect({ class: "sx-fp-gap", fill, x: X(s.lo), y: r24(Y(s.along) - tpx / 2 - 0.5), width: px(s.hi - s.lo), height: r24(tpx + 1) })
38372
+ );
38373
+ }
38374
+ }
38375
+ for (const o of lay.openings) {
38376
+ openings.push(punchGap(o, lay, ctx));
38377
+ if (o.kind === "window") openings.push(windowSymbol(o, ctx));
38378
+ else if (o.kind === "door") openings.push(doorSymbol(o, ctx));
38379
+ else openings.push(jambLines(o, ctx));
38380
+ }
38381
+ for (const d of lay.dims) dims.push(renderDim(d, ctx));
38382
+ if (lay.north !== void 0) {
38383
+ const ncx = r24(X(lay.bounds.maxX) + px(0.55));
38384
+ const ncy = r24(Y(lay.bounds.minY) - px(0.62));
38385
+ const rr = px(0.32);
38386
+ const rot = Math.round(lay.north * 10) / 10;
38387
+ dims.push(
38388
+ group({ class: "sx-fp-compass-g", transform: rot ? `rotate(${rot} ${ncx} ${ncy})` : "" }, [
38389
+ el("circle", { class: "sx-fp-compass", cx: ncx, cy: ncy, r: rr }),
38390
+ line({ class: "sx-fp-compass", x1: ncx, y1: r24(ncy + rr * 0.7), x2: ncx, y2: r24(ncy - rr * 0.55) }),
38391
+ el("polygon", {
38392
+ class: "sx-fp-compass",
38393
+ fill: t.dimText,
38394
+ points: `${ncx},${r24(ncy - rr * 0.85)} ${r24(ncx - rr * 0.22)},${r24(ncy - rr * 0.3)} ${r24(ncx + rr * 0.22)},${r24(ncy - rr * 0.3)}`
38395
+ }),
38396
+ text({ class: "sx-fp-compass-n", x: r24(ncx + rr + 4), y: r24(ncy - rr * 0.45), "text-anchor": "start" }, "N")
38397
+ ])
38398
+ );
38399
+ }
38400
+ const warnBlock = [];
38401
+ if (lay.warnings.length) {
38402
+ const y0 = H2 - warnH + 4;
38403
+ lay.warnings.forEach((w, i) => {
38404
+ warnBlock.push(text({ class: "sx-fp-warn", x: 10, y: r24(y0 + i * 17 + 10) }, `\u26A0 ${w}`));
38405
+ });
38406
+ }
38407
+ const nRooms = lay.rooms.length;
38408
+ const descText = `${nRooms} room${nRooms === 1 ? "" : "s"}, ${formatArea(lay.totalAreaM2, lay.unit)} total. ${lay.items.length} furniture item${lay.items.length === 1 ? "" : "s"}.` + (lay.warnings.length ? ` Warnings: ${lay.warnings.join("; ")}.` : "");
38409
+ return svgRoot(
38410
+ { viewBox: `0 0 ${W2} ${H2}`, class: "sx-fp", role: "img" },
38411
+ [
38412
+ title(lay.title),
38413
+ desc(descText),
38414
+ el("style", {}, buildCss13(t)),
38415
+ rect({ fill: t.bg, x: 0, y: 0, width: W2, height: H2 }),
38416
+ text({ class: "sx-fp-title", x: titleX, y: TITLE.y, "text-anchor": "middle" }, lay.title),
38417
+ group({ transform: `translate(0,${titleH})` }, [
38418
+ group({ class: "sx-fp-floors" }, floors),
38419
+ group({ class: "sx-fp-furniture" }, furniture),
38420
+ group({ class: "sx-fp-walls" }, walls),
38421
+ group({ class: "sx-fp-openings" }, openings),
38422
+ group({ class: "sx-fp-labels" }, labels),
38423
+ group({ class: "sx-fp-dims" }, dims)
38424
+ ]),
38425
+ ...warnBlock
38426
+ ]
38427
+ );
38428
+ }
38429
+ function renderFloorplan(text2, config) {
38430
+ return renderFloorplanLayout(layoutFloorplan(parseFloorplan(text2)), config);
38431
+ }
38432
+
38433
+ // src/diagrams/floorplan/index.ts
38434
+ var floorplan = {
38435
+ type: "floorplan",
38436
+ detect(text2) {
38437
+ for (const raw of text2.split(/\r?\n/)) {
38438
+ const t = raw.trim();
38439
+ if (!t) continue;
38440
+ if (t.startsWith("#") || t.startsWith("//")) continue;
38441
+ return /^floorplan\b/i.test(t);
38442
+ }
38443
+ return false;
38444
+ },
38445
+ parse: parseFloorplan,
38446
+ render(text2, config) {
38447
+ return renderFloorplan(text2, config);
38448
+ },
38449
+ lint(text2) {
38450
+ try {
38451
+ const lay = layoutFloorplan(parseFloorplan(text2));
38452
+ return [
38453
+ ...lay.errors.map(
38454
+ (message) => ({
38455
+ severity: "error",
38456
+ code: "floorplan/validation",
38457
+ message,
38458
+ fatal: false
38459
+ })
38460
+ ),
38461
+ ...lay.warnings.map(
38462
+ (message) => ({
38463
+ severity: "warning",
38464
+ code: "floorplan/warning",
38465
+ message,
38466
+ fatal: false
38467
+ })
38468
+ )
38469
+ ];
38470
+ } catch {
38471
+ return [];
38472
+ }
38473
+ }
38474
+ };
38475
+
35925
38476
  // src/core/api.ts
35926
38477
  var plugins = [
35927
38478
  genogram,
@@ -35968,7 +38519,8 @@ var plugins = [
35968
38519
  epc,
35969
38520
  idef0,
35970
38521
  threatmodel,
35971
- welding
38522
+ welding,
38523
+ floorplan
35972
38524
  ];
35973
38525
  function detectPlugin(text2, config) {
35974
38526
  if (config?.type) {
@@ -35979,7 +38531,7 @@ function detectPlugin(text2, config) {
35979
38531
  if (plugin.detect(text2)) return plugin;
35980
38532
  }
35981
38533
  throw new Error(
35982
- "Cannot detect diagram type. Start your text with 'genogram', 'ecomap', 'pedigree', 'phylo', 'sociogram', 'timing', 'logic', 'circuit', 'blockdiagram', 'ladder', 'sld', 'entity-structure', 'fishbone', 'venn', 'flowchart', 'mindmap', 'matrix', 'orgchart', 'state', 'pid', 'erd', 'breadboard', 'bpmn', 'fbd', 'sfc', 'prisma', 'usecase', 'pert', 'sequence', 'petri', 'network', 'umlclass', 'faulttree', or 'bowtie'."
38534
+ "Cannot detect diagram type. Start your text with 'genogram', 'ecomap', 'pedigree', 'phylo', 'sociogram', 'timing', 'logic', 'circuit', 'blockdiagram', 'ladder', 'sld', 'entity-structure', 'fishbone', 'venn', 'flowchart', 'mindmap', 'matrix', 'orgchart', 'state', 'pid', 'erd', 'breadboard', 'bpmn', 'fbd', 'sfc', 'prisma', 'usecase', 'pert', 'sequence', 'petri', 'network', 'umlclass', 'faulttree', 'bowtie', or 'floorplan'."
35983
38535
  );
35984
38536
  }
35985
38537
  function stripCodeFences(text2) {
@@ -36116,6 +38668,6 @@ function renderWithPlugin(prepared, plugin, config) {
36116
38668
  return plugin.render(prepared, renderConfig);
36117
38669
  }
36118
38670
 
36119
- export { GEOMETRY, bowtie2 as bowtie, causalloop, decisiontree, drawDeviceIcon, epc, eventtree, faulttree, fmea, gitgraph, iconSize, idef0, markov, network, parse, parseResult, pert, petri, pid, prisma, render, renderEquip, renderPreview, renderResult, sequence, state, threatmodel, timeline, umlclass, usecase, welding };
36120
- //# sourceMappingURL=chunk-VOJGLBE5.js.map
36121
- //# sourceMappingURL=chunk-VOJGLBE5.js.map
38671
+ export { FLOORPLAN_SYMBOLS, GEOMETRY, bowtie2 as bowtie, causalloop, decisiontree, drawDeviceIcon, epc, eventtree, faulttree, fmea, gitgraph, iconSize, idef0, markov, network, parse, parseResult, pert, petri, pid, prisma, render, renderEquip, renderPreview, renderResult, sequence, state, threatmodel, timeline, umlclass, usecase, welding };
38672
+ //# sourceMappingURL=chunk-BP6MLXJU.js.map
38673
+ //# sourceMappingURL=chunk-BP6MLXJU.js.map