schematex 0.9.2 → 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 (174) hide show
  1. package/README.md +21 -0
  2. package/dist/ai/ai-sdk.cjs +23 -23
  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 +18 -18
  6. package/dist/ai/index.cjs +32 -32
  7. package/dist/ai/index.d.cts +4 -4
  8. package/dist/ai/index.d.ts +4 -4
  9. package/dist/ai/index.js +19 -19
  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 +24 -24
  13. package/dist/browser.d.cts +3 -3
  14. package/dist/browser.d.ts +3 -3
  15. package/dist/browser.js +18 -18
  16. package/dist/{chunk-7SVB3XA7.js → chunk-4CLAD7VZ.js} +3 -3
  17. package/dist/{chunk-7SVB3XA7.js.map → chunk-4CLAD7VZ.js.map} +1 -1
  18. package/dist/{chunk-KR7PPCC4.js → chunk-6GHXO3WQ.js} +3 -3
  19. package/dist/{chunk-KR7PPCC4.js.map → chunk-6GHXO3WQ.js.map} +1 -1
  20. package/dist/{chunk-PSQGLE6U.cjs → chunk-6W6RFZND.cjs} +5 -5
  21. package/dist/{chunk-PSQGLE6U.cjs.map → chunk-6W6RFZND.cjs.map} +1 -1
  22. package/dist/{chunk-VMUNKEB2.js → chunk-7P4C5DMD.js} +3 -3
  23. package/dist/chunk-7P4C5DMD.js.map +1 -0
  24. package/dist/{chunk-GYTU7L4L.cjs → chunk-AEZVCGH4.cjs} +12 -12
  25. package/dist/{chunk-GYTU7L4L.cjs.map → chunk-AEZVCGH4.cjs.map} +1 -1
  26. package/dist/{chunk-NTQBNBBP.cjs → chunk-AHSSFGDJ.cjs} +2777 -273
  27. package/dist/chunk-AHSSFGDJ.cjs.map +1 -0
  28. package/dist/{chunk-XA6XIAMN.cjs → chunk-ATE7LD6I.cjs} +4 -4
  29. package/dist/{chunk-XA6XIAMN.cjs.map → chunk-ATE7LD6I.cjs.map} +1 -1
  30. package/dist/{chunk-TP7LQ5PF.js → chunk-BP6MLXJU.js} +2596 -93
  31. package/dist/chunk-BP6MLXJU.js.map +1 -0
  32. package/dist/{chunk-2A5HJFOX.cjs → chunk-BXS53MLV.cjs} +4 -4
  33. package/dist/{chunk-2A5HJFOX.cjs.map → chunk-BXS53MLV.cjs.map} +1 -1
  34. package/dist/{chunk-HILYXWGJ.cjs → chunk-C2BNO3CI.cjs} +12 -12
  35. package/dist/{chunk-HILYXWGJ.cjs.map → chunk-C2BNO3CI.cjs.map} +1 -1
  36. package/dist/{chunk-NT6VVMLW.cjs → chunk-CAAMBDEN.cjs} +60 -4
  37. package/dist/chunk-CAAMBDEN.cjs.map +1 -0
  38. package/dist/{chunk-TYEQC7PV.js → chunk-D4QZ6UDO.js} +59 -5
  39. package/dist/chunk-D4QZ6UDO.js.map +1 -0
  40. package/dist/{chunk-MYVY55DO.cjs → chunk-FFAJQ36U.cjs} +15 -15
  41. package/dist/{chunk-MYVY55DO.cjs.map → chunk-FFAJQ36U.cjs.map} +1 -1
  42. package/dist/{chunk-VYAUTNHC.cjs → chunk-IOD2EFMX.cjs} +4 -4
  43. package/dist/{chunk-VYAUTNHC.cjs.map → chunk-IOD2EFMX.cjs.map} +1 -1
  44. package/dist/{chunk-WFVRUUJW.cjs → chunk-IU26USXA.cjs} +4 -4
  45. package/dist/{chunk-WFVRUUJW.cjs.map → chunk-IU26USXA.cjs.map} +1 -1
  46. package/dist/{chunk-CURAZVOH.js → chunk-JCJWSW5Y.js} +406 -4
  47. package/dist/chunk-JCJWSW5Y.js.map +1 -0
  48. package/dist/{chunk-IQLZUCWI.js → chunk-JEQGWH5N.js} +3 -3
  49. package/dist/{chunk-IQLZUCWI.js.map → chunk-JEQGWH5N.js.map} +1 -1
  50. package/dist/{chunk-L2NY4XEY.cjs → chunk-JYAL26WQ.cjs} +4 -4
  51. package/dist/chunk-JYAL26WQ.cjs.map +1 -0
  52. package/dist/{chunk-3DSNGR26.cjs → chunk-LFJE64RD.cjs} +408 -6
  53. package/dist/chunk-LFJE64RD.cjs.map +1 -0
  54. package/dist/{chunk-7JJQEECD.cjs → chunk-MZWVJFTV.cjs} +15 -15
  55. package/dist/{chunk-7JJQEECD.cjs.map → chunk-MZWVJFTV.cjs.map} +1 -1
  56. package/dist/{chunk-2ASZMLC3.cjs → chunk-O6A2GJLI.cjs} +4 -4
  57. package/dist/{chunk-2ASZMLC3.cjs.map → chunk-O6A2GJLI.cjs.map} +1 -1
  58. package/dist/{chunk-VWIKDJNV.js → chunk-OND4N5ZZ.js} +3 -3
  59. package/dist/{chunk-VWIKDJNV.js.map → chunk-OND4N5ZZ.js.map} +1 -1
  60. package/dist/{chunk-7Z2GDS4G.js → chunk-PUD7PIY5.js} +3 -3
  61. package/dist/{chunk-7Z2GDS4G.js.map → chunk-PUD7PIY5.js.map} +1 -1
  62. package/dist/{chunk-2RFFCZ3I.js → chunk-PZCYFT2A.js} +3 -3
  63. package/dist/{chunk-2RFFCZ3I.js.map → chunk-PZCYFT2A.js.map} +1 -1
  64. package/dist/{chunk-MERU76LW.js → chunk-QHZEGWE7.js} +3 -3
  65. package/dist/{chunk-MERU76LW.js.map → chunk-QHZEGWE7.js.map} +1 -1
  66. package/dist/{chunk-56DSMSXK.js → chunk-TDSGQT7R.js} +3 -3
  67. package/dist/{chunk-56DSMSXK.js.map → chunk-TDSGQT7R.js.map} +1 -1
  68. package/dist/{chunk-MUE45N2A.cjs → chunk-TFNH2NLJ.cjs} +4 -4
  69. package/dist/{chunk-MUE45N2A.cjs.map → chunk-TFNH2NLJ.cjs.map} +1 -1
  70. package/dist/{chunk-JR6JNQEZ.js → chunk-U4AJLMHC.js} +3 -3
  71. package/dist/{chunk-JR6JNQEZ.js.map → chunk-U4AJLMHC.js.map} +1 -1
  72. package/dist/{chunk-TWTUIUNK.js → chunk-UNLLWCQK.js} +3 -3
  73. package/dist/{chunk-TWTUIUNK.js.map → chunk-UNLLWCQK.js.map} +1 -1
  74. package/dist/{chunk-4MGALM2C.cjs → chunk-VG5LP5A4.cjs} +4 -4
  75. package/dist/{chunk-4MGALM2C.cjs.map → chunk-VG5LP5A4.cjs.map} +1 -1
  76. package/dist/{chunk-6BJKQULN.cjs → chunk-VOFND6ZQ.cjs} +4 -4
  77. package/dist/{chunk-6BJKQULN.cjs.map → chunk-VOFND6ZQ.cjs.map} +1 -1
  78. package/dist/{chunk-VY4JOTM2.js → chunk-X3GEGVW2.js} +3 -3
  79. package/dist/{chunk-VY4JOTM2.js.map → chunk-X3GEGVW2.js.map} +1 -1
  80. package/dist/{chunk-TMAOPJCT.js → chunk-XAYD5NVJ.js} +3 -3
  81. package/dist/{chunk-TMAOPJCT.js.map → chunk-XAYD5NVJ.js.map} +1 -1
  82. package/dist/{chunk-V3JTVTPY.cjs → chunk-Y4OBXYGW.cjs} +12 -12
  83. package/dist/{chunk-V3JTVTPY.cjs.map → chunk-Y4OBXYGW.cjs.map} +1 -1
  84. package/dist/{chunk-VZVREOTM.js → chunk-ZQECHIBI.js} +3 -3
  85. package/dist/{chunk-VZVREOTM.js.map → chunk-ZQECHIBI.js.map} +1 -1
  86. package/dist/{chunk-HJA7MIMV.js → chunk-ZUK4BY55.js} +3 -3
  87. package/dist/{chunk-HJA7MIMV.js.map → chunk-ZUK4BY55.js.map} +1 -1
  88. package/dist/{diagnostics-hObcaaFC.d.ts → diagnostics-fyjWjeDa.d.cts} +1 -1
  89. package/dist/{diagnostics-hObcaaFC.d.cts → diagnostics-fyjWjeDa.d.ts} +1 -1
  90. package/dist/diagrams/blockdiagram/index.cjs +6 -6
  91. package/dist/diagrams/blockdiagram/index.d.cts +1 -1
  92. package/dist/diagrams/blockdiagram/index.d.ts +1 -1
  93. package/dist/diagrams/blockdiagram/index.js +2 -2
  94. package/dist/diagrams/circuit/index.cjs +9 -9
  95. package/dist/diagrams/circuit/index.d.cts +1 -1
  96. package/dist/diagrams/circuit/index.d.ts +1 -1
  97. package/dist/diagrams/circuit/index.js +2 -2
  98. package/dist/diagrams/ecomap/index.cjs +7 -7
  99. package/dist/diagrams/ecomap/index.d.cts +1 -1
  100. package/dist/diagrams/ecomap/index.d.ts +1 -1
  101. package/dist/diagrams/ecomap/index.js +2 -2
  102. package/dist/diagrams/entity/index.cjs +6 -6
  103. package/dist/diagrams/entity/index.d.cts +1 -1
  104. package/dist/diagrams/entity/index.d.ts +1 -1
  105. package/dist/diagrams/entity/index.js +2 -2
  106. package/dist/diagrams/fishbone/index.cjs +8 -8
  107. package/dist/diagrams/fishbone/index.d.cts +1 -1
  108. package/dist/diagrams/fishbone/index.d.ts +1 -1
  109. package/dist/diagrams/fishbone/index.js +2 -2
  110. package/dist/diagrams/flowchart/index.cjs +8 -8
  111. package/dist/diagrams/flowchart/index.d.cts +2 -2
  112. package/dist/diagrams/flowchart/index.d.ts +2 -2
  113. package/dist/diagrams/flowchart/index.js +2 -2
  114. package/dist/diagrams/genogram/index.cjs +9 -9
  115. package/dist/diagrams/genogram/index.d.cts +1 -1
  116. package/dist/diagrams/genogram/index.d.ts +1 -1
  117. package/dist/diagrams/genogram/index.js +2 -2
  118. package/dist/diagrams/ladder/index.cjs +6 -6
  119. package/dist/diagrams/ladder/index.d.cts +1 -1
  120. package/dist/diagrams/ladder/index.d.ts +1 -1
  121. package/dist/diagrams/ladder/index.js +2 -2
  122. package/dist/diagrams/logic/index.cjs +8 -8
  123. package/dist/diagrams/logic/index.d.cts +1 -1
  124. package/dist/diagrams/logic/index.d.ts +1 -1
  125. package/dist/diagrams/logic/index.js +2 -2
  126. package/dist/diagrams/orgchart/index.cjs +8 -8
  127. package/dist/diagrams/orgchart/index.d.cts +1 -1
  128. package/dist/diagrams/orgchart/index.d.ts +1 -1
  129. package/dist/diagrams/orgchart/index.js +2 -2
  130. package/dist/diagrams/pedigree/index.cjs +7 -7
  131. package/dist/diagrams/pedigree/index.d.cts +1 -1
  132. package/dist/diagrams/pedigree/index.d.ts +1 -1
  133. package/dist/diagrams/pedigree/index.js +2 -2
  134. package/dist/diagrams/phylo/index.cjs +7 -7
  135. package/dist/diagrams/phylo/index.d.cts +1 -1
  136. package/dist/diagrams/phylo/index.d.ts +1 -1
  137. package/dist/diagrams/phylo/index.js +2 -2
  138. package/dist/diagrams/sld/index.cjs +8 -8
  139. package/dist/diagrams/sld/index.d.cts +1 -1
  140. package/dist/diagrams/sld/index.d.ts +1 -1
  141. package/dist/diagrams/sld/index.js +2 -2
  142. package/dist/diagrams/sociogram/index.cjs +6 -6
  143. package/dist/diagrams/sociogram/index.d.cts +1 -1
  144. package/dist/diagrams/sociogram/index.d.ts +1 -1
  145. package/dist/diagrams/sociogram/index.js +2 -2
  146. package/dist/diagrams/timing/index.d.cts +1 -1
  147. package/dist/diagrams/timing/index.d.ts +1 -1
  148. package/dist/diagrams/venn/index.cjs +9 -9
  149. package/dist/diagrams/venn/index.d.cts +1 -1
  150. package/dist/diagrams/venn/index.d.ts +1 -1
  151. package/dist/diagrams/venn/index.js +2 -2
  152. package/dist/{index-Cq8y1aaa.d.ts → index-CFaKjWPy.d.ts} +1 -1
  153. package/dist/{index-BiXWjQht.d.cts → index-DZNmJWGg.d.cts} +1 -1
  154. package/dist/index.cjs +111 -85
  155. package/dist/index.cjs.map +1 -1
  156. package/dist/index.d.cts +4 -4
  157. package/dist/index.d.ts +4 -4
  158. package/dist/index.js +48 -22
  159. package/dist/index.js.map +1 -1
  160. package/dist/react.cjs +18 -18
  161. package/dist/react.d.cts +2 -2
  162. package/dist/react.d.ts +2 -2
  163. package/dist/react.js +17 -17
  164. package/dist/{tools-BbTuTWs_.d.ts → tools-BDNih0Mb.d.ts} +3 -3
  165. package/dist/{tools-D5dkAqNy.d.cts → tools-C23t_WeN.d.cts} +3 -3
  166. package/package.json +1 -1
  167. package/dist/chunk-3DSNGR26.cjs.map +0 -1
  168. package/dist/chunk-CURAZVOH.js.map +0 -1
  169. package/dist/chunk-L2NY4XEY.cjs.map +0 -1
  170. package/dist/chunk-NT6VVMLW.cjs.map +0 -1
  171. package/dist/chunk-NTQBNBBP.cjs.map +0 -1
  172. package/dist/chunk-TP7LQ5PF.js.map +0 -1
  173. package/dist/chunk-TYEQC7PV.js.map +0 -1
  174. package/dist/chunk-VMUNKEB2.js.map +0 -1
@@ -1,21 +1,21 @@
1
- import { orgchart } from './chunk-HJA7MIMV.js';
2
- import { circuit } from './chunk-MERU76LW.js';
3
- import { blockdiagram } from './chunk-IQLZUCWI.js';
4
- import { ladder } from './chunk-VY4JOTM2.js';
5
- import { sld } from './chunk-VMUNKEB2.js';
6
- import { entity } from './chunk-TMAOPJCT.js';
7
- import { fishbone } from './chunk-KR7PPCC4.js';
8
- import { venn } from './chunk-TWTUIUNK.js';
9
- import { flowchart, layoutFlowchart } from './chunk-7Z2GDS4G.js';
10
- import { genogram } from './chunk-2RFFCZ3I.js';
11
- import { ecomap, estimateTextWidth } from './chunk-VWIKDJNV.js';
12
- import { pedigree } from './chunk-7SVB3XA7.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-VZVREOTM.js';
15
- import { sociogram } from './chunk-56DSMSXK.js';
14
+ import { phylo } from './chunk-ZQECHIBI.js';
15
+ import { sociogram } from './chunk-TDSGQT7R.js';
16
16
  import { timing } from './chunk-MTIZIHWE.js';
17
- import { logic } from './chunk-JR6JNQEZ.js';
18
- import { resolveBaseTheme, resolveTimelineTheme, resolveStateTheme, cssCustomProperties, resolvePetriTheme, resolveNetworkTheme, resolveUmlClassTheme, DEFAULT_FONT_FAMILY, FONT_SIZE, resolveReliabilityTheme, STROKE_WIDTH, resolveBowtieTheme, resolveMindmapTheme, resolveMatrixTheme, resolveBpmnTheme } from './chunk-TYEQC7PV.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
 
@@ -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;
@@ -7453,10 +7453,10 @@ function arrowMarker(t) {
7453
7453
  )
7454
7454
  ]);
7455
7455
  }
7456
- function classForBox(box) {
7457
- if (box.variant === "previous") return "prisma-stage-previous";
7458
- const base = box.variant === "exclusion" ? "prisma-exclusion" : "prisma-stage";
7459
- 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}`;
7460
7460
  }
7461
7461
  function classForLine(style) {
7462
7462
  switch (style) {
@@ -7540,22 +7540,22 @@ function renderHeader(h) {
7540
7540
  );
7541
7541
  return group({ "data-header": h.column }, [r6, ...lines]);
7542
7542
  }
7543
- function renderBox(box) {
7543
+ function renderBox(box2) {
7544
7544
  const r6 = rect({
7545
- x: box.x,
7546
- y: box.y,
7547
- width: box.width,
7548
- height: box.height,
7545
+ x: box2.x,
7546
+ y: box2.y,
7547
+ width: box2.width,
7548
+ height: box2.height,
7549
7549
  rx: PRISMA_CONST.BOX_RADIUS,
7550
7550
  ry: PRISMA_CONST.BOX_RADIUS,
7551
- class: classForBox(box),
7552
- "data-role": box.role
7551
+ class: classForBox(box2),
7552
+ "data-role": box2.role
7553
7553
  });
7554
- const innerLeft = box.x + PRISMA_CONST.BOX_PAD_X;
7555
- const innerRight = box.x + box.width - PRISMA_CONST.BOX_PAD_X;
7556
- 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;
7557
7557
  let widestIndented = 0;
7558
- for (const line2 of box.lines) {
7558
+ for (const line2 of box2.lines) {
7559
7559
  if ((line2.indent ?? 0) > 0) {
7560
7560
  const w = approxLineWidth(line2);
7561
7561
  if (w > widestIndented) widestIndented = w;
@@ -7565,9 +7565,9 @@ function renderBox(box) {
7565
7565
  innerLeft,
7566
7566
  Math.min(centerX - widestIndented / 2, innerRight - widestIndented)
7567
7567
  );
7568
- let textY = box.y + PRISMA_CONST.BOX_PAD_Y;
7568
+ let textY = box2.y + PRISMA_CONST.BOX_PAD_Y;
7569
7569
  const textEls = [];
7570
- for (const line2 of box.lines) {
7570
+ for (const line2 of box2.lines) {
7571
7571
  const lh = lineHeightForLine(line2.style);
7572
7572
  const baseline = textY + Math.round(lh * 0.75);
7573
7573
  const indented = (line2.indent ?? 0) > 0;
@@ -7595,9 +7595,9 @@ function renderBox(box) {
7595
7595
  }
7596
7596
  return group(
7597
7597
  {
7598
- "data-prisma-role": box.role,
7599
- "data-prisma-variant": box.variant,
7600
- "data-prisma-stage": box.stage
7598
+ "data-prisma-role": box2.role,
7599
+ "data-prisma-variant": box2.variant,
7600
+ "data-prisma-stage": box2.stage
7601
7601
  },
7602
7602
  [r6, ...textEls]
7603
7603
  );
@@ -8528,7 +8528,7 @@ function layoutUsecase(ast) {
8528
8528
  const y = cy - h / 2;
8529
8529
  const anchorX = side === "left" ? x + w : x;
8530
8530
  const anchorY = isRect ? cy : cy - 6;
8531
- const box = {
8531
+ const box2 = {
8532
8532
  actor: a,
8533
8533
  x,
8534
8534
  y,
@@ -8538,8 +8538,8 @@ function layoutUsecase(ast) {
8538
8538
  anchorX,
8539
8539
  anchorY
8540
8540
  };
8541
- actorBoxes.push(box);
8542
- actorById2.set(a.id, box);
8541
+ actorBoxes.push(box2);
8542
+ actorById2.set(a.id, box2);
8543
8543
  cy += C2.ACTOR_PITCH;
8544
8544
  }
8545
8545
  }
@@ -10126,7 +10126,7 @@ function layoutNetwork(ast, schedule) {
10126
10126
  y = cursor;
10127
10127
  cursor += C2.BOX_H + C2.V_GAP;
10128
10128
  }
10129
- const box = {
10129
+ const box2 = {
10130
10130
  id,
10131
10131
  task: t,
10132
10132
  computed,
@@ -10137,8 +10137,8 @@ function layoutNetwork(ast, schedule) {
10137
10137
  milestone: t.milestone,
10138
10138
  rank: r6
10139
10139
  };
10140
- boxes.push(box);
10141
- boxById.set(id, box);
10140
+ boxes.push(box2);
10141
+ boxById.set(id, box2);
10142
10142
  }
10143
10143
  }
10144
10144
  const edges = [];
@@ -10345,7 +10345,7 @@ function layoutSwimlane2(ast, schedule) {
10345
10345
  const w = t.milestone ? C2.MS_W : C2.BOX_W;
10346
10346
  const x = colX(r6) + (C2.BOX_W - w) / 2;
10347
10347
  const y = y0 + idx * (C2.BOX_H + C2.V_GAP);
10348
- const box = {
10348
+ const box2 = {
10349
10349
  id,
10350
10350
  task: t,
10351
10351
  computed: schedule.computed.get(id),
@@ -10356,8 +10356,8 @@ function layoutSwimlane2(ast, schedule) {
10356
10356
  milestone: t.milestone,
10357
10357
  rank: r6
10358
10358
  };
10359
- boxes.push(box);
10360
- boxById.set(id, box);
10359
+ boxes.push(box2);
10360
+ boxById.set(id, box2);
10361
10361
  });
10362
10362
  }
10363
10363
  }
@@ -10427,7 +10427,7 @@ function layoutTimescaled(ast, schedule) {
10427
10427
  const iv = intervalById.get(t.id);
10428
10428
  const lane = laneOf.get(t.id);
10429
10429
  const y = topPad + lane * (C2.TS_BOX_H + C2.TS_LANE_GAP);
10430
- const box = {
10430
+ const box2 = {
10431
10431
  id: t.id,
10432
10432
  task: t,
10433
10433
  computed: schedule.computed.get(t.id),
@@ -10438,8 +10438,8 @@ function layoutTimescaled(ast, schedule) {
10438
10438
  milestone: t.milestone,
10439
10439
  rank: 0
10440
10440
  };
10441
- boxes.push(box);
10442
- boxById.set(t.id, box);
10441
+ boxes.push(box2);
10442
+ boxById.set(t.id, box2);
10443
10443
  }
10444
10444
  const edges = [];
10445
10445
  for (const t of ast.tasks) {
@@ -13864,26 +13864,26 @@ function labelText(d) {
13864
13864
  function deviceFootprint(d) {
13865
13865
  return iconSize(d.kind);
13866
13866
  }
13867
- function effBox(box) {
13868
- 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);
13869
13869
  const half = labelW / 2;
13870
13870
  return {
13871
- left: box.cx - half,
13872
- top: box.y,
13873
- right: box.cx + half,
13874
- bottom: box.y + box.h + labelExtra(box.device)
13875
- };
13876
- }
13877
- function edgePoint(box, tx, ty) {
13878
- const dx = tx - box.cx;
13879
- const dy = ty - box.cy;
13880
- if (dx === 0 && dy === 0) return { x: box.cx, y: box.cy };
13881
- const hw = box.w / 2;
13882
- 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;
13883
13883
  const sx = dx !== 0 ? hw / Math.abs(dx) : Infinity;
13884
13884
  const sy = dy !== 0 ? hh / Math.abs(dy) : Infinity;
13885
13885
  const s = Math.min(sx, sy);
13886
- return { x: box.cx + dx * s, y: box.cy + dy * s };
13886
+ return { x: box2.cx + dx * s, y: box2.cy + dy * s };
13887
13887
  }
13888
13888
  function placeBanded(ast, ranks) {
13889
13889
  const lr = ast.direction === "lr";
@@ -17558,7 +17558,7 @@ function layoutFaultTree(ast) {
17558
17558
  bump(g.cx + g.width / 2, g.cy + g.height / 2);
17559
17559
  if (g.cond) bump(g.cond.x + g.cond.w / 2, g.cond.y + g.cond.h / 2);
17560
17560
  }
17561
- 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);
17562
17562
  for (const t of transfers) bump(t.x + 22, t.y + 30);
17563
17563
  if (ast.title) bump(C2.CANVAS_PAD + ast.title.length * 8.5, 0);
17564
17564
  if (ast.analysis.probability) {
@@ -17637,20 +17637,20 @@ function renderFaultTreeLayout(layout, config) {
17637
17637
  text({ x: layout.width / 2, y: 22, class: "sx-ft-title", "font-family": fontFamily, "text-anchor": "middle" }, ast.title)
17638
17638
  );
17639
17639
  }
17640
- for (const box of layout.cutSetBoxes) {
17640
+ for (const box2 of layout.cutSetBoxes) {
17641
17641
  inner.push(
17642
17642
  rect({
17643
- x: box.x,
17644
- y: box.y,
17645
- width: box.width,
17646
- height: box.height,
17643
+ x: box2.x,
17644
+ y: box2.y,
17645
+ width: box2.width,
17646
+ height: box2.height,
17647
17647
  rx: 6,
17648
17648
  class: "sx-ft-cutset",
17649
- "data-cutset": box.cutSet.events.join(","),
17650
- "data-cutset-index": String(box.index),
17651
- "data-order": String(box.cutSet.order),
17652
- ...box.cutSet.isSpof ? { "data-spof": "true" } : {},
17653
- ...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) } : {}
17654
17654
  })
17655
17655
  );
17656
17656
  }
@@ -21735,16 +21735,16 @@ function layoutMarkov(ast) {
21735
21735
  const tag = classOf?.[st.id];
21736
21736
  const isAbsorbing = tag === "absorbing";
21737
21737
  const pi = piMap?.[st.id] ?? perClassPi[st.id];
21738
- const box = {
21738
+ const box2 = {
21739
21739
  state: st,
21740
21740
  cx: c.x,
21741
21741
  cy: c.y,
21742
21742
  r: C2.STATE_R,
21743
21743
  isAbsorbing
21744
21744
  };
21745
- if (tag) box.classTag = tag;
21746
- if (pi !== void 0) box.pi = pi;
21747
- return box;
21745
+ if (tag) box2.classTag = tag;
21746
+ if (pi !== void 0) box2.pi = pi;
21747
+ return box2;
21748
21748
  });
21749
21749
  const arcKey = (a, b) => `${a}\0${b}`;
21750
21750
  const present = /* @__PURE__ */ new Set();
@@ -24163,13 +24163,13 @@ function parseFunction2(ast, rest, lineNo) {
24163
24163
  ast.warnings.push(`Line ${lineNo}: box "${id}" redeclared \u2014 keeping first declaration.`);
24164
24164
  return;
24165
24165
  }
24166
- const box = {
24166
+ const box2 = {
24167
24167
  id,
24168
24168
  name,
24169
24169
  // analysis assigns the real number; explicit wins if given.
24170
24170
  number: explicitNumber ?? 0
24171
24171
  };
24172
- ast.boxes.push(box);
24172
+ ast.boxes.push(box2);
24173
24173
  }
24174
24174
  function parseRoleArrow(ast, role, rest, lineNo) {
24175
24175
  const idM = /^([A-Za-z_]\w*)/.exec(rest);
@@ -24441,8 +24441,8 @@ function layoutIdef0(astIn) {
24441
24441
  const C2 = IDEF0_CONST;
24442
24442
  const ox = C2.MARGIN;
24443
24443
  const oy = C2.MARGIN + C2.TITLE_H;
24444
- const boxes = ast.boxes.map((box, idx) => ({
24445
- box,
24444
+ const boxes = ast.boxes.map((box2, idx) => ({
24445
+ box: box2,
24446
24446
  x: ox + idx * C2.STEP_X,
24447
24447
  y: oy + idx * C2.STEP_Y,
24448
24448
  width: C2.BOX_W,
@@ -35971,6 +35971,2508 @@ var sfc = {
35971
35971
  }
35972
35972
  };
35973
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
+
35974
38476
  // src/core/api.ts
35975
38477
  var plugins = [
35976
38478
  genogram,
@@ -36017,7 +38519,8 @@ var plugins = [
36017
38519
  epc,
36018
38520
  idef0,
36019
38521
  threatmodel,
36020
- welding
38522
+ welding,
38523
+ floorplan
36021
38524
  ];
36022
38525
  function detectPlugin(text2, config) {
36023
38526
  if (config?.type) {
@@ -36028,7 +38531,7 @@ function detectPlugin(text2, config) {
36028
38531
  if (plugin.detect(text2)) return plugin;
36029
38532
  }
36030
38533
  throw new Error(
36031
- "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'."
36032
38535
  );
36033
38536
  }
36034
38537
  function stripCodeFences(text2) {
@@ -36165,6 +38668,6 @@ function renderWithPlugin(prepared, plugin, config) {
36165
38668
  return plugin.render(prepared, renderConfig);
36166
38669
  }
36167
38670
 
36168
- 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 };
36169
- //# sourceMappingURL=chunk-TP7LQ5PF.js.map
36170
- //# sourceMappingURL=chunk-TP7LQ5PF.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