floor-editor-ts 1.3.6 → 1.3.8

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.
@@ -7091,6 +7091,108 @@ var localStore = {
7091
7091
  }
7092
7092
  };
7093
7093
  //#endregion
7094
+ //#region src/lib/utils/heatmapColorScale.ts
7095
+ function normalizeHeatmapColorScale(value) {
7096
+ if (!value || typeof value !== "object") return void 0;
7097
+ const raw = value;
7098
+ const mode = raw.mode === "levels" ? "levels" : raw.mode === "gradient" ? "gradient" : void 0;
7099
+ if (!mode) return void 0;
7100
+ const hideBelow = typeof raw.hideBelow === "number" && Number.isFinite(raw.hideBelow) ? raw.hideBelow : 0;
7101
+ const levelsIn = Array.isArray(raw.levels) ? raw.levels : [];
7102
+ const levels = [];
7103
+ for (const item of levelsIn) {
7104
+ if (!item || typeof item !== "object") continue;
7105
+ const row = item;
7106
+ const min = typeof row.min === "number" && Number.isFinite(row.min) ? row.min : NaN;
7107
+ const color = typeof row.color === "string" && row.color.trim() ? row.color.trim() : "";
7108
+ if (!Number.isFinite(min) || !color) continue;
7109
+ const level = {
7110
+ min,
7111
+ color
7112
+ };
7113
+ if (typeof row.max === "number" && Number.isFinite(row.max)) level.max = row.max;
7114
+ if (typeof row.label === "string" && row.label.trim()) level.label = row.label.trim();
7115
+ levels.push(level);
7116
+ }
7117
+ levels.sort((a, b) => a.min - b.min);
7118
+ if (mode === "levels" && levels.length === 0) return {
7119
+ mode: "gradient",
7120
+ hideBelow
7121
+ };
7122
+ return {
7123
+ mode,
7124
+ hideBelow,
7125
+ ...levels.length ? { levels } : {}
7126
+ };
7127
+ }
7128
+ /** Fingerprint for heatmap offscreen cache invalidation. */
7129
+ function getHeatmapColorScaleKey(scale) {
7130
+ var _scale$hideBelow, _scale$levels;
7131
+ if (!scale) return "gradient";
7132
+ const hide = (_scale$hideBelow = scale.hideBelow) !== null && _scale$hideBelow !== void 0 ? _scale$hideBelow : 0;
7133
+ if (scale.mode !== "levels" || !((_scale$levels = scale.levels) === null || _scale$levels === void 0 ? void 0 : _scale$levels.length)) return `gradient:${hide}`;
7134
+ return `levels:${hide}:${scale.levels.map((l) => {
7135
+ var _l$max, _l$label;
7136
+ return `${l.min}:${(_l$max = l.max) !== null && _l$max !== void 0 ? _l$max : ""}:${l.color}:${(_l$label = l.label) !== null && _l$label !== void 0 ? _l$label : ""}`;
7137
+ }).join("|")}`;
7138
+ }
7139
+ function colorWithAlpha(color, alpha) {
7140
+ const a = Math.max(0, Math.min(1, alpha));
7141
+ const c = color.trim();
7142
+ const rgbaMatch = c.match(/^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*[\d.]+\s*)?\)$/i);
7143
+ if (rgbaMatch) return `rgba(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]}, ${a})`;
7144
+ let hex = c;
7145
+ const short = hex.match(/^#([0-9a-f]{3})$/i);
7146
+ if (short) {
7147
+ const h = short[1];
7148
+ hex = `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`;
7149
+ }
7150
+ const long = hex.match(/^#([0-9a-f]{6})$/i);
7151
+ if (long) {
7152
+ const n = parseInt(long[1], 16);
7153
+ return `rgba(${n >> 16 & 255}, ${n >> 8 & 255}, ${n & 255}, ${a})`;
7154
+ }
7155
+ const longA = hex.match(/^#([0-9a-f]{8})$/i);
7156
+ if (longA) {
7157
+ const n = parseInt(longA[1], 16);
7158
+ return `rgba(${n >> 24 & 255}, ${n >> 16 & 255}, ${n >> 8 & 255}, ${a})`;
7159
+ }
7160
+ return c;
7161
+ }
7162
+ /**
7163
+ * Resolve discrete band color for a cell value.
7164
+ * @returns null → do not draw
7165
+ */
7166
+ function resolveHeatmapLevelColor(value, scale) {
7167
+ var _scale$hideBelow2, _scale$levels2;
7168
+ if (!(value > ((_scale$hideBelow2 = scale.hideBelow) !== null && _scale$hideBelow2 !== void 0 ? _scale$hideBelow2 : 0))) return null;
7169
+ const levels = (_scale$levels2 = scale.levels) !== null && _scale$levels2 !== void 0 ? _scale$levels2 : [];
7170
+ if (!levels.length) return null;
7171
+ const sorted = [...levels].sort((a, b) => a.min - b.min);
7172
+ const levelCount = sorted.length;
7173
+ if (sorted.every((l) => typeof l.max === "number" && Number.isFinite(l.max))) {
7174
+ const idx = sorted.findIndex((l) => value >= l.min && value <= l.max);
7175
+ if (idx < 0) return null;
7176
+ return {
7177
+ color: sorted[idx].color,
7178
+ levelIndex: idx,
7179
+ levelCount
7180
+ };
7181
+ }
7182
+ for (let i = 0; i < sorted.length; i++) {
7183
+ const upper = i + 1 < sorted.length ? sorted[i + 1].min : Infinity;
7184
+ if (value >= sorted[i].min && value < upper) return {
7185
+ color: sorted[i].color,
7186
+ levelIndex: i,
7187
+ levelCount
7188
+ };
7189
+ }
7190
+ return null;
7191
+ }
7192
+ function isLevelsColorScale(scale) {
7193
+ return !!scale && scale.mode === "levels" && Array.isArray(scale.levels) && scale.levels.length > 0;
7194
+ }
7195
+ //#endregion
7094
7196
  //#region src/lib/packageDefaults.ts
7095
7197
  /** Flatten host camera object into string props for furniture + UI. */
7096
7198
  function assignedCameraToProps(cam) {
@@ -7110,6 +7212,7 @@ var DEFAULT_CONFIG = {
7110
7212
  floorMaxHeight: 400,
7111
7213
  heatmapEnabled: false,
7112
7214
  heatmapMatrix: [],
7215
+ heatmapColorScale: void 0,
7113
7216
  projectName: void 0,
7114
7217
  selectedCameraSerial: null,
7115
7218
  showCameraCones: false,
@@ -7120,7 +7223,8 @@ var DEFAULT_CONFIG = {
7120
7223
  cameraThumbnail: "",
7121
7224
  cameraThumbnailOpacity: .65,
7122
7225
  canvasBackground: "#f8f9fa",
7123
- floorPlateBackground: "#f8f9fa"
7226
+ floorPlateBackground: "#f8f9fa",
7227
+ headerToolsMode: "full"
7124
7228
  };
7125
7229
  function normalizeConfig(value) {
7126
7230
  if (!value || typeof value !== "object") return { ...DEFAULT_CONFIG };
@@ -7138,6 +7242,8 @@ function normalizeConfig(value) {
7138
7242
  const opacity = typeof value.cameraThumbnailOpacity === "number" && value.cameraThumbnailOpacity >= 0 && value.cameraThumbnailOpacity <= 1 ? value.cameraThumbnailOpacity : DEFAULT_CONFIG.cameraThumbnailOpacity;
7139
7243
  const canvasBackground = normalizeColor$1(value.canvasBackground, DEFAULT_CONFIG.canvasBackground);
7140
7244
  const floorPlateBackground = normalizeColor$1(value.floorPlateBackground, DEFAULT_CONFIG.floorPlateBackground);
7245
+ const headerToolsMode = value.headerToolsMode === "compact" || value.headerToolsMode === "auto" || value.headerToolsMode === "full" ? value.headerToolsMode : DEFAULT_CONFIG.headerToolsMode;
7246
+ const heatmapColorScale = normalizeHeatmapColorScale(value.heatmapColorScale);
7141
7247
  return {
7142
7248
  ...DEFAULT_CONFIG,
7143
7249
  ...value,
@@ -7146,7 +7252,9 @@ function normalizeConfig(value) {
7146
7252
  cameraThumbnail: image,
7147
7253
  cameraThumbnailOpacity: opacity,
7148
7254
  canvasBackground,
7149
- floorPlateBackground
7255
+ floorPlateBackground,
7256
+ headerToolsMode,
7257
+ heatmapColorScale
7150
7258
  };
7151
7259
  }
7152
7260
  function normalizeColor$1(value, fallback) {
@@ -7155,16 +7263,31 @@ function normalizeColor$1(value, fallback) {
7155
7263
  if (/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(trimmed)) return trimmed;
7156
7264
  return fallback;
7157
7265
  }
7266
+ function isFloorLike(value) {
7267
+ return !!value && typeof value === "object" && typeof value.id === "string" && Array.isArray(value.walls);
7268
+ }
7269
+ /**
7270
+ * Accepts a full Project, `{ floors }`, a floors array, or a single Floor.
7271
+ * Furniture (incl. cameras) is preserved as given.
7272
+ */
7158
7273
  function normalizeFloorData(data) {
7159
7274
  if (!data || typeof data !== "object") return createDefaultProject();
7160
- if (!Array.isArray(data.floors) || data.floors.length === 0) return createDefaultProject();
7161
- if (!data.activeFloorId || !data.floors.some((f) => f.id === data.activeFloorId)) return {
7275
+ let floors;
7276
+ let rest = {};
7277
+ if (Array.isArray(data)) floors = data.filter(isFloorLike);
7278
+ else if (isFloorLike(data) && !Array.isArray(data.floors)) floors = [data];
7279
+ else {
7280
+ rest = data;
7281
+ floors = Array.isArray(rest.floors) ? rest.floors.filter(isFloorLike) : void 0;
7282
+ }
7283
+ if (!floors || floors.length === 0) return createDefaultProject();
7284
+ const activeFloorId = rest.activeFloorId && floors.some((f) => f.id === rest.activeFloorId) ? rest.activeFloorId : floors[0].id;
7285
+ return {
7162
7286
  ...createDefaultProject(),
7163
- ...data,
7164
- floors: data.floors,
7165
- activeFloorId: data.floors[0].id
7287
+ ...rest,
7288
+ floors,
7289
+ activeFloorId
7166
7290
  };
7167
- return data;
7168
7291
  }
7169
7292
  var RUNTIME_FURNITURE_FIELDS = [
7170
7293
  "showHeatmap",
@@ -56264,11 +56387,11 @@ var root_7$6 = /* @__PURE__ */ from_html(`<div class="flex items-center justify-
56264
56387
  before:content-[''] before:absolute before:w-4 before:h-4 before:rounded-full before:bg-white before:top-0.5 before:left-0.5 before:transition-transform checked:before:translate-x-5"/></label> <label class="flex items-center justify-between px-4 py-3.5 cursor-pointer"><span class="text-sm text-gray-700">Extension Lines</span> <input type="checkbox" class="w-10 h-5 rounded-full appearance-none cursor-pointer bg-gray-300 checked:bg-slate-700 relative transition-colors
56265
56388
  before:content-[''] before:absolute before:w-4 before:h-4 before:rounded-full before:bg-white before:top-0.5 before:left-0.5 before:transition-transform checked:before:translate-x-5"/></label> <label class="flex items-center justify-between px-4 py-3.5 cursor-pointer"><span class="text-sm text-gray-700">Object Distance</span> <input type="checkbox" class="w-10 h-5 rounded-full appearance-none cursor-pointer bg-gray-300 checked:bg-slate-700 relative transition-colors
56266
56389
  before:content-[''] before:absolute before:w-4 before:h-4 before:rounded-full before:bg-white before:top-0.5 before:left-0.5 before:transition-transform checked:before:translate-x-5"/></label> <div class="flex items-center justify-between px-4 py-3.5"><span class="text-sm text-gray-700">Line Color</span> <div class="flex items-center gap-2"><button style="background-color: #ffffff" aria-label="White line color"></button> <span class="text-gray-300">|</span> <button style="background-color: #1e293b" aria-label="Dark line color"></button></div></div></div>`, 1);
56267
- var root_9$2 = /* @__PURE__ */ from_html(`<button><span class="text-2xl"> </span> <span class="text-xs font-medium text-gray-700 dark:text-gray-300"> </span></button>`);
56390
+ var root_9$3 = /* @__PURE__ */ from_html(`<button><span class="text-2xl"> </span> <span class="text-xs font-medium text-gray-700 dark:text-gray-300"> </span></button>`);
56268
56391
  var root_8$3 = /* @__PURE__ */ from_html(`<div class="space-y-4"><div><span class="text-sm font-medium text-gray-700 dark:text-gray-300 block mb-3">Theme</span> <div class="flex gap-3"></div></div></div>`);
56269
- var root_11$2 = /* @__PURE__ */ from_html(`<button class="px-4 py-2 text-sm font-medium text-red-600 dark:text-red-400 border border-red-300 dark:border-red-600 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors">Remove</button>`);
56392
+ var root_11$3 = /* @__PURE__ */ from_html(`<button class="px-4 py-2 text-sm font-medium text-red-600 dark:text-red-400 border border-red-300 dark:border-red-600 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors">Remove</button>`);
56270
56393
  var root_12$3 = /* @__PURE__ */ from_html(`<button class="px-4 py-2 text-sm font-medium text-red-600 dark:text-red-400 border border-red-300 dark:border-red-600 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors">Remove</button>`);
56271
- var root_10$2 = /* @__PURE__ */ from_html(`<div class="space-y-4"><div><span class="text-sm font-medium text-gray-700 dark:text-gray-300 block mb-1">Gemini API Key</span> <p class="text-xs text-gray-500 dark:text-gray-400 mb-3">Required for AI-powered photorealistic rendering. Your key is stored locally in your browser only - never sent to our servers.</p> <div class="flex gap-2"><div class="relative flex-1"><input class="w-full px-3 py-2 pr-10 border border-gray-300 dark:border-gray-600 rounded-lg text-sm font-mono focus:ring-2 focus:ring-slate-500 focus:border-slate-500 outline-none bg-white dark:bg-gray-700 dark:text-gray-100" placeholder="AIza..."/> <button class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 text-sm"> </button></div></div> <div class="flex gap-2 mt-3"><button class="px-4 py-2 text-sm font-medium bg-slate-700 text-white rounded-lg hover:bg-slate-600 transition-colors"> </button> <!></div></div> <div class="border-t border-gray-200 dark:border-gray-700 pt-4"><span class="text-sm font-medium text-gray-700 dark:text-gray-300 block mb-2">How to get a Gemini key</span> <ol class="text-xs text-gray-500 dark:text-gray-400 space-y-1 list-decimal list-inside"><li>Go to <a href="https://aistudio.google.com/apikey" target="_blank" rel="noopener" class="text-blue-500 hover:underline">Google AI Studio</a></li> <li>Click "Create API Key"</li> <li>Copy and paste it above</li></ol></div> <div class="border-t border-gray-200 dark:border-gray-700 pt-4"><span class="text-sm font-medium text-gray-700 dark:text-gray-300 block mb-1">OpenAI API Key</span> <p class="text-xs text-gray-500 dark:text-gray-400 mb-3">Optional — enables OpenAI image generation as an alternative to Gemini. Stored locally only.</p> <div class="flex gap-2"><div class="relative flex-1"><input class="w-full px-3 py-2 pr-10 border border-gray-300 dark:border-gray-600 rounded-lg text-sm font-mono focus:ring-2 focus:ring-slate-500 focus:border-slate-500 outline-none bg-white dark:bg-gray-700 dark:text-gray-100" placeholder="sk-..."/> <button class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 text-sm"> </button></div></div> <div class="flex gap-2 mt-3"><button class="px-4 py-2 text-sm font-medium bg-slate-700 text-white rounded-lg hover:bg-slate-600 transition-colors"> </button> <!></div> <div class="mt-3"><span class="text-sm font-medium text-gray-700 dark:text-gray-300 block mb-2">How to get an OpenAI key</span> <ol class="text-xs text-gray-500 dark:text-gray-400 space-y-1 list-decimal list-inside"><li>Go to <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener" class="text-blue-500 hover:underline">OpenAI Platform</a></li> <li>Click "Create new secret key"</li> <li>Copy and paste it above</li></ol></div></div></div>`);
56394
+ var root_10$3 = /* @__PURE__ */ from_html(`<div class="space-y-4"><div><span class="text-sm font-medium text-gray-700 dark:text-gray-300 block mb-1">Gemini API Key</span> <p class="text-xs text-gray-500 dark:text-gray-400 mb-3">Required for AI-powered photorealistic rendering. Your key is stored locally in your browser only - never sent to our servers.</p> <div class="flex gap-2"><div class="relative flex-1"><input class="w-full px-3 py-2 pr-10 border border-gray-300 dark:border-gray-600 rounded-lg text-sm font-mono focus:ring-2 focus:ring-slate-500 focus:border-slate-500 outline-none bg-white dark:bg-gray-700 dark:text-gray-100" placeholder="AIza..."/> <button class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 text-sm"> </button></div></div> <div class="flex gap-2 mt-3"><button class="px-4 py-2 text-sm font-medium bg-slate-700 text-white rounded-lg hover:bg-slate-600 transition-colors"> </button> <!></div></div> <div class="border-t border-gray-200 dark:border-gray-700 pt-4"><span class="text-sm font-medium text-gray-700 dark:text-gray-300 block mb-2">How to get a Gemini key</span> <ol class="text-xs text-gray-500 dark:text-gray-400 space-y-1 list-decimal list-inside"><li>Go to <a href="https://aistudio.google.com/apikey" target="_blank" rel="noopener" class="text-blue-500 hover:underline">Google AI Studio</a></li> <li>Click "Create API Key"</li> <li>Copy and paste it above</li></ol></div> <div class="border-t border-gray-200 dark:border-gray-700 pt-4"><span class="text-sm font-medium text-gray-700 dark:text-gray-300 block mb-1">OpenAI API Key</span> <p class="text-xs text-gray-500 dark:text-gray-400 mb-3">Optional — enables OpenAI image generation as an alternative to Gemini. Stored locally only.</p> <div class="flex gap-2"><div class="relative flex-1"><input class="w-full px-3 py-2 pr-10 border border-gray-300 dark:border-gray-600 rounded-lg text-sm font-mono focus:ring-2 focus:ring-slate-500 focus:border-slate-500 outline-none bg-white dark:bg-gray-700 dark:text-gray-100" placeholder="sk-..."/> <button class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 text-sm"> </button></div></div> <div class="flex gap-2 mt-3"><button class="px-4 py-2 text-sm font-medium bg-slate-700 text-white rounded-lg hover:bg-slate-600 transition-colors"> </button> <!></div> <div class="mt-3"><span class="text-sm font-medium text-gray-700 dark:text-gray-300 block mb-2">How to get an OpenAI key</span> <ol class="text-xs text-gray-500 dark:text-gray-400 space-y-1 list-decimal list-inside"><li>Go to <a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener" class="text-blue-500 hover:underline">OpenAI Platform</a></li> <li>Click "Create new secret key"</li> <li>Copy and paste it above</li></ol></div></div></div>`);
56272
56395
  var root_1$13 = /* @__PURE__ */ from_html(`<div class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center" role="dialog" tabindex="-1" aria-label="Settings"><div class="bg-white dark:bg-gray-800 rounded-xl shadow-2xl w-[420px] max-h-[80vh] flex flex-col" role="document"><div class="flex items-center justify-between px-5 pt-4 pb-2"><h2 class="text-lg font-bold text-gray-800 dark:text-gray-100">Settings</h2> <button class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 text-xl leading-none" aria-label="Close settings">✕</button></div> <div class="flex border-b border-gray-200 dark:border-gray-700 px-5"><button>Project <!></button> <button>Dimensions <!></button> <button>Appearance <!></button> <button>AI <!></button></div> <div class="p-5 overflow-y-auto"><!></div></div></div>`);
56273
56396
  function SettingsDialog($$anchor, $$props) {
56274
56397
  push($$props, true);
@@ -56519,7 +56642,7 @@ function SettingsDialog($$anchor, $$props) {
56519
56642
  let value = () => get($$array)[0];
56520
56643
  let icon = () => get($$array)[1];
56521
56644
  let label = () => get($$array)[2];
56522
- var button_9 = root_9$2();
56645
+ var button_9 = root_9$3();
56523
56646
  var span = child(button_9);
56524
56647
  var text = child(span, true);
56525
56648
  reset(span);
@@ -56541,7 +56664,7 @@ function SettingsDialog($$anchor, $$props) {
56541
56664
  append($$anchor, div_15);
56542
56665
  };
56543
56666
  var consequent_9 = ($$anchor) => {
56544
- var div_18 = root_10$2();
56667
+ var div_18 = root_10$3();
56545
56668
  var div_19 = child(div_18);
56546
56669
  var div_20 = sibling(child(div_19), 4);
56547
56670
  var div_21 = child(div_20);
@@ -56558,7 +56681,7 @@ function SettingsDialog($$anchor, $$props) {
56558
56681
  reset(button_11);
56559
56682
  var node_6 = sibling(button_11, 2);
56560
56683
  var consequent_7 = ($$anchor) => {
56561
- var button_12 = root_11$2();
56684
+ var button_12 = root_11$3();
56562
56685
  delegated("click", button_12, clearGeminiKey);
56563
56686
  append($$anchor, button_12);
56564
56687
  };
@@ -57129,14 +57252,19 @@ init_onboarding_svelte();
57129
57252
  var root_2$10 = /* @__PURE__ */ from_html(`<input type="text" class="bg-white/20 text-white font-semibold px-2 py-0.5 rounded border border-white/30 outline-none text-sm w-40"/>`);
57130
57253
  var root_3$9 = /* @__PURE__ */ from_html(`<button class="font-semibold text-white text-sm hover:bg-white/10 px-2 py-0.5 rounded transition-colors" title="Click to rename"> </button>`);
57131
57254
  var root_4$8 = /* @__PURE__ */ from_html(`<button> </button>`);
57132
- var root_5$5 = /* @__PURE__ */ from_html(`<div class="flex bg-white/15 rounded-full p-0.5"><button title="Select mode (V)" aria-label="Select mode"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"></path><path d="M13 13l6 6"></path></svg></button> <button title="Pan mode (H)" aria-label="Pan mode"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 11V6a2 2 0 0 0-4 0v1"></path><path d="M14 10V4a2 2 0 0 0-4 0v2"></path><path d="M10 10.5V6a2 2 0 0 0-4 0v8"></path><path d="M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"></path></svg></button></div>`);
57133
- var root_6$4 = /* @__PURE__ */ from_html(`<div class="flex items-center gap-1 bg-white/15 rounded-full p-0.5"><button class="w-7 h-7 flex items-center justify-center text-white/80 hover:text-white hover:bg-white/10 rounded-full transition-colors text-sm font-bold" title="Zoom Out (−)" aria-label="Zoom Out">−</button> <button class="px-2 py-1 text-xs font-medium text-white/80 hover:text-white hover:bg-white/10 rounded-full transition-colors min-w-[3rem] text-center" title="Reset Zoom (100%)"> </button> <button class="w-7 h-7 flex items-center justify-center text-white/80 hover:text-white hover:bg-white/10 rounded-full transition-colors text-sm font-bold" title="Zoom In (+)" aria-label="Zoom In">+</button></div>`);
57134
- var root_8$2 = /* @__PURE__ */ from_html(`<button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg> Import JSON</button>`);
57135
- var root_7$5 = /* @__PURE__ */ from_html(`<div class="absolute right-0 top-full mt-1 bg-white rounded-lg shadow-lg border border-gray-200 py-1 w-48 z-50"><button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"></polyline><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path><rect x="6" y="14" width="12" height="8"></rect></svg> Print Layout</button> <div class="h-px bg-gray-100 my-1"></div> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><path d="M21 15l-5-5L5 21"></path></svg> Export 2D as PNG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"></path><path d="M2 17l10 5 10-5"></path><path d="M2 12l10 5 10-5"></path></svg> Export 3D as PNG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 19V5"></path><path d="M5 12l7-7 7 7"></path></svg> Export as SVG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M8 16h2"></path><path d="M14 16h2"></path></svg> Export as DXF</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M9 16h6"></path></svg> Export as DWG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M16 11v6"></path><path d="M8 11v6"></path><path d="M12 11v6"></path></svg> Export as PDF</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path></svg> Download JSON</button> <div class="h-px bg-gray-100 my-1"></div> <!> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg> New Project</button></div>`);
57136
- var root_1$10 = /* @__PURE__ */ from_html(`<div class="h-12 bg-gradient-to-r from-slate-800 to-slate-700 flex items-center px-4 gap-3 shrink-0 shadow-sm"><button type="button"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2.5"></rect><path d="M9 3v18"></path></svg></button> <!> <div class="h-5 w-px bg-white/20"></div> <div class="flex items-center gap-1"><!> <button class="text-white/80 hover:text-white text-xs hover:bg-white/10 px-1.5 py-0.5 rounded transition-colors" title="Add Floor" aria-label="Add Floor">+</button> <span class="text-white/40 text-[10px] ml-1"> </span></div> <div class="flex-1"></div> <button class="p-1.5 text-white/80 hover:text-white hover:bg-white/10 rounded transition-colors" title="Undo (Ctrl+Z)" aria-label="Undo"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg></button> <button class="p-1.5 text-white/80 hover:text-white hover:bg-white/10 rounded transition-colors" title="Redo (Ctrl+Y)" aria-label="Redo"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><path d="M20.49 15a9 9 0 1 1-2.13-9.36L23 10"></path></svg></button> <div class="h-5 w-px bg-white/20"></div> <button aria-label="Snap to Grid"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg></button> <!> <button aria-label="Toggle Furniture"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="12" width="20" height="8" rx="1"></rect><path d="M4 12V7a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v5"></path><line x1="12" y1="12" x2="12" y2="20"></line></svg></button> <div class="h-5 w-px bg-white/20"></div> <!> <button class="p-1.5 text-white/80 hover:text-white hover:bg-white/10 rounded transition-colors" title="Version History" aria-label="Version History"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg></button> <button class="px-2 py-1.5 text-white/80 hover:text-white hover:bg-white/10 rounded transition-colors" title="Area Summary" aria-label="Area Summary"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><path d="M3 9h18"></path><path d="M9 3v18"></path></svg></button> <button class="px-2 py-1.5 text-white/80 hover:text-white hover:bg-white/10 rounded transition-colors" title="Settings" aria-label="Settings"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg></button> <div class="h-5 w-px bg-white/20"></div> <div class="relative"><button class="px-3 py-1.5 text-sm text-white/90 hover:text-white hover:bg-white/10 rounded transition-colors flex items-center gap-1.5"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg> Export</button> <!></div> <span><!></span> <button class="px-3 py-1.5 text-sm bg-white text-slate-800 font-semibold rounded-lg hover:bg-blue-50 transition-colors shadow-sm">Save</button></div>`);
57137
- var root_13$2 = /* @__PURE__ */ from_html(`<div class="absolute right-0 top-full mt-1 bg-white rounded-lg shadow-lg border border-gray-200 py-1 w-48 z-50"><button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"></polyline><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path><rect x="6" y="14" width="12" height="8"></rect></svg> Print Layout</button> <div class="h-px bg-gray-100 my-1"></div> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><path d="M21 15l-5-5L5 21"></path></svg> Export 2D as PNG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"></path><path d="M2 17l10 5 10-5"></path><path d="M2 12l10 5 10-5"></path></svg> Export 3D as PNG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 19V5"></path><path d="M5 12l7-7 7 7"></path></svg> Export as SVG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M8 16h2"></path><path d="M14 16h2"></path></svg> Export as DXF</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M9 16h6"></path></svg> Export as DWG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M16 11v6"></path><path d="M8 11v6"></path><path d="M12 11v6"></path></svg> Export as PDF</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path></svg> Download JSON</button> <div class="h-px bg-gray-100 my-1"></div> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg> Import JSON</button></div>`);
57138
- var root_12$2 = /* @__PURE__ */ from_html(`<div class="h-12 bg-gradient-to-r from-slate-800 to-slate-700 flex items-center justify-between px-4 gap-3 shrink-0 shadow-sm"><span class="font-semibold text-white text-sm px-2 py-0.5 rounded transition-colors"> </span> <div class="relative"><button class="px-3 py-1.5 text-sm text-white/90 hover:text-white hover:bg-white/10 rounded transition-colors flex items-center gap-1.5"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg> Import</button> <!></div></div>`);
57139
- var root_14$2 = /* @__PURE__ */ from_html(`<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40"><div class="bg-white rounded-xl shadow-2xl w-[420px] max-h-[80vh] overflow-hidden"><div class="flex items-center justify-between px-5 py-3 border-b border-gray-200"><h2 class="text-base font-semibold text-gray-800">📐 Area Summary</h2> <button class="text-gray-400 hover:text-gray-600 text-xl leading-none">&times;</button></div> <div class="overflow-y-auto max-h-[calc(80vh-52px)] p-1"><!></div></div></div>`);
57255
+ var root_5$5 = /* @__PURE__ */ from_html(`<button aria-label="Snap to Grid"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg></button>`);
57256
+ var root_6$4 = /* @__PURE__ */ from_html(`<div class="flex bg-white/15 rounded-full p-0.5 shrink-0"><button title="Select mode (V)" aria-label="Select mode"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"></path><path d="M13 13l6 6"></path></svg></button> <button title="Pan mode (H)" aria-label="Pan mode"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 11V6a2 2 0 0 0-4 0v1"></path><path d="M14 10V4a2 2 0 0 0-4 0v2"></path><path d="M10 10.5V6a2 2 0 0 0-4 0v8"></path><path d="M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"></path></svg></button></div>`);
57257
+ var root_7$5 = /* @__PURE__ */ from_html(`<button aria-label="Toggle Furniture"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="12" width="20" height="8" rx="1"></rect><path d="M4 12V7a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v5"></path><line x1="12" y1="12" x2="12" y2="20"></line></svg></button>`);
57258
+ var root_8$2 = /* @__PURE__ */ from_html(`<div class="flex items-center gap-1 bg-white/15 rounded-full p-0.5 shrink-0"><button class="w-7 h-7 flex items-center justify-center text-white/80 hover:text-white hover:bg-white/10 rounded-full transition-colors text-sm font-bold" title="Zoom Out (−)" aria-label="Zoom Out">−</button> <button class="px-2 py-1 text-xs font-medium text-white/80 hover:text-white hover:bg-white/10 rounded-full transition-colors min-w-[3rem] text-center" title="Reset Zoom (100%)"> </button> <button class="w-7 h-7 flex items-center justify-center text-white/80 hover:text-white hover:bg-white/10 rounded-full transition-colors text-sm font-bold" title="Zoom In (+)" aria-label="Zoom In">+</button></div>`);
57259
+ var root_10$2 = /* @__PURE__ */ from_html(`<div class="absolute right-0 top-full mt-1 bg-white rounded-lg shadow-lg border border-gray-200 py-1 w-56 z-50" role="menu"><button type="button" role="menuitemcheckbox" class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center justify-between gap-2"><span>Snap to grid</span> <span> </span></button> <button type="button" role="menuitemcheckbox" class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center justify-between gap-2"><span>Show furniture</span> <span> </span></button> <div class="h-px bg-gray-100 my-1"></div> <button type="button" role="menuitem" class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left">Version history</button> <button type="button" role="menuitem" class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left">Area summary</button> <div class="h-px bg-gray-100 my-1"></div> <button type="button" role="menuitem" class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left">Settings…</button></div>`);
57260
+ var root_9$2 = /* @__PURE__ */ from_html(`<div class="relative shrink-0"><button title="Settings &amp; tools" aria-label="Settings &amp; tools"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg></button> <!></div>`);
57261
+ var root_11$2 = /* @__PURE__ */ from_html(`<button class="p-1.5 text-white/80 hover:text-white hover:bg-white/10 rounded transition-colors" title="Version History" aria-label="Version History"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg></button> <button class="px-2 py-1.5 text-white/80 hover:text-white hover:bg-white/10 rounded transition-colors" title="Area Summary" aria-label="Area Summary"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><path d="M3 9h18"></path><path d="M9 3v18"></path></svg></button> <button class="px-2 py-1.5 text-white/80 hover:text-white hover:bg-white/10 rounded transition-colors" title="Settings" aria-label="Settings"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg></button>`, 1);
57262
+ var root_13$2 = /* @__PURE__ */ from_html(`<button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg> Import JSON</button>`);
57263
+ var root_12$2 = /* @__PURE__ */ from_html(`<div class="absolute right-0 top-full mt-1 bg-white rounded-lg shadow-lg border border-gray-200 py-1 w-48 z-50"><button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"></polyline><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path><rect x="6" y="14" width="12" height="8"></rect></svg> Print Layout</button> <div class="h-px bg-gray-100 my-1"></div> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><path d="M21 15l-5-5L5 21"></path></svg> Export 2D as PNG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"></path><path d="M2 17l10 5 10-5"></path><path d="M2 12l10 5 10-5"></path></svg> Export 3D as PNG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 19V5"></path><path d="M5 12l7-7 7 7"></path></svg> Export as SVG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M8 16h2"></path><path d="M14 16h2"></path></svg> Export as DXF</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M9 16h6"></path></svg> Export as DWG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M16 11v6"></path><path d="M8 11v6"></path><path d="M12 11v6"></path></svg> Export as PDF</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path></svg> Download JSON</button> <div class="h-px bg-gray-100 my-1"></div> <!> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg> New Project</button></div>`);
57264
+ var root_1$10 = /* @__PURE__ */ from_html(`<div class="h-12 bg-gradient-to-r from-slate-800 to-slate-700 flex items-center px-4 gap-3 shrink-0 shadow-sm overflow-hidden"><button type="button"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2.5"></rect><path d="M9 3v18"></path></svg></button> <!> <div class="h-5 w-px bg-white/20"></div> <div class="flex items-center gap-1"><!> <button class="text-white/80 hover:text-white text-xs hover:bg-white/10 px-1.5 py-0.5 rounded transition-colors" title="Add Floor" aria-label="Add Floor">+</button> <span class="text-white/40 text-[10px] ml-1"> </span></div> <div class="flex-1"></div> <button class="p-1.5 text-white/80 hover:text-white hover:bg-white/10 rounded transition-colors" title="Undo (Ctrl+Z)" aria-label="Undo"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg></button> <button class="p-1.5 text-white/80 hover:text-white hover:bg-white/10 rounded transition-colors" title="Redo (Ctrl+Y)" aria-label="Redo"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><path d="M20.49 15a9 9 0 1 1-2.13-9.36L23 10"></path></svg></button> <div class="h-5 w-px bg-white/20"></div> <!> <!> <!> <div class="h-5 w-px bg-white/20"></div> <!> <!> <div class="h-5 w-px bg-white/20 shrink-0"></div> <div class="relative"><button class="px-3 py-1.5 text-sm text-white/90 hover:text-white hover:bg-white/10 rounded transition-colors flex items-center gap-1.5"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg> Export</button> <!></div> <span><!></span> <button class="px-3 py-1.5 text-sm bg-white text-slate-800 font-semibold rounded-lg hover:bg-blue-50 transition-colors shadow-sm">Save</button></div>`);
57265
+ var root_18$3 = /* @__PURE__ */ from_html(`<div class="absolute right-0 top-full mt-1 bg-white rounded-lg shadow-lg border border-gray-200 py-1 w-48 z-50"><button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"></polyline><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path><rect x="6" y="14" width="12" height="8"></rect></svg> Print Layout</button> <div class="h-px bg-gray-100 my-1"></div> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><path d="M21 15l-5-5L5 21"></path></svg> Export 2D as PNG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"></path><path d="M2 17l10 5 10-5"></path><path d="M2 12l10 5 10-5"></path></svg> Export 3D as PNG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 19V5"></path><path d="M5 12l7-7 7 7"></path></svg> Export as SVG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M8 16h2"></path><path d="M14 16h2"></path></svg> Export as DXF</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M9 16h6"></path></svg> Export as DWG</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M16 11v6"></path><path d="M8 11v6"></path><path d="M12 11v6"></path></svg> Export as PDF</button> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path></svg> Download JSON</button> <div class="h-px bg-gray-100 my-1"></div> <button class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 text-left flex items-center gap-2"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg> Import JSON</button></div>`);
57266
+ var root_17$3 = /* @__PURE__ */ from_html(`<div class="h-12 bg-gradient-to-r from-slate-800 to-slate-700 flex items-center justify-between px-4 gap-3 shrink-0 shadow-sm"><span class="font-semibold text-white text-sm px-2 py-0.5 rounded transition-colors"> </span> <div class="relative"><button class="px-3 py-1.5 text-sm text-white/90 hover:text-white hover:bg-white/10 rounded transition-colors flex items-center gap-1.5"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg> Import</button> <!></div></div>`);
57267
+ var root_19$3 = /* @__PURE__ */ from_html(`<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40"><div class="bg-white rounded-xl shadow-2xl w-[420px] max-h-[80vh] overflow-hidden"><div class="flex items-center justify-between px-5 py-3 border-b border-gray-200"><h2 class="text-base font-semibold text-gray-800">📐 Area Summary</h2> <button class="text-gray-400 hover:text-gray-600 text-xl leading-none">&times;</button></div> <div class="overflow-y-auto max-h-[calc(80vh-52px)] p-1"><!></div></div></div>`);
57140
57268
  var root$5 = /* @__PURE__ */ from_html(`<!> <!> <!> <!>`, 1);
57141
57269
  function TopBar($$anchor, $$props) {
57142
57270
  push($$props, true);
@@ -57145,10 +57273,17 @@ function TopBar($$anchor, $$props) {
57145
57273
  const $canvasZoom = () => store_get(canvasZoom, "$canvasZoom", $$stores);
57146
57274
  const $saveState = () => store_get(saveState, "$saveState", $$stores);
57147
57275
  const [$$stores, $$cleanup] = setup_stores();
57148
- let showBuildSidebar = prop($$props, "showBuildSidebar", 15, true);
57276
+ /** Tailwind / common `lg` breakpoint — below this, secondary tools fold into Settings. */
57277
+ const HEADER_AUTO_COMPACT_PX = 1024;
57278
+ let showBuildSidebar = prop($$props, "showBuildSidebar", 15, true), headerToolsMode = prop($$props, "headerToolsMode", 3, "full");
57149
57279
  let settingsOpen = /* @__PURE__ */ state(false);
57150
57280
  let areaOpen = /* @__PURE__ */ state(false);
57151
57281
  let versionHistoryOpen = /* @__PURE__ */ state(false);
57282
+ let toolsMenuOpen = /* @__PURE__ */ state(false);
57283
+ let headerNarrow = /* @__PURE__ */ state(false);
57284
+ let headerEl = /* @__PURE__ */ state(void 0);
57285
+ let toolsMenuRef = /* @__PURE__ */ state(void 0);
57286
+ const useCompactTools = /* @__PURE__ */ user_derived(() => headerToolsMode() === "compact" || headerToolsMode() === "auto" && get(headerNarrow));
57152
57287
  let projectName = /* @__PURE__ */ state("");
57153
57288
  let mode = /* @__PURE__ */ state("2d");
57154
57289
  let floors = /* @__PURE__ */ state(proxy([]));
@@ -57267,15 +57402,27 @@ function TopBar($$anchor, $$props) {
57267
57402
  currentProject.set(createDefaultProject());
57268
57403
  set(exportOpen, false);
57269
57404
  }
57405
+ function toggleSnap() {
57406
+ snapEnabled.update((v) => !v);
57407
+ set(snapOn, !get(snapOn));
57408
+ }
57409
+ function toggleFurniture() {
57410
+ layerVisibility.update((v) => ({
57411
+ ...v,
57412
+ furniture: !v.furniture
57413
+ }));
57414
+ }
57270
57415
  onMount(() => {
57271
57416
  initAutoSave($$props.onDispatch);
57272
57417
  initVersionHistory();
57273
57418
  const interval = setInterval(updateLastSavedText, 15e3);
57274
57419
  function handleClickOutside(e) {
57275
57420
  if (get(exportOpen) && exportRef && !exportRef.contains(e.target)) set(exportOpen, false);
57421
+ if (get(toolsMenuOpen) && get(toolsMenuRef) && !get(toolsMenuRef).contains(e.target)) set(toolsMenuOpen, false);
57276
57422
  }
57277
57423
  function handleKeydown(e) {
57278
57424
  if (get(exportOpen)) set(exportOpen, false);
57425
+ if (e.key === "Escape" && get(toolsMenuOpen)) set(toolsMenuOpen, false);
57279
57426
  if (e.key === "Escape" && get(versionHistoryOpen)) set(versionHistoryOpen, false);
57280
57427
  if (e.key === "Escape" && get(areaOpen)) set(areaOpen, false);
57281
57428
  }
@@ -57287,6 +57434,28 @@ function TopBar($$anchor, $$props) {
57287
57434
  clearInterval(interval);
57288
57435
  };
57289
57436
  });
57437
+ user_effect(() => {
57438
+ const el = get(headerEl);
57439
+ if (!el || headerToolsMode() !== "auto") {
57440
+ if (headerToolsMode() !== "auto") set(headerNarrow, false);
57441
+ return;
57442
+ }
57443
+ if (typeof ResizeObserver === "undefined") {
57444
+ set(headerNarrow, el.clientWidth < HEADER_AUTO_COMPACT_PX);
57445
+ return;
57446
+ }
57447
+ const ro = new ResizeObserver((entries) => {
57448
+ var _entries$0$contentRec, _entries$;
57449
+ const w = (_entries$0$contentRec = (_entries$ = entries[0]) === null || _entries$ === void 0 ? void 0 : _entries$.contentRect.width) !== null && _entries$0$contentRec !== void 0 ? _entries$0$contentRec : 0;
57450
+ set(headerNarrow, w > 0 && w < HEADER_AUTO_COMPACT_PX, true);
57451
+ });
57452
+ ro.observe(el);
57453
+ set(headerNarrow, el.clientWidth < HEADER_AUTO_COMPACT_PX);
57454
+ return () => ro.disconnect();
57455
+ });
57456
+ user_effect(() => {
57457
+ if (!get(useCompactTools) && get(toolsMenuOpen)) set(toolsMenuOpen, false);
57458
+ });
57290
57459
  function onImportJSON() {
57291
57460
  const input = document.createElement("input");
57292
57461
  input.type = "file";
@@ -57326,7 +57495,7 @@ function TopBar($$anchor, $$props) {
57326
57495
  }
57327
57496
  var fragment = root$5();
57328
57497
  var node = first_child(fragment);
57329
- var consequent_7 = ($$anchor) => {
57498
+ var consequent_11 = ($$anchor) => {
57330
57499
  var div = root_1$10();
57331
57500
  var button = child(div);
57332
57501
  var node_1 = sibling(button, 2);
@@ -57372,10 +57541,22 @@ function TopBar($$anchor, $$props) {
57372
57541
  reset(div_1);
57373
57542
  var button_4 = sibling(div_1, 4);
57374
57543
  var button_5 = sibling(button_4, 2);
57375
- var button_6 = sibling(button_5, 4);
57376
- var node_3 = sibling(button_6, 2);
57544
+ var node_3 = sibling(button_5, 4);
57377
57545
  var consequent_1 = ($$anchor) => {
57378
- var div_2 = root_5$5();
57546
+ var button_6 = root_5$5();
57547
+ template_effect(() => {
57548
+ set_class(button_6, 1, `p-1.5 rounded transition-colors ${get(snapOn) ? "text-white bg-white/20" : "text-white/40 hover:text-white/70 hover:bg-white/10"}`);
57549
+ set_attribute(button_6, "title", `Snap to Grid (${get(snapOn) ? "On" : "Off"})`);
57550
+ });
57551
+ delegated("click", button_6, toggleSnap);
57552
+ append($$anchor, button_6);
57553
+ };
57554
+ if_block(node_3, ($$render) => {
57555
+ if (!get(useCompactTools)) $$render(consequent_1);
57556
+ });
57557
+ var node_4 = sibling(node_3, 2);
57558
+ var consequent_2 = ($$anchor) => {
57559
+ var div_2 = root_6$4();
57379
57560
  var button_7 = child(div_2);
57380
57561
  var button_8 = sibling(button_7, 2);
57381
57562
  reset(div_2);
@@ -57387,13 +57568,25 @@ function TopBar($$anchor, $$props) {
57387
57568
  delegated("click", button_8, () => panMode.set(true));
57388
57569
  append($$anchor, div_2);
57389
57570
  };
57390
- if_block(node_3, ($$render) => {
57391
- if (get(mode) === "2d") $$render(consequent_1);
57571
+ if_block(node_4, ($$render) => {
57572
+ if (get(mode) === "2d") $$render(consequent_2);
57392
57573
  });
57393
- var button_9 = sibling(node_3, 2);
57394
- var node_4 = sibling(button_9, 4);
57395
- var consequent_2 = ($$anchor) => {
57396
- var div_3 = root_6$4();
57574
+ var node_5 = sibling(node_4, 2);
57575
+ var consequent_3 = ($$anchor) => {
57576
+ var button_9 = root_7$5();
57577
+ template_effect(() => {
57578
+ set_class(button_9, 1, `p-1.5 rounded transition-colors ${$showFurnitureStore() ? "text-white bg-white/20" : "text-white/40 hover:text-white/70 hover:bg-white/10"}`);
57579
+ set_attribute(button_9, "title", `Toggle Furniture (${$showFurnitureStore() ? "Visible" : "Hidden"})`);
57580
+ });
57581
+ delegated("click", button_9, toggleFurniture);
57582
+ append($$anchor, button_9);
57583
+ };
57584
+ if_block(node_5, ($$render) => {
57585
+ if (!get(useCompactTools)) $$render(consequent_3);
57586
+ });
57587
+ var node_6 = sibling(node_5, 4);
57588
+ var consequent_4 = ($$anchor) => {
57589
+ var div_3 = root_8$2();
57397
57590
  var button_10 = child(div_3);
57398
57591
  var button_11 = sibling(button_10, 2);
57399
57592
  var text_4 = child(button_11);
@@ -57406,77 +57599,146 @@ function TopBar($$anchor, $$props) {
57406
57599
  delegated("click", button_12, () => canvasZoom.update((z) => Math.min(10, z * 1.25)));
57407
57600
  append($$anchor, div_3);
57408
57601
  };
57409
- if_block(node_4, ($$render) => {
57410
- if (get(mode) === "2d") $$render(consequent_2);
57602
+ if_block(node_6, ($$render) => {
57603
+ if (get(mode) === "2d") $$render(consequent_4);
57411
57604
  });
57412
- var button_13 = sibling(node_4, 2);
57413
- var button_14 = sibling(button_13, 2);
57414
- var button_15 = sibling(button_14, 2);
57415
- var div_4 = sibling(button_15, 4);
57416
- var button_16 = child(div_4);
57417
- var node_5 = sibling(button_16, 2);
57418
- var consequent_4 = ($$anchor) => {
57419
- var div_5 = root_7$5();
57420
- var button_17 = child(div_5);
57421
- var button_18 = sibling(button_17, 4);
57422
- var button_19 = sibling(button_18, 2);
57605
+ var node_7 = sibling(node_6, 2);
57606
+ var consequent_6 = ($$anchor) => {
57607
+ var div_4 = root_9$2();
57608
+ var button_13 = child(div_4);
57609
+ var node_8 = sibling(button_13, 2);
57610
+ var consequent_5 = ($$anchor) => {
57611
+ var div_5 = root_10$2();
57612
+ var button_14 = child(div_5);
57613
+ var span_1 = sibling(child(button_14), 2);
57614
+ var text_5 = child(span_1, true);
57615
+ reset(span_1);
57616
+ reset(button_14);
57617
+ var button_15 = sibling(button_14, 2);
57618
+ var span_2 = sibling(child(button_15), 2);
57619
+ var text_6 = child(span_2, true);
57620
+ reset(span_2);
57621
+ reset(button_15);
57622
+ var button_16 = sibling(button_15, 4);
57623
+ var button_17 = sibling(button_16, 2);
57624
+ var button_18 = sibling(button_17, 4);
57625
+ reset(div_5);
57626
+ template_effect(() => {
57627
+ set_attribute(button_14, "aria-checked", get(snapOn));
57628
+ set_class(span_1, 1, `text-xs font-medium ${get(snapOn) ? "text-emerald-600" : "text-gray-400"}`);
57629
+ set_text(text_5, get(snapOn) ? "On" : "Off");
57630
+ set_attribute(button_15, "aria-checked", $showFurnitureStore());
57631
+ set_class(span_2, 1, `text-xs font-medium ${$showFurnitureStore() ? "text-emerald-600" : "text-gray-400"}`);
57632
+ set_text(text_6, $showFurnitureStore() ? "On" : "Off");
57633
+ });
57634
+ delegated("click", button_14, toggleSnap);
57635
+ delegated("click", button_15, toggleFurniture);
57636
+ delegated("click", button_16, () => {
57637
+ set(toolsMenuOpen, false);
57638
+ set(versionHistoryOpen, true);
57639
+ });
57640
+ delegated("click", button_17, () => {
57641
+ set(toolsMenuOpen, false);
57642
+ set(areaOpen, true);
57643
+ });
57644
+ delegated("click", button_18, () => {
57645
+ set(toolsMenuOpen, false);
57646
+ set(settingsOpen, true);
57647
+ });
57648
+ append($$anchor, div_5);
57649
+ };
57650
+ if_block(node_8, ($$render) => {
57651
+ if (get(toolsMenuOpen)) $$render(consequent_5);
57652
+ });
57653
+ reset(div_4);
57654
+ bind_this(div_4, ($$value) => set(toolsMenuRef, $$value), () => get(toolsMenuRef));
57655
+ template_effect(() => {
57656
+ set_class(button_13, 1, `px-2 py-1.5 text-white/80 hover:text-white hover:bg-white/10 rounded transition-colors ${get(toolsMenuOpen) ? "bg-white/15 text-white" : ""}`);
57657
+ set_attribute(button_13, "aria-expanded", get(toolsMenuOpen));
57658
+ });
57659
+ delegated("click", button_13, () => set(toolsMenuOpen, !get(toolsMenuOpen)));
57660
+ append($$anchor, div_4);
57661
+ };
57662
+ var alternate_1 = ($$anchor) => {
57663
+ var fragment_1 = root_11$2();
57664
+ var button_19 = first_child(fragment_1);
57423
57665
  var button_20 = sibling(button_19, 2);
57424
57666
  var button_21 = sibling(button_20, 2);
57425
- var button_22 = sibling(button_21, 2);
57426
- var button_23 = sibling(button_22, 2);
57427
- var button_24 = sibling(button_23, 2);
57428
- var node_6 = sibling(button_24, 4);
57429
- var consequent_3 = ($$anchor) => {
57430
- var button_25 = root_8$2();
57431
- delegated("click", button_25, onImportJSON);
57432
- append($$anchor, button_25);
57667
+ delegated("click", button_19, () => set(versionHistoryOpen, true));
57668
+ delegated("click", button_20, () => set(areaOpen, true));
57669
+ delegated("click", button_21, () => set(settingsOpen, true));
57670
+ append($$anchor, fragment_1);
57671
+ };
57672
+ if_block(node_7, ($$render) => {
57673
+ if (get(useCompactTools)) $$render(consequent_6);
57674
+ else $$render(alternate_1, -1);
57675
+ });
57676
+ var div_6 = sibling(node_7, 4);
57677
+ var button_22 = child(div_6);
57678
+ var node_9 = sibling(button_22, 2);
57679
+ var consequent_8 = ($$anchor) => {
57680
+ var div_7 = root_12$2();
57681
+ var button_23 = child(div_7);
57682
+ var button_24 = sibling(button_23, 4);
57683
+ var button_25 = sibling(button_24, 2);
57684
+ var button_26 = sibling(button_25, 2);
57685
+ var button_27 = sibling(button_26, 2);
57686
+ var button_28 = sibling(button_27, 2);
57687
+ var button_29 = sibling(button_28, 2);
57688
+ var button_30 = sibling(button_29, 2);
57689
+ var node_10 = sibling(button_30, 4);
57690
+ var consequent_7 = ($$anchor) => {
57691
+ var button_31 = root_13$2();
57692
+ delegated("click", button_31, onImportJSON);
57693
+ append($$anchor, button_31);
57433
57694
  };
57434
- if_block(node_6, ($$render) => {
57435
- if (!$$props.viewOnly) $$render(consequent_3);
57695
+ if_block(node_10, ($$render) => {
57696
+ if (!$$props.viewOnly) $$render(consequent_7);
57436
57697
  });
57437
- var button_26 = sibling(node_6, 2);
57438
- reset(div_5);
57439
- delegated("click", button_17, () => {
57698
+ var button_32 = sibling(node_10, 2);
57699
+ reset(div_7);
57700
+ delegated("click", button_23, () => {
57440
57701
  set(exportOpen, false);
57441
57702
  window.dispatchEvent(new KeyboardEvent("keydown", {
57442
57703
  key: "p",
57443
57704
  ctrlKey: true
57444
57705
  }));
57445
57706
  });
57446
- delegated("click", button_18, onExport2DPNG);
57447
- delegated("click", button_19, onExport3DPNG);
57448
- delegated("click", button_20, onExportSVG);
57449
- delegated("click", button_21, onExportDXF);
57450
- delegated("click", button_22, onExportDWG);
57451
- delegated("click", button_23, onExportPDF);
57452
- delegated("click", button_24, onExportJSON);
57453
- delegated("click", button_26, newProject);
57454
- append($$anchor, div_5);
57707
+ delegated("click", button_24, onExport2DPNG);
57708
+ delegated("click", button_25, onExport3DPNG);
57709
+ delegated("click", button_26, onExportSVG);
57710
+ delegated("click", button_27, onExportDXF);
57711
+ delegated("click", button_28, onExportDWG);
57712
+ delegated("click", button_29, onExportPDF);
57713
+ delegated("click", button_30, onExportJSON);
57714
+ delegated("click", button_32, newProject);
57715
+ append($$anchor, div_7);
57455
57716
  };
57456
- if_block(node_5, ($$render) => {
57457
- if (get(exportOpen)) $$render(consequent_4);
57717
+ if_block(node_9, ($$render) => {
57718
+ if (get(exportOpen)) $$render(consequent_8);
57458
57719
  });
57459
- reset(div_4);
57460
- bind_this(div_4, ($$value) => exportRef = $$value, () => exportRef);
57461
- var span_1 = sibling(div_4, 2);
57462
- var node_7 = child(span_1);
57463
- var consequent_5 = ($$anchor) => {
57720
+ reset(div_6);
57721
+ bind_this(div_6, ($$value) => exportRef = $$value, () => exportRef);
57722
+ var span_3 = sibling(div_6, 2);
57723
+ var node_11 = child(span_3);
57724
+ var consequent_9 = ($$anchor) => {
57464
57725
  append($$anchor, text$1("Saving…"));
57465
57726
  };
57466
- var consequent_6 = ($$anchor) => {
57727
+ var consequent_10 = ($$anchor) => {
57467
57728
  append($$anchor, text$1("Saved ✓"));
57468
57729
  };
57469
- var alternate_1 = ($$anchor) => {
57730
+ var alternate_2 = ($$anchor) => {
57470
57731
  append($$anchor, text$1("Unsaved •"));
57471
57732
  };
57472
- if_block(node_7, ($$render) => {
57473
- if ($saveState() === "saving") $$render(consequent_5);
57474
- else if ($saveState() === "saved") $$render(consequent_6, 1);
57475
- else $$render(alternate_1, -1);
57733
+ if_block(node_11, ($$render) => {
57734
+ if ($saveState() === "saving") $$render(consequent_9);
57735
+ else if ($saveState() === "saved") $$render(consequent_10, 1);
57736
+ else $$render(alternate_2, -1);
57476
57737
  });
57477
- reset(span_1);
57478
- var button_27 = sibling(span_1, 2);
57738
+ reset(span_3);
57739
+ var button_33 = sibling(span_3, 2);
57479
57740
  reset(div);
57741
+ bind_this(div, ($$value) => set(headerEl, $$value), () => get(headerEl));
57480
57742
  template_effect(() => {
57481
57743
  var _$$get$length;
57482
57744
  set_class(button, 1, `shrink-0 w-8 h-8 rounded-md flex items-center justify-center text-white/80 hover:text-white hover:bg-white/10 transition-colors ${showBuildSidebar() ? "bg-white/15 text-white" : ""}`);
@@ -57484,12 +57746,8 @@ function TopBar($$anchor, $$props) {
57484
57746
  set_attribute(button, "aria-label", showBuildSidebar() ? "Hide sidebar" : "Show sidebar");
57485
57747
  set_attribute(button, "aria-pressed", showBuildSidebar());
57486
57748
  set_text(text_3, `${(_$$get$length = get(floors).length) !== null && _$$get$length !== void 0 ? _$$get$length : ""}F`);
57487
- set_class(button_6, 1, `p-1.5 rounded transition-colors ${get(snapOn) ? "text-white bg-white/20" : "text-white/40 hover:text-white/70 hover:bg-white/10"}`);
57488
- set_attribute(button_6, "title", `Snap to Grid (${get(snapOn) ? "On" : "Off"})`);
57489
- set_class(button_9, 1, `p-1.5 rounded transition-colors ${$showFurnitureStore() ? "text-white bg-white/20" : "text-white/40 hover:text-white/70 hover:bg-white/10"}`);
57490
- set_attribute(button_9, "title", `Toggle Furniture (${$showFurnitureStore() ? "Visible" : "Hidden"})`);
57491
- set_class(span_1, 1, `text-[11px] font-medium transition-all duration-300 ${$saveState() === "saved" ? "text-emerald-400" : $saveState() === "saving" ? "text-amber-300 animate-pulse" : "text-white/50"}`);
57492
- set_attribute(span_1, "title", get(lastSavedText) || "Not saved yet");
57749
+ set_class(span_3, 1, `text-[11px] font-medium transition-all duration-300 ${$saveState() === "saved" ? "text-emerald-400" : $saveState() === "saving" ? "text-amber-300 animate-pulse" : "text-white/50"}`);
57750
+ set_attribute(span_3, "title", get(lastSavedText) || "Not saved yet");
57493
57751
  });
57494
57752
  delegated("click", button, () => showBuildSidebar(!showBuildSidebar()));
57495
57753
  delegated("click", button_3, onAddFloor);
@@ -57499,80 +57757,69 @@ function TopBar($$anchor, $$props) {
57499
57757
  delegated("click", button_5, function(...$$args) {
57500
57758
  redo === null || redo === void 0 || redo.apply(this, $$args);
57501
57759
  });
57502
- delegated("click", button_6, () => {
57503
- snapEnabled.update((v) => !v);
57504
- set(snapOn, !get(snapOn));
57505
- });
57506
- delegated("click", button_9, () => layerVisibility.update((v) => ({
57507
- ...v,
57508
- furniture: !v.furniture
57509
- })));
57510
- delegated("click", button_13, () => set(versionHistoryOpen, true));
57511
- delegated("click", button_14, () => set(areaOpen, true));
57512
- delegated("click", button_15, () => set(settingsOpen, true));
57513
- delegated("click", button_16, () => {
57760
+ delegated("click", button_22, () => {
57514
57761
  set(exportOpen, !get(exportOpen));
57515
57762
  if (get(exportOpen)) triggerTip("first-export", 300, 60);
57516
57763
  });
57517
- delegated("click", button_27, save);
57764
+ delegated("click", button_33, save);
57518
57765
  append($$anchor, div);
57519
57766
  };
57520
- var alternate_2 = ($$anchor) => {
57521
- var div_6 = root_12$2();
57522
- var span_2 = child(div_6);
57523
- var text_8 = child(span_2, true);
57524
- reset(span_2);
57525
- var div_7 = sibling(span_2, 2);
57526
- var button_28 = child(div_7);
57527
- var node_8 = sibling(button_28, 2);
57528
- var consequent_8 = ($$anchor) => {
57529
- var div_8 = root_13$2();
57530
- var button_29 = child(div_8);
57531
- var button_30 = sibling(button_29, 4);
57532
- var button_31 = sibling(button_30, 2);
57533
- var button_32 = sibling(button_31, 2);
57534
- var button_33 = sibling(button_32, 2);
57535
- var button_34 = sibling(button_33, 2);
57536
- var button_35 = sibling(button_34, 2);
57537
- var button_36 = sibling(button_35, 2);
57538
- var button_37 = sibling(button_36, 4);
57539
- reset(div_8);
57540
- delegated("click", button_29, () => {
57767
+ var alternate_3 = ($$anchor) => {
57768
+ var div_8 = root_17$3();
57769
+ var span_4 = child(div_8);
57770
+ var text_10 = child(span_4, true);
57771
+ reset(span_4);
57772
+ var div_9 = sibling(span_4, 2);
57773
+ var button_34 = child(div_9);
57774
+ var node_12 = sibling(button_34, 2);
57775
+ var consequent_12 = ($$anchor) => {
57776
+ var div_10 = root_18$3();
57777
+ var button_35 = child(div_10);
57778
+ var button_36 = sibling(button_35, 4);
57779
+ var button_37 = sibling(button_36, 2);
57780
+ var button_38 = sibling(button_37, 2);
57781
+ var button_39 = sibling(button_38, 2);
57782
+ var button_40 = sibling(button_39, 2);
57783
+ var button_41 = sibling(button_40, 2);
57784
+ var button_42 = sibling(button_41, 2);
57785
+ var button_43 = sibling(button_42, 4);
57786
+ reset(div_10);
57787
+ delegated("click", button_35, () => {
57541
57788
  set(exportOpen, false);
57542
57789
  window.dispatchEvent(new KeyboardEvent("keydown", {
57543
57790
  key: "p",
57544
57791
  ctrlKey: true
57545
57792
  }));
57546
57793
  });
57547
- delegated("click", button_30, onExport2DPNG);
57548
- delegated("click", button_31, onExport3DPNG);
57549
- delegated("click", button_32, onExportSVG);
57550
- delegated("click", button_33, onExportDXF);
57551
- delegated("click", button_34, onExportDWG);
57552
- delegated("click", button_35, onExportPDF);
57553
- delegated("click", button_36, onExportJSON);
57554
- delegated("click", button_37, onImportJSON);
57555
- append($$anchor, div_8);
57794
+ delegated("click", button_36, onExport2DPNG);
57795
+ delegated("click", button_37, onExport3DPNG);
57796
+ delegated("click", button_38, onExportSVG);
57797
+ delegated("click", button_39, onExportDXF);
57798
+ delegated("click", button_40, onExportDWG);
57799
+ delegated("click", button_41, onExportPDF);
57800
+ delegated("click", button_42, onExportJSON);
57801
+ delegated("click", button_43, onImportJSON);
57802
+ append($$anchor, div_10);
57556
57803
  };
57557
- if_block(node_8, ($$render) => {
57558
- if (get(exportOpen)) $$render(consequent_8);
57804
+ if_block(node_12, ($$render) => {
57805
+ if (get(exportOpen)) $$render(consequent_12);
57559
57806
  });
57560
- reset(div_7);
57561
- bind_this(div_7, ($$value) => exportRef = $$value, () => exportRef);
57562
- reset(div_6);
57563
- template_effect(() => set_text(text_8, get(projectName)));
57564
- delegated("click", button_28, () => {
57807
+ reset(div_9);
57808
+ bind_this(div_9, ($$value) => exportRef = $$value, () => exportRef);
57809
+ reset(div_8);
57810
+ template_effect(() => set_text(text_10, get(projectName)));
57811
+ delegated("click", button_34, () => {
57565
57812
  set(exportOpen, !get(exportOpen));
57566
57813
  if (get(exportOpen)) triggerTip("first-export", 300, 60);
57567
57814
  });
57568
- append($$anchor, div_6);
57815
+ append($$anchor, div_8);
57569
57816
  };
57570
57817
  if_block(node, ($$render) => {
57571
- if (!$$props.viewOnly) $$render(consequent_7);
57572
- else $$render(alternate_2, -1);
57818
+ if (!$$props.viewOnly) $$render(consequent_11);
57819
+ else $$render(alternate_3, -1);
57573
57820
  });
57574
- var node_9 = sibling(node, 2);
57575
- SettingsDialog(node_9, {
57821
+ var node_13 = sibling(node, 2);
57822
+ SettingsDialog(node_13, {
57576
57823
  get open() {
57577
57824
  return get(settingsOpen);
57578
57825
  },
@@ -57580,8 +57827,8 @@ function TopBar($$anchor, $$props) {
57580
57827
  set(settingsOpen, $$value, true);
57581
57828
  }
57582
57829
  });
57583
- var node_10 = sibling(node_9, 2);
57584
- VersionHistoryPanel(node_10, {
57830
+ var node_14 = sibling(node_13, 2);
57831
+ VersionHistoryPanel(node_14, {
57585
57832
  get open() {
57586
57833
  return get(versionHistoryOpen);
57587
57834
  },
@@ -57589,28 +57836,28 @@ function TopBar($$anchor, $$props) {
57589
57836
  set(versionHistoryOpen, $$value, true);
57590
57837
  }
57591
57838
  });
57592
- var node_11 = sibling(node_10, 2);
57593
- var consequent_9 = ($$anchor) => {
57594
- var div_9 = root_14$2();
57595
- var div_10 = child(div_9);
57596
- var div_11 = child(div_10);
57597
- var button_38 = sibling(child(div_11), 2);
57598
- reset(div_11);
57599
- var div_12 = sibling(div_11, 2);
57600
- AreaSummaryPanel(child(div_12), {});
57839
+ var node_15 = sibling(node_14, 2);
57840
+ var consequent_13 = ($$anchor) => {
57841
+ var div_11 = root_19$3();
57842
+ var div_12 = child(div_11);
57843
+ var div_13 = child(div_12);
57844
+ var button_44 = sibling(child(div_13), 2);
57845
+ reset(div_13);
57846
+ var div_14 = sibling(div_13, 2);
57847
+ AreaSummaryPanel(child(div_14), {});
57848
+ reset(div_14);
57601
57849
  reset(div_12);
57602
- reset(div_10);
57603
- reset(div_9);
57604
- delegated("click", div_9, () => set(areaOpen, false));
57605
- delegated("keydown", div_9, (e) => {
57850
+ reset(div_11);
57851
+ delegated("click", div_11, () => set(areaOpen, false));
57852
+ delegated("keydown", div_11, (e) => {
57606
57853
  if (e.key === "Escape") set(areaOpen, false);
57607
57854
  });
57608
- delegated("click", div_10, (e) => e.stopPropagation());
57609
- delegated("click", button_38, () => set(areaOpen, false));
57610
- append($$anchor, div_9);
57855
+ delegated("click", div_12, (e) => e.stopPropagation());
57856
+ delegated("click", button_44, () => set(areaOpen, false));
57857
+ append($$anchor, div_11);
57611
57858
  };
57612
- if_block(node_11, ($$render) => {
57613
- if (get(areaOpen)) $$render(consequent_9);
57859
+ if_block(node_15, ($$render) => {
57860
+ if (get(areaOpen)) $$render(consequent_13);
57614
57861
  });
57615
57862
  append($$anchor, fragment);
57616
57863
  pop();
@@ -64603,14 +64850,39 @@ function getMatrixKey(matrix) {
64603
64850
  const step = Math.max(1, Math.floor(flat.length / 20));
64604
64851
  return flat.filter((_, i) => i % step === 0).join(",");
64605
64852
  }
64606
- function drawFloorHeatmap(cs, floor, heatmapMatrix, detectedRooms) {
64853
+ function paintHeatmapBlob(blobCtx, px, py, radius, centerColor, midColor, edgeColor, fadeColor) {
64854
+ const gradient = blobCtx.createRadialGradient(px, py, 0, px, py, radius);
64855
+ gradient.addColorStop(0, centerColor);
64856
+ gradient.addColorStop(.2, midColor);
64857
+ gradient.addColorStop(.5, edgeColor);
64858
+ gradient.addColorStop(1, fadeColor);
64859
+ blobCtx.fillStyle = gradient;
64860
+ blobCtx.beginPath();
64861
+ blobCtx.arc(px, py, radius, 0, Math.PI * 2);
64862
+ blobCtx.fill();
64863
+ }
64864
+ function drawFloorHeatmap(cs, floor, heatmapMatrix, detectedRooms, colorScale) {
64607
64865
  var _heatmapMatrix$;
64608
64866
  const { ctx } = cs;
64609
64867
  const rows = heatmapMatrix.length;
64610
64868
  const cols = ((_heatmapMatrix$ = heatmapMatrix[0]) === null || _heatmapMatrix$ === void 0 ? void 0 : _heatmapMatrix$.length) || 0;
64611
64869
  if (rows === 0 || cols === 0) return;
64870
+ const useLevels = isLevelsColorScale(colorScale);
64612
64871
  const maxValue = heatmapMatrix.reduce((max, row) => row.reduce((m, v) => v > m ? v : m, max), 0);
64613
- if (maxValue <= 0) return;
64872
+ if (!useLevels && maxValue <= 0) return;
64873
+ if (useLevels) {
64874
+ var _colorScale$hideBelow;
64875
+ const hide = (_colorScale$hideBelow = colorScale.hideBelow) !== null && _colorScale$hideBelow !== void 0 ? _colorScale$hideBelow : 0;
64876
+ let anyVisible = false;
64877
+ outer: for (let y = 0; y < rows; y++) for (let x = 0; x < cols; x++) {
64878
+ const v = heatmapMatrix[y][x];
64879
+ if (v > hide && resolveHeatmapLevelColor(v, colorScale)) {
64880
+ anyVisible = true;
64881
+ break outer;
64882
+ }
64883
+ }
64884
+ if (!anyVisible) return;
64885
+ }
64614
64886
  const bounds = getFloorBounds(floor);
64615
64887
  if (!bounds) return;
64616
64888
  const { minX, minY, maxX, maxY } = bounds;
@@ -64618,8 +64890,9 @@ function drawFloorHeatmap(cs, floor, heatmapMatrix, detectedRooms) {
64618
64890
  const boxH = bounds.height;
64619
64891
  if (boxW <= 0 || boxH <= 0) return;
64620
64892
  const matrixKey = getMatrixKey(heatmapMatrix);
64893
+ const scaleKey = getHeatmapColorScaleKey(colorScale);
64621
64894
  const floorId = floor.id;
64622
- const needsRedraw = !heatmapCache || heatmapCache.matrixKey !== matrixKey || heatmapCache.floorId !== floorId;
64895
+ const needsRedraw = !heatmapCache || heatmapCache.matrixKey !== matrixKey || heatmapCache.scaleKey !== scaleKey || heatmapCache.floorId !== floorId;
64623
64896
  const OFFSCREEN_W = 512;
64624
64897
  const OFFSCREEN_H = 512;
64625
64898
  if (needsRedraw) {
@@ -64634,19 +64907,18 @@ function drawFloorHeatmap(cs, floor, heatmapMatrix, detectedRooms) {
64634
64907
  for (let y = 0; y < rows; y++) for (let x = 0; x < cols; x++) {
64635
64908
  const value = heatmapMatrix[y][x];
64636
64909
  if (!value || value <= 0) continue;
64637
- const intensity = Math.min(value / maxValue, 1);
64638
64910
  const px = (x + .5) * offCellW;
64639
64911
  const py = (y + .5) * offCellH;
64640
- const alpha = .5 + intensity * .45;
64641
- const gradient = blobCtx.createRadialGradient(px, py, 0, px, py, radius);
64642
- gradient.addColorStop(0, getHeatmapColor(intensity, alpha));
64643
- gradient.addColorStop(.2, getHeatmapColor(intensity, alpha * .75));
64644
- gradient.addColorStop(.5, getHeatmapColor(intensity, alpha * .25));
64645
- gradient.addColorStop(1, getHeatmapColor(intensity, 0));
64646
- blobCtx.fillStyle = gradient;
64647
- blobCtx.beginPath();
64648
- blobCtx.arc(px, py, radius, 0, Math.PI * 2);
64649
- blobCtx.fill();
64912
+ if (useLevels) {
64913
+ const hit = resolveHeatmapLevelColor(value, colorScale);
64914
+ if (!hit) continue;
64915
+ const alpha = .55 + (hit.levelCount <= 1 ? 1 : hit.levelIndex / (hit.levelCount - 1)) * .4;
64916
+ paintHeatmapBlob(blobCtx, px, py, radius, colorWithAlpha(hit.color, alpha), colorWithAlpha(hit.color, alpha * .75), colorWithAlpha(hit.color, alpha * .25), colorWithAlpha(hit.color, 0));
64917
+ } else {
64918
+ const intensity = Math.min(value / maxValue, 1);
64919
+ const alpha = .5 + intensity * .45;
64920
+ paintHeatmapBlob(blobCtx, px, py, radius, getHeatmapColor(intensity, alpha), getHeatmapColor(intensity, alpha * .75), getHeatmapColor(intensity, alpha * .25), getHeatmapColor(intensity, 0));
64921
+ }
64650
64922
  }
64651
64923
  const finalCanvas = document.createElement("canvas");
64652
64924
  finalCanvas.width = OFFSCREEN_W;
@@ -64671,6 +64943,7 @@ function drawFloorHeatmap(cs, floor, heatmapMatrix, detectedRooms) {
64671
64943
  heatmapCache = {
64672
64944
  canvas: finalCanvas,
64673
64945
  matrixKey,
64946
+ scaleKey,
64674
64947
  floorId
64675
64948
  };
64676
64949
  }
@@ -64976,7 +65249,7 @@ function FloorPlanCanvas($$anchor, $$props) {
64976
65249
  const $panMode = () => store_get(panMode, "$panMode", $$stores);
64977
65250
  const $projectSettings = () => store_get(projectSettings, "$projectSettings", $$stores);
64978
65251
  const [$$stores, $$cleanup] = setup_stores();
64979
- let selectedCameraSerial = prop($$props, "selectedCameraSerial", 3, null), showCameraCones = prop($$props, "showCameraCones", 3, false), showCameraLabels = prop($$props, "showCameraLabels", 3, true), cameraHeatmapMatrix = prop($$props, "cameraHeatmapMatrix", 19, () => []), cameraThumbnail = prop($$props, "cameraThumbnail", 3, ""), canvasBackground = prop($$props, "canvasBackground", 3, "#2E353F"), floorPlateBackground = prop($$props, "floorPlateBackground", 3, "#ffffff");
65252
+ let heatmapColorScale = prop($$props, "heatmapColorScale", 3, void 0), selectedCameraSerial = prop($$props, "selectedCameraSerial", 3, null), showCameraCones = prop($$props, "showCameraCones", 3, false), showCameraLabels = prop($$props, "showCameraLabels", 3, true), cameraHeatmapMatrix = prop($$props, "cameraHeatmapMatrix", 19, () => []), cameraThumbnail = prop($$props, "cameraThumbnail", 3, ""), canvasBackground = prop($$props, "canvasBackground", 3, "#2E353F"), floorPlateBackground = prop($$props, "floorPlateBackground", 3, "#ffffff");
64980
65253
  let canvas;
64981
65254
  let ctx;
64982
65255
  let width = /* @__PURE__ */ state(800);
@@ -65081,6 +65354,7 @@ function FloorPlanCanvas($$anchor, $$props) {
65081
65354
  $$props.projectName;
65082
65355
  $$props.heatmapEnabled;
65083
65356
  $$props.heatmapMatrix;
65357
+ heatmapColorScale();
65084
65358
  markDirty();
65085
65359
  });
65086
65360
  let layerVis = /* @__PURE__ */ state(proxy({
@@ -65967,7 +66241,7 @@ function FloorPlanCanvas($$anchor, $$props) {
65967
66241
  var _$$props$heatmapMatri, _$$props$heatmapMatri2;
65968
66242
  const floor = get(currentFloor);
65969
66243
  if (!floor || !$$props.heatmapEnabled || !((_$$props$heatmapMatri = $$props.heatmapMatrix) === null || _$$props$heatmapMatri === void 0 ? void 0 : _$$props$heatmapMatri.length)) return;
65970
- drawFloorHeatmap(getCS(), floor, (_$$props$heatmapMatri2 = $$props.heatmapMatrix) !== null && _$$props$heatmapMatri2 !== void 0 ? _$$props$heatmapMatri2 : [], get(detectedRooms));
66244
+ drawFloorHeatmap(getCS(), floor, (_$$props$heatmapMatri2 = $$props.heatmapMatrix) !== null && _$$props$heatmapMatri2 !== void 0 ? _$$props$heatmapMatri2 : [], get(detectedRooms), heatmapColorScale());
65971
66245
  }
65972
66246
  function getFloorWallBounds(floor = get(currentFloor)) {
65973
66247
  var _floor$walls;
@@ -66756,13 +67030,19 @@ function FloorPlanCanvas($$anchor, $$props) {
66756
67030
  markDirty();
66757
67031
  });
66758
67032
  const unsubZoom = canvasZoom.subscribe((v) => {
67033
+ if (get(zoom) === v) return;
66759
67034
  set(zoom, v, true);
67035
+ markDirty();
66760
67036
  });
66761
67037
  const unsubCamX = canvasCamX.subscribe((v) => {
67038
+ if (get(camX) === v) return;
66762
67039
  set(camX, v, true);
67040
+ markDirty();
66763
67041
  });
66764
67042
  const unsubCamY = canvasCamY.subscribe((v) => {
67043
+ if (get(camY) === v) return;
66765
67044
  set(camY, v, true);
67045
+ markDirty();
66766
67046
  });
66767
67047
  function handlePaste(e) {
66768
67048
  if (!e.clipboardData) return;
@@ -68961,12 +69241,15 @@ function FloorPlanCanvas($$anchor, $$props) {
68961
69241
  set(camX, worldCX - (get(width) / 2 - get(width) / 2) / newZoom);
68962
69242
  set(camY, worldCY - (get(height) / 2 - get(height) / 2) / newZoom);
68963
69243
  set(zoom, newZoom, true);
69244
+ markDirty();
68964
69245
  });
68965
69246
  delegated("click", button_12, () => {
68966
69247
  set(zoom, 1);
69248
+ markDirty();
68967
69249
  });
68968
69250
  delegated("click", button_13, () => {
68969
69251
  set(zoom, Math.min(10, get(zoom) * 1.25), true);
69252
+ markDirty();
68970
69253
  });
68971
69254
  delegated("click", button_14, () => zoomToFit());
68972
69255
  append($$anchor, fragment);
@@ -69968,6 +70251,17 @@ var CAMERA_SPACING = 40;
69968
70251
  var CAMERA_HIT = 28;
69969
70252
  /** Avoid spamming host with the same rejection. */
69970
70253
  var lastRejectedKey = "";
70254
+ /**
70255
+ * Serials last applied from host `assignedCameras`.
70256
+ * Empty + never synced ⇒ do not strip cameras that came from floorData
70257
+ * (host may load a saved floor without passing assignedCameras).
70258
+ */
70259
+ var lastSyncedSerials = /* @__PURE__ */ new Set();
70260
+ /** Call when host replaces floorData so load doesn't fight a stale sync set. */
70261
+ function resetAssignedCamerasSyncState() {
70262
+ lastSyncedSerials = /* @__PURE__ */ new Set();
70263
+ lastRejectedKey = "";
70264
+ }
69971
70265
  function getCameraFurniture(floor) {
69972
70266
  return floor.furniture.filter((f) => f.catalogId === "camera");
69973
70267
  }
@@ -70053,9 +70347,11 @@ function syncAssignedCameras(assigned) {
70053
70347
  });
70054
70348
  }
70055
70349
  if (toAdd.length > 0 && !hasFloorLayout) rejectNoFloor(toAdd);
70350
+ const newSerials = new Set(normalized.map((c) => String(c.serial).trim()));
70351
+ const hostDrivesAssignment = newSerials.size > 0 || lastSyncedSerials.size > 0;
70056
70352
  setAssignedCamerasSyncing(true);
70057
70353
  try {
70058
- for (const item of existing) {
70354
+ if (hostDrivesAssignment) for (const item of existing) {
70059
70355
  if (!item.serial) continue;
70060
70356
  if (!assignedBySerial.has(item.serial)) removeFurniture(item.id);
70061
70357
  }
@@ -70069,10 +70365,14 @@ function syncAssignedCameras(assigned) {
70069
70365
  nextProps.serial = item.serial;
70070
70366
  if (!propsEqual(item.props, nextProps) || item.name !== nextProps.name) applyCameraProps(item.id, assignedCam);
70071
70367
  }
70072
- if (!hasFloorLayout) return;
70368
+ if (!hasFloorLayout) {
70369
+ if (hostDrivesAssignment || newSerials.size > 0) lastSyncedSerials = newSerials;
70370
+ return;
70371
+ }
70073
70372
  const center = getFloorCenter(floorAfterRemove);
70074
70373
  if (!center) {
70075
70374
  rejectNoFloor(toAdd);
70375
+ if (hostDrivesAssignment || newSerials.size > 0) lastSyncedSerials = newSerials;
70076
70376
  return;
70077
70377
  }
70078
70378
  lastRejectedKey = "";
@@ -70083,6 +70383,7 @@ function syncAssignedCameras(assigned) {
70083
70383
  applyCameraProps(addFurniture("camera", findOpenCameraPosition(workingFloor, center)), cam);
70084
70384
  workingFloor = get$1(activeFloor);
70085
70385
  }
70386
+ lastSyncedSerials = newSerials;
70086
70387
  } finally {
70087
70388
  setAssignedCamerasSyncing(false);
70088
70389
  }
@@ -70134,6 +70435,7 @@ function App($$anchor, $$props) {
70134
70435
  set(mode, m, true);
70135
70436
  });
70136
70437
  user_effect(() => {
70438
+ resetAssignedCamerasSyncState();
70137
70439
  currentProject.set(normalizeFloorData(get(floorData)));
70138
70440
  });
70139
70441
  user_effect(() => {
@@ -70176,20 +70478,29 @@ function App($$anchor, $$props) {
70176
70478
  var div = first_child(fragment);
70177
70479
  var node = child(div);
70178
70480
  var consequent = ($$anchor) => {
70179
- TopBar($$anchor, {
70180
- get viewOnly() {
70181
- return get(config).viewOnly;
70182
- },
70183
- get onDispatch() {
70184
- return $$props.onDispatch;
70185
- },
70186
- get showBuildSidebar() {
70187
- return get(showBuildSidebar);
70188
- },
70189
- set showBuildSidebar($$value) {
70190
- set(showBuildSidebar, $$value, true);
70191
- }
70192
- });
70481
+ {
70482
+ let $0 = /* @__PURE__ */ user_derived(() => {
70483
+ var _$$get$headerToolsMod;
70484
+ return (_$$get$headerToolsMod = get(config).headerToolsMode) !== null && _$$get$headerToolsMod !== void 0 ? _$$get$headerToolsMod : "full";
70485
+ });
70486
+ TopBar($$anchor, {
70487
+ get viewOnly() {
70488
+ return get(config).viewOnly;
70489
+ },
70490
+ get headerToolsMode() {
70491
+ return get($0);
70492
+ },
70493
+ get onDispatch() {
70494
+ return $$props.onDispatch;
70495
+ },
70496
+ get showBuildSidebar() {
70497
+ return get(showBuildSidebar);
70498
+ },
70499
+ set showBuildSidebar($$value) {
70500
+ set(showBuildSidebar, $$value, true);
70501
+ }
70502
+ });
70503
+ }
70193
70504
  };
70194
70505
  if_block(node, ($$render) => {
70195
70506
  if (!get(config).viewOnly) $$render(consequent);
@@ -70260,35 +70571,39 @@ function App($$anchor, $$props) {
70260
70571
  });
70261
70572
  let $5 = /* @__PURE__ */ user_derived(() => {
70262
70573
  var _$$get7;
70263
- return (_$$get7 = get(config)) === null || _$$get7 === void 0 ? void 0 : _$$get7.projectName;
70574
+ return (_$$get7 = get(config)) === null || _$$get7 === void 0 ? void 0 : _$$get7.heatmapColorScale;
70264
70575
  });
70265
70576
  let $6 = /* @__PURE__ */ user_derived(() => {
70266
70577
  var _$$get8;
70267
- return (_$$get8 = get(config)) === null || _$$get8 === void 0 ? void 0 : _$$get8.selectedCameraSerial;
70578
+ return (_$$get8 = get(config)) === null || _$$get8 === void 0 ? void 0 : _$$get8.projectName;
70268
70579
  });
70269
70580
  let $7 = /* @__PURE__ */ user_derived(() => {
70270
70581
  var _$$get9;
70271
- return (_$$get9 = get(config)) === null || _$$get9 === void 0 ? void 0 : _$$get9.showCameraCones;
70582
+ return (_$$get9 = get(config)) === null || _$$get9 === void 0 ? void 0 : _$$get9.selectedCameraSerial;
70272
70583
  });
70273
70584
  let $8 = /* @__PURE__ */ user_derived(() => {
70274
70585
  var _$$get10;
70275
- return (_$$get10 = get(config)) === null || _$$get10 === void 0 ? void 0 : _$$get10.showCameraLabels;
70586
+ return (_$$get10 = get(config)) === null || _$$get10 === void 0 ? void 0 : _$$get10.showCameraCones;
70276
70587
  });
70277
70588
  let $9 = /* @__PURE__ */ user_derived(() => {
70278
70589
  var _$$get11;
70279
- return (_$$get11 = get(config)) === null || _$$get11 === void 0 ? void 0 : _$$get11.cameraHeatmapMatrix;
70590
+ return (_$$get11 = get(config)) === null || _$$get11 === void 0 ? void 0 : _$$get11.showCameraLabels;
70280
70591
  });
70281
70592
  let $10 = /* @__PURE__ */ user_derived(() => {
70282
70593
  var _$$get12;
70283
- return ((_$$get12 = get(config)) === null || _$$get12 === void 0 ? void 0 : _$$get12.cameraThumbnail) || "";
70594
+ return (_$$get12 = get(config)) === null || _$$get12 === void 0 ? void 0 : _$$get12.cameraHeatmapMatrix;
70284
70595
  });
70285
70596
  let $11 = /* @__PURE__ */ user_derived(() => {
70286
70597
  var _$$get13;
70287
- return (_$$get13 = get(config)) === null || _$$get13 === void 0 ? void 0 : _$$get13.canvasBackground;
70598
+ return ((_$$get13 = get(config)) === null || _$$get13 === void 0 ? void 0 : _$$get13.cameraThumbnail) || "";
70288
70599
  });
70289
70600
  let $12 = /* @__PURE__ */ user_derived(() => {
70290
70601
  var _$$get14;
70291
- return (_$$get14 = get(config)) === null || _$$get14 === void 0 ? void 0 : _$$get14.floorPlateBackground;
70602
+ return (_$$get14 = get(config)) === null || _$$get14 === void 0 ? void 0 : _$$get14.canvasBackground;
70603
+ });
70604
+ let $13 = /* @__PURE__ */ user_derived(() => {
70605
+ var _$$get15;
70606
+ return (_$$get15 = get(config)) === null || _$$get15 === void 0 ? void 0 : _$$get15.floorPlateBackground;
70292
70607
  });
70293
70608
  FloorPlanCanvas(node_4, {
70294
70609
  get viewOnly() {
@@ -70306,29 +70621,32 @@ function App($$anchor, $$props) {
70306
70621
  get heatmapEnabled() {
70307
70622
  return get($4);
70308
70623
  },
70309
- get projectName() {
70624
+ get heatmapColorScale() {
70310
70625
  return get($5);
70311
70626
  },
70312
- get selectedCameraSerial() {
70627
+ get projectName() {
70313
70628
  return get($6);
70314
70629
  },
70315
- get showCameraCones() {
70630
+ get selectedCameraSerial() {
70316
70631
  return get($7);
70317
70632
  },
70318
- get showCameraLabels() {
70633
+ get showCameraCones() {
70319
70634
  return get($8);
70320
70635
  },
70321
- get cameraHeatmapMatrix() {
70636
+ get showCameraLabels() {
70322
70637
  return get($9);
70323
70638
  },
70324
- get cameraThumbnail() {
70639
+ get cameraHeatmapMatrix() {
70325
70640
  return get($10);
70326
70641
  },
70327
- get canvasBackground() {
70642
+ get cameraThumbnail() {
70328
70643
  return get($11);
70329
70644
  },
70330
- get floorPlateBackground() {
70645
+ get canvasBackground() {
70331
70646
  return get($12);
70647
+ },
70648
+ get floorPlateBackground() {
70649
+ return get($13);
70332
70650
  }
70333
70651
  });
70334
70652
  }
@@ -70357,12 +70675,12 @@ function App($$anchor, $$props) {
70357
70675
  {
70358
70676
  let $0 = /* @__PURE__ */ user_derived(() => get(mode) === "3d");
70359
70677
  let $1 = /* @__PURE__ */ user_derived(() => {
70360
- var _$$get$siteCameras, _$$get15;
70361
- return (_$$get$siteCameras = (_$$get15 = get(config)) === null || _$$get15 === void 0 ? void 0 : _$$get15.siteCameras) !== null && _$$get$siteCameras !== void 0 ? _$$get$siteCameras : [];
70678
+ var _$$get$siteCameras, _$$get16;
70679
+ return (_$$get$siteCameras = (_$$get16 = get(config)) === null || _$$get16 === void 0 ? void 0 : _$$get16.siteCameras) !== null && _$$get$siteCameras !== void 0 ? _$$get$siteCameras : [];
70362
70680
  });
70363
70681
  let $2 = /* @__PURE__ */ user_derived(() => {
70364
- var _$$get$cameraThumbnai, _$$get16;
70365
- return (_$$get$cameraThumbnai = (_$$get16 = get(config)) === null || _$$get16 === void 0 ? void 0 : _$$get16.cameraThumbnail) !== null && _$$get$cameraThumbnai !== void 0 ? _$$get$cameraThumbnai : "";
70682
+ var _$$get$cameraThumbnai, _$$get17;
70683
+ return (_$$get$cameraThumbnai = (_$$get17 = get(config)) === null || _$$get17 === void 0 ? void 0 : _$$get17.cameraThumbnail) !== null && _$$get$cameraThumbnai !== void 0 ? _$$get$cameraThumbnai : "";
70366
70684
  });
70367
70685
  PropertiesPanel($$anchor, {
70368
70686
  get is3D() {