@nex125/seatmap-editor 0.1.5 → 0.1.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.
package/dist/index.cjs CHANGED
@@ -23,16 +23,75 @@ var BaseTool = class {
23
23
  };
24
24
 
25
25
  // src/tools/PanTool.ts
26
- var PanTool = class extends BaseTool {
26
+ var PanTool = class _PanTool extends BaseTool {
27
27
  name = "pan";
28
28
  cursor = "grab";
29
29
  isPanning = false;
30
30
  lastX = 0;
31
31
  lastY = 0;
32
+ panVelocity = { x: 0, y: 0 };
33
+ lastPanSampleTime = 0;
34
+ inertiaRaf = 0;
35
+ inertiaLastTime = 0;
36
+ panInertiaJelly = 55;
37
+ panInertiaCarry;
38
+ panInertiaFriction;
39
+ panInertiaMinSpeed;
40
+ static DEFAULT_PAN_VELOCITY_BLEND = 0.3;
41
+ static DEFAULT_PAN_STOP_DELTA = 0.3;
42
+ static DEFAULT_PAN_RELEASE_IDLE_MS = 90;
43
+ panVelocityBlend = _PanTool.DEFAULT_PAN_VELOCITY_BLEND;
44
+ panStopDelta = _PanTool.DEFAULT_PAN_STOP_DELTA;
45
+ panReleaseIdleMs = _PanTool.DEFAULT_PAN_RELEASE_IDLE_MS;
46
+ setInertiaOptions(options) {
47
+ this.panInertiaJelly = options.panInertiaJelly ?? this.panInertiaJelly;
48
+ this.panInertiaCarry = options.panInertiaCarry;
49
+ this.panInertiaFriction = options.panInertiaFriction;
50
+ this.panInertiaMinSpeed = options.panInertiaMinSpeed;
51
+ this.panVelocityBlend = options.panVelocityBlend !== void 0 ? Math.max(0.05, Math.min(0.95, options.panVelocityBlend)) : _PanTool.DEFAULT_PAN_VELOCITY_BLEND;
52
+ this.panStopDelta = options.panStopDelta !== void 0 ? Math.max(0, Math.min(4, options.panStopDelta)) : _PanTool.DEFAULT_PAN_STOP_DELTA;
53
+ this.panReleaseIdleMs = options.panReleaseIdleMs !== void 0 ? Math.max(0, Math.min(400, options.panReleaseIdleMs)) : _PanTool.DEFAULT_PAN_RELEASE_IDLE_MS;
54
+ }
55
+ stopPanInertia() {
56
+ if (this.inertiaRaf) {
57
+ cancelAnimationFrame(this.inertiaRaf);
58
+ this.inertiaRaf = 0;
59
+ }
60
+ }
61
+ startPanInertia(viewport) {
62
+ this.stopPanInertia();
63
+ const jelly = Math.max(0, Math.min(100, this.panInertiaJelly)) / 100;
64
+ const carry = this.panInertiaCarry !== void 0 ? Math.max(0, Math.min(0.98, this.panInertiaCarry)) : 0.58 + jelly * 0.28;
65
+ const baseFriction = this.panInertiaFriction !== void 0 ? Math.max(0.8, Math.min(0.995, this.panInertiaFriction)) : 0.88 + jelly * 0.08;
66
+ const minSpeed = this.panInertiaMinSpeed !== void 0 ? Math.max(1e-3, Math.min(0.05, this.panInertiaMinSpeed)) : 0.012 - jelly * 4e-3;
67
+ let velocity = {
68
+ x: this.panVelocity.x * carry,
69
+ y: this.panVelocity.y * carry
70
+ };
71
+ if (Math.hypot(velocity.x, velocity.y) < minSpeed) return;
72
+ this.inertiaLastTime = performance.now();
73
+ const tick = () => {
74
+ const now = performance.now();
75
+ const dt = Math.min(32, Math.max(8, now - this.inertiaLastTime));
76
+ this.inertiaLastTime = now;
77
+ const decay = Math.pow(baseFriction, dt / 16.67);
78
+ velocity = { x: velocity.x * decay, y: velocity.y * decay };
79
+ if (Math.hypot(velocity.x, velocity.y) < minSpeed) {
80
+ this.inertiaRaf = 0;
81
+ return;
82
+ }
83
+ viewport.pan(velocity.x * dt, velocity.y * dt);
84
+ this.inertiaRaf = requestAnimationFrame(tick);
85
+ };
86
+ this.inertiaRaf = requestAnimationFrame(tick);
87
+ }
32
88
  onPointerDown(e) {
89
+ this.stopPanInertia();
33
90
  this.isPanning = true;
34
91
  this.lastX = e.screenX;
35
92
  this.lastY = e.screenY;
93
+ this.panVelocity = { x: 0, y: 0 };
94
+ this.lastPanSampleTime = performance.now();
36
95
  }
37
96
  onPointerMove(e, viewport) {
38
97
  if (!this.isPanning) return;
@@ -40,13 +99,30 @@ var PanTool = class extends BaseTool {
40
99
  const dy = e.screenY - this.lastY;
41
100
  this.lastX = e.screenX;
42
101
  this.lastY = e.screenY;
102
+ const now = performance.now();
103
+ const dt = Math.max(1, now - this.lastPanSampleTime);
104
+ this.lastPanSampleTime = now;
105
+ const nextVelocity = { x: dx / dt, y: dy / dt };
106
+ const isNearlyStopped = Math.hypot(dx, dy) < this.panStopDelta;
107
+ this.panVelocity = isNearlyStopped ? { x: this.panVelocity.x * 0.35, y: this.panVelocity.y * 0.35 } : {
108
+ x: this.panVelocity.x * (1 - this.panVelocityBlend) + nextVelocity.x * this.panVelocityBlend,
109
+ y: this.panVelocity.y * (1 - this.panVelocityBlend) + nextVelocity.y * this.panVelocityBlend
110
+ };
43
111
  viewport.pan(dx, dy);
44
112
  }
45
- onPointerUp() {
113
+ onPointerUp(_e, viewport) {
114
+ if (this.isPanning) {
115
+ const timeSinceLastSample = performance.now() - this.lastPanSampleTime;
116
+ if (timeSinceLastSample > this.panReleaseIdleMs || Math.hypot(this.panVelocity.x, this.panVelocity.y) < 0.01) {
117
+ this.panVelocity = { x: 0, y: 0 };
118
+ }
119
+ this.startPanInertia(viewport);
120
+ }
46
121
  this.isPanning = false;
47
122
  }
48
123
  onDeactivate() {
49
124
  this.isPanning = false;
125
+ this.stopPanInertia();
50
126
  }
51
127
  };
52
128
  var GRID = 20;
@@ -91,6 +167,8 @@ var SelectTool = class extends BaseTool {
91
167
  const hits = this.spatialIndex.queryPoint({ x: e.worldX, y: e.worldY }, 12);
92
168
  const seatHit = this.pickNearestSeatHit(hits, { x: e.worldX, y: e.worldY });
93
169
  const sectionHit = hits.find((h) => h.type === "section");
170
+ const seatSection = seatHit ? venue.sections.find((section) => section.id === seatHit.sectionId) ?? null : null;
171
+ const treatSeatHitAsSection = Boolean(seatSection && seatmapCore.isDancefloorSection(seatSection));
94
172
  if (this.sectionResizeEnabled) {
95
173
  const clickedSection = sectionHit ? venue.sections.find((s) => s.id === sectionHit.sectionId) ?? null : null;
96
174
  if (clickedSection) {
@@ -126,11 +204,12 @@ var SelectTool = class extends BaseTool {
126
204
  return;
127
205
  }
128
206
  if (clickedSection && !seatHit) {
207
+ this.beginSectionDrag(venue, store, clickedSection);
129
208
  return;
130
209
  }
131
210
  }
132
211
  }
133
- if (seatHit?.seatId && store.getState().selectedSeatIds.has(seatHit.seatId)) {
212
+ if (seatHit?.seatId && !treatSeatHitAsSection && store.getState().selectedSeatIds.has(seatHit.seatId)) {
134
213
  const selectedIds = store.getState().selectedSeatIds;
135
214
  const sectionId = seatHit.sectionId;
136
215
  const originals = /* @__PURE__ */ new Map();
@@ -155,24 +234,11 @@ var SelectTool = class extends BaseTool {
155
234
  return;
156
235
  }
157
236
  }
158
- if (!this.sectionResizeEnabled && sectionHit && !seatHit) {
159
- const section = venue.sections.find((s) => s.id === sectionHit.sectionId);
237
+ const sectionToDragId = treatSeatHitAsSection ? seatHit?.sectionId : sectionHit?.sectionId;
238
+ if (sectionToDragId && (treatSeatHitAsSection || !seatHit)) {
239
+ const section = venue.sections.find((s) => s.id === sectionToDragId);
160
240
  if (section) {
161
- const selectedSectionIds = store.getState().selectedSectionIds;
162
- const sectionIds = selectedSectionIds.has(section.id) && selectedSectionIds.size > 1 ? [...selectedSectionIds] : [section.id];
163
- const originalPositions = /* @__PURE__ */ new Map();
164
- for (const sec of venue.sections) {
165
- if (sectionIds.includes(sec.id)) {
166
- originalPositions.set(sec.id, { ...sec.position });
167
- }
168
- }
169
- this.dragMode = {
170
- type: "section",
171
- primarySectionId: section.id,
172
- sectionIds,
173
- originalPositions,
174
- delta: { x: 0, y: 0 }
175
- };
241
+ this.beginSectionDrag(venue, store, section);
176
242
  return;
177
243
  }
178
244
  }
@@ -327,8 +393,10 @@ var SelectTool = class extends BaseTool {
327
393
  const hits = this.spatialIndex.queryPoint({ x: e.worldX, y: e.worldY }, 12);
328
394
  const seatHit = this.pickNearestSeatHit(hits, { x: e.worldX, y: e.worldY });
329
395
  const sectionHit = hits.find((h) => h.type === "section");
396
+ const seatSection = seatHit ? store.getState().venue?.sections.find((section) => section.id === seatHit.sectionId) ?? null : null;
397
+ const treatSeatHitAsSection = Boolean(seatSection && seatmapCore.isDancefloorSection(seatSection));
330
398
  const isMulti = e.ctrlKey || e.shiftKey || e.metaKey;
331
- if (seatHit?.seatId) {
399
+ if (seatHit?.seatId && !treatSeatHitAsSection) {
332
400
  this.resizeTargetSectionId = seatHit.sectionId;
333
401
  if (isMulti) {
334
402
  store.getState().toggleSeat(seatHit.seatId);
@@ -339,22 +407,28 @@ var SelectTool = class extends BaseTool {
339
407
  store.getState().setSelection([seatHit.seatId]);
340
408
  store.getState().selectSection(seatHit.sectionId);
341
409
  }
342
- } else if (sectionHit?.sectionId) {
410
+ } else {
411
+ const sectionId = treatSeatHitAsSection ? seatHit?.sectionId : sectionHit?.sectionId;
412
+ if (!sectionId) {
413
+ if (!isMulti) {
414
+ if (this.sectionResizeEnabled) {
415
+ this.resizeTargetSectionId = null;
416
+ }
417
+ store.getState().clearSelection();
418
+ }
419
+ this.reset();
420
+ return;
421
+ }
343
422
  if (this.sectionResizeEnabled) {
344
- this.resizeTargetSectionId = sectionHit.sectionId;
423
+ this.resizeTargetSectionId = sectionId;
345
424
  } else {
346
425
  if (isMulti) {
347
- store.getState().toggleSection(sectionHit.sectionId);
426
+ store.getState().toggleSection(sectionId);
348
427
  } else {
349
428
  store.getState().clearSelection();
350
- store.getState().selectSection(sectionHit.sectionId);
429
+ store.getState().selectSection(sectionId);
351
430
  }
352
431
  }
353
- } else if (!isMulti) {
354
- if (this.sectionResizeEnabled) {
355
- this.resizeTargetSectionId = null;
356
- }
357
- store.getState().clearSelection();
358
432
  }
359
433
  }
360
434
  this.reset();
@@ -395,6 +469,23 @@ var SelectTool = class extends BaseTool {
395
469
  }
396
470
  });
397
471
  }
472
+ beginSectionDrag(venue, store, section) {
473
+ const selectedSectionIds = store.getState().selectedSectionIds;
474
+ const sectionIds = selectedSectionIds.has(section.id) && selectedSectionIds.size > 1 ? [...selectedSectionIds] : [section.id];
475
+ const originalPositions = /* @__PURE__ */ new Map();
476
+ for (const sec of venue.sections) {
477
+ if (sectionIds.includes(sec.id)) {
478
+ originalPositions.set(sec.id, { ...sec.position });
479
+ }
480
+ }
481
+ this.dragMode = {
482
+ type: "section",
483
+ primarySectionId: section.id,
484
+ sectionIds,
485
+ originalPositions,
486
+ delta: { x: 0, y: 0 }
487
+ };
488
+ }
398
489
  commitDrag(store, dropWorld) {
399
490
  const venue = store.getState().venue;
400
491
  if (!venue) return;
@@ -1027,6 +1118,8 @@ var SelectTool = class extends BaseTool {
1027
1118
  }
1028
1119
  };
1029
1120
  var CLOSE_THRESHOLD = 15;
1121
+ var DANCEFLOOR_SEAT_ID_SUFFIX = "dancefloor-seat";
1122
+ var DANCEFLOOR_ROW_ID_SUFFIX = "dancefloor-row";
1030
1123
  var AddSectionTool = class extends BaseTool {
1031
1124
  constructor(history, categoryId = "") {
1032
1125
  super();
@@ -1036,8 +1129,10 @@ var AddSectionTool = class extends BaseTool {
1036
1129
  name = "add-section";
1037
1130
  cursor = "crosshair";
1038
1131
  mode = "rectangle";
1132
+ sectionKind = "section";
1039
1133
  points = [];
1040
1134
  onPointsChange;
1135
+ onSectionCreated;
1041
1136
  setCategoryId(id) {
1042
1137
  this.categoryId = id;
1043
1138
  }
@@ -1046,6 +1141,20 @@ var AddSectionTool = class extends BaseTool {
1046
1141
  this.points = [];
1047
1142
  this.notifyChange();
1048
1143
  }
1144
+ setSectionKind(sectionKind) {
1145
+ if (this.sectionKind === sectionKind) return;
1146
+ this.sectionKind = sectionKind;
1147
+ this.points = [];
1148
+ this.notifyChange();
1149
+ }
1150
+ hasPendingDraft() {
1151
+ return this.points.length > 0;
1152
+ }
1153
+ cancelDrawing() {
1154
+ if (this.points.length === 0) return;
1155
+ this.points = [];
1156
+ this.notifyChange();
1157
+ }
1049
1158
  onPointerDown(e, _viewport, store) {
1050
1159
  if (this.mode === "rectangle") {
1051
1160
  if (this.points.length === 0) {
@@ -1107,11 +1216,12 @@ var AddSectionTool = class extends BaseTool {
1107
1216
  }));
1108
1217
  const newSection = {
1109
1218
  id: seatmapCore.generateId(),
1110
- label: `Section ${Date.now().toString(36).slice(-3).toUpperCase()}`,
1219
+ label: this.sectionKind === "stage" ? "Stage" : this.sectionKind === "dancefloor" ? "Dancefloor" : `Section ${Date.now().toString(36).slice(-3).toUpperCase()}`,
1220
+ kind: this.sectionKind,
1111
1221
  position: { x: cx, y: cy },
1112
1222
  rotation: 0,
1113
- categoryId: this.categoryId,
1114
- rows: [],
1223
+ categoryId: this.sectionKind === "stage" ? "" : this.categoryId,
1224
+ rows: this.sectionKind === "dancefloor" ? this.createDancefloorRows(this.categoryId) : [],
1115
1225
  outline
1116
1226
  };
1117
1227
  this.history.execute({
@@ -1120,6 +1230,7 @@ var AddSectionTool = class extends BaseTool {
1120
1230
  const v = store.getState().venue;
1121
1231
  if (!v) return;
1122
1232
  store.getState().setVenue({ ...v, sections: [...v.sections, newSection] });
1233
+ this.onSectionCreated?.(newSection.id);
1123
1234
  },
1124
1235
  undo: () => {
1125
1236
  const v = store.getState().venue;
@@ -1139,12 +1250,27 @@ var AddSectionTool = class extends BaseTool {
1139
1250
  { x: a.x, y: b.y }
1140
1251
  ];
1141
1252
  }
1253
+ createDancefloorRows(categoryId) {
1254
+ const dancefloorSeat = {
1255
+ id: seatmapCore.generateId(DANCEFLOOR_SEAT_ID_SUFFIX),
1256
+ label: "Dancefloor",
1257
+ position: { x: 0, y: 0 },
1258
+ status: "available",
1259
+ categoryId
1260
+ };
1261
+ return [
1262
+ {
1263
+ id: seatmapCore.generateId(DANCEFLOOR_ROW_ID_SUFFIX),
1264
+ label: "DF",
1265
+ seats: [dancefloorSeat]
1266
+ }
1267
+ ];
1268
+ }
1142
1269
  notifyChange() {
1143
1270
  this.onPointsChange?.(this.points, false);
1144
1271
  }
1145
1272
  onDeactivate() {
1146
- this.points = [];
1147
- this.notifyChange();
1273
+ this.cancelDrawing();
1148
1274
  }
1149
1275
  };
1150
1276
  var GRID2 = 20;
@@ -1276,7 +1402,7 @@ var AddRowTool = class extends BaseTool {
1276
1402
  const sectionHits = hits.filter((h) => h.type === "section");
1277
1403
  if (sectionHits.length === 0) return null;
1278
1404
  const sectionIds = [...new Set(sectionHits.map((h) => h.sectionId))];
1279
- const sections = sectionIds.map((id) => venue.sections.find((s) => s.id === id)).filter((s) => Boolean(s));
1405
+ const sections = sectionIds.map((id) => venue.sections.find((s) => s.id === id)).filter((s) => Boolean(s)).filter((section) => !seatmapCore.isStageSection(section) && !seatmapCore.isDancefloorSection(section));
1280
1406
  if (sections.length === 0) return null;
1281
1407
  const containing = sections.find((section) => {
1282
1408
  if (section.outline.length < 3) return true;
@@ -1322,7 +1448,7 @@ var AddSeatTool = class extends BaseTool {
1322
1448
  const sectionHits = hits.filter((h) => h.type === "section");
1323
1449
  if (sectionHits.length === 0) return;
1324
1450
  const sectionIds = [...new Set(sectionHits.map((h) => h.sectionId))];
1325
- let section = sectionIds.map((id) => venue.sections.find((s) => s.id === id)).filter((s) => Boolean(s)).find((s) => {
1451
+ let section = sectionIds.map((id) => venue.sections.find((s) => s.id === id)).filter((s) => Boolean(s)).filter((s) => !seatmapCore.isStageSection(s) && !seatmapCore.isDancefloorSection(s)).find((s) => {
1326
1452
  if (s.outline.length < 3) return true;
1327
1453
  const c2 = Math.cos(-s.rotation);
1328
1454
  const s22 = Math.sin(-s.rotation);
@@ -1335,7 +1461,7 @@ var AddSeatTool = class extends BaseTool {
1335
1461
  return seatmapCore.pointInPolygon(local, s.outline);
1336
1462
  });
1337
1463
  if (!section) {
1338
- section = sectionIds.map((id) => venue.sections.find((s) => s.id === id)).filter((s) => Boolean(s)).sort(
1464
+ section = sectionIds.map((id) => venue.sections.find((s) => s.id === id)).filter((s) => Boolean(s)).filter((s) => !seatmapCore.isStageSection(s) && !seatmapCore.isDancefloorSection(s)).sort(
1339
1465
  (a, b) => Math.hypot(e.worldX - a.position.x, e.worldY - a.position.y) - Math.hypot(e.worldX - b.position.x, e.worldY - b.position.y)
1340
1466
  )[0];
1341
1467
  }
@@ -1450,33 +1576,100 @@ var AddSeatTool = class extends BaseTool {
1450
1576
  return candidate;
1451
1577
  }
1452
1578
  };
1579
+ function PanIcon(props) {
1580
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": true, ...props, children: [
1581
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 11V6.6a1.6 1.6 0 1 1 3.2 0V10", strokeLinecap: "round" }),
1582
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M11.2 10V5.2a1.6 1.6 0 1 1 3.2 0v5", strokeLinecap: "round" }),
1583
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14.4 10V6.8a1.6 1.6 0 1 1 3.2 0v6.2c0 4-2 6.2-6 6.2h-.4c-3.3 0-5.8-2.2-6.4-5.4l-.7-3.7a1.4 1.4 0 1 1 2.7-.6l.6 2.5", strokeLinecap: "round" })
1584
+ ] });
1585
+ }
1586
+ function SelectIcon(props) {
1587
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 4.5v14l4.4-3 2.5 4.1 2.1-1.3-2.5-4.1 5-.3L5 4.5Z", strokeLinejoin: "round" }) });
1588
+ }
1589
+ function SectionIcon(props) {
1590
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": true, ...props, children: [
1591
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "4.5", y: "4.5", width: "15", height: "15", rx: "2.5" }),
1592
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 9h6M9 15h6", strokeLinecap: "round" })
1593
+ ] });
1594
+ }
1595
+ function RowIcon(props) {
1596
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": true, ...props, children: [
1597
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4.5 8h15M4.5 12h15M4.5 16h15", strokeLinecap: "round" }),
1598
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "6.4", cy: "8", r: "0.8", fill: "currentColor", stroke: "none" }),
1599
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "6.4", cy: "12", r: "0.8", fill: "currentColor", stroke: "none" }),
1600
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "6.4", cy: "16", r: "0.8", fill: "currentColor", stroke: "none" })
1601
+ ] });
1602
+ }
1603
+ function SeatIcon(props) {
1604
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": true, ...props, children: [
1605
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 12.5V9.4a2.4 2.4 0 1 1 4.8 0v3.1", strokeLinecap: "round" }),
1606
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 17v-3.1a1.9 1.9 0 0 1 1.9-1.9h8.2a1.9 1.9 0 0 1 1.9 1.9V17", strokeLinecap: "round" }),
1607
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4.8 17.8h14.4", strokeLinecap: "round" })
1608
+ ] });
1609
+ }
1610
+ function UndoIcon(props) {
1611
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": true, ...props, children: [
1612
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8.5 8.5H5v3.5", strokeLinecap: "round", strokeLinejoin: "round" }),
1613
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5.2 8.7c1.8-2 4.2-3.2 7-3.2 5 0 8.8 3.7 8.8 8.5", strokeLinecap: "round" })
1614
+ ] });
1615
+ }
1616
+ function RedoIcon(props) {
1617
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": true, ...props, children: [
1618
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15.5 8.5H19v3.5", strokeLinecap: "round", strokeLinejoin: "round" }),
1619
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18.8 8.7c-1.8-2-4.2-3.2-7-3.2C6.8 5.5 3 9.2 3 14", strokeLinecap: "round" })
1620
+ ] });
1621
+ }
1622
+ function FitIcon(props) {
1623
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": true, ...props, children: [
1624
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 4.8H4.8V9M15 4.8h4.2V9M9 19.2H4.8V15M15 19.2h4.2V15", strokeLinecap: "round", strokeLinejoin: "round" }),
1625
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "8.2", y: "8.2", width: "7.6", height: "7.6", rx: "1.4" })
1626
+ ] });
1627
+ }
1628
+ function GridIcon(props) {
1629
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": true, ...props, children: [
1630
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", y: "5", width: "14", height: "14", rx: "2" }),
1631
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 5v14M5 12h14", strokeLinecap: "round" })
1632
+ ] });
1633
+ }
1634
+ function HintIcon(props) {
1635
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": true, ...props, children: [
1636
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9.4 9.2a2.7 2.7 0 1 1 4.8 1.6c-.6.8-1.6 1.4-1.8 2.7", strokeLinecap: "round" }),
1637
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 17.4h.01M8.7 19h6.6", strokeLinecap: "round" }),
1638
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 3.8a8.2 8.2 0 1 1 0 16.4 8.2 8.2 0 0 1 0-16.4Z" })
1639
+ ] });
1640
+ }
1641
+ function SaveIcon(props) {
1642
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": true, ...props, children: [
1643
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 4.5v9.2M8.4 10.7 12 14.3l3.6-3.6", strokeLinecap: "round", strokeLinejoin: "round" }),
1644
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", y: "16.2", width: "14", height: "3.8", rx: "1.2" })
1645
+ ] });
1646
+ }
1647
+ function LoadIcon(props) {
1648
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": true, ...props, children: [
1649
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 14.7V5.5M8.4 9.3 12 5.7l3.6 3.6", strokeLinecap: "round", strokeLinejoin: "round" }),
1650
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", y: "16.2", width: "14", height: "3.8", rx: "1.2" })
1651
+ ] });
1652
+ }
1653
+ function SettingsIcon(props) {
1654
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": true, ...props, children: [
1655
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 8.2a3.8 3.8 0 1 1 0 7.6 3.8 3.8 0 0 1 0-7.6Z" }),
1656
+ /* @__PURE__ */ jsxRuntime.jsx(
1657
+ "path",
1658
+ {
1659
+ d: "m18.8 12 .9 1.6-1.5 2.6-1.8-.1a6.8 6.8 0 0 1-1.4.8L14.4 19h-3l-.6-2.1a6.8 6.8 0 0 1-1.4-.8l-1.8.1-1.5-2.6.9-1.6a6.7 6.7 0 0 1 0-1.6l-.9-1.6 1.5-2.6 1.8.1c.4-.3.9-.6 1.4-.8l.6-2.1h3l.6 2.1c.5.2 1 .5 1.4.8l1.8-.1 1.5 2.6-.9 1.6c.1.5.1 1.1 0 1.6Z",
1660
+ strokeLinecap: "round",
1661
+ strokeLinejoin: "round"
1662
+ }
1663
+ )
1664
+ ] });
1665
+ }
1453
1666
  var tools = [
1454
- { id: "pan", label: "Pan", icon: "\u270B" },
1455
- { id: "select", label: "Select", icon: "\u2196" },
1456
- { id: "add-section", label: "Section", icon: "\u25A2" },
1457
- { id: "add-row", label: "Row", icon: "\u22EF" },
1458
- { id: "add-seat", label: "Seat", icon: "+" }
1667
+ { id: "pan", label: "Pan", icon: PanIcon },
1668
+ { id: "select", label: "Select", icon: SelectIcon },
1669
+ { id: "add-section", label: "Section", icon: SectionIcon },
1670
+ { id: "add-row", label: "Row", icon: RowIcon },
1671
+ { id: "add-seat", label: "Seat", icon: SeatIcon }
1459
1672
  ];
1460
- var btnBase = {
1461
- padding: "6px 10px",
1462
- borderWidth: 1,
1463
- borderStyle: "solid",
1464
- borderColor: "#3a3a5a",
1465
- borderRadius: 6,
1466
- background: "#2a2a4a",
1467
- color: "#e0e0e0",
1468
- cursor: "pointer",
1469
- fontSize: 13,
1470
- fontFamily: "system-ui",
1471
- display: "flex",
1472
- alignItems: "center",
1473
- gap: 4
1474
- };
1475
- var activeBtnStyle = {
1476
- ...btnBase,
1477
- background: "#4a4a7a",
1478
- borderColor: "#6a6aaa"
1479
- };
1480
1673
  function Toolbar({
1481
1674
  activeTool,
1482
1675
  onToolChange,
@@ -1490,112 +1683,171 @@ function Toolbar({
1490
1683
  onFitView,
1491
1684
  onSave,
1492
1685
  onLoad,
1686
+ showHints,
1687
+ onToggleHints,
1688
+ isEditorSettingsOpen,
1689
+ onToggleEditorSettings,
1493
1690
  style
1494
1691
  }) {
1692
+ const getToolbarButtonClassName = (isActive = false, isHighlighted = false) => `seatmap-editor__toolbar-button${isActive ? " is-active" : ""}${isHighlighted ? " is-highlighted" : ""}`;
1495
1693
  return /* @__PURE__ */ jsxRuntime.jsx(
1496
1694
  "div",
1497
1695
  {
1696
+ className: `seatmap-editor__toolbar${showHints ? " has-shortcuts-row" : ""}`,
1498
1697
  style: {
1499
- display: "flex",
1500
- background: "#1a1a2e",
1501
- borderBottom: "1px solid #2a2a4a",
1502
1698
  ...style
1503
1699
  },
1504
- children: /* @__PURE__ */ jsxRuntime.jsxs(
1505
- "div",
1506
- {
1507
- style: {
1508
- display: "flex",
1509
- gap: 4,
1510
- padding: "8px 12px",
1511
- alignItems: "center",
1512
- flexWrap: "wrap"
1700
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__toolbar-row seatmap-editor__toolbar-row--primary", children: [
1701
+ tools.map((tool) => /* @__PURE__ */ jsxRuntime.jsxs(
1702
+ "button",
1703
+ {
1704
+ type: "button",
1705
+ onClick: () => onToolChange(tool.id),
1706
+ className: `${getToolbarButtonClassName(activeTool === tool.id)} seatmap-editor__toolbar-tool-button`,
1707
+ title: tool.label,
1708
+ children: [
1709
+ /* @__PURE__ */ jsxRuntime.jsx(tool.icon, { className: "seatmap-editor__toolbar-icon" }),
1710
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: tool.label })
1711
+ ]
1513
1712
  },
1514
- children: [
1515
- tools.map((tool) => /* @__PURE__ */ jsxRuntime.jsxs(
1516
- "button",
1517
- {
1518
- onClick: () => onToolChange(tool.id),
1519
- style: activeTool === tool.id ? activeBtnStyle : btnBase,
1520
- title: tool.label,
1521
- children: [
1522
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: tool.icon }),
1523
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: tool.label })
1524
- ]
1525
- },
1526
- tool.id
1527
- )),
1528
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 1, height: 24, background: "#3a3a5a", margin: "0 6px" } }),
1529
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onUndo, disabled: !canUndo, style: { ...btnBase, opacity: canUndo ? 1 : 0.4 }, title: "Undo (Ctrl+Z)", children: "\u21A9 Undo" }),
1530
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onRedo, disabled: !canRedo, style: { ...btnBase, opacity: canRedo ? 1 : 0.4 }, title: "Redo (Ctrl+Shift+Z)", children: "\u21AA Redo" }),
1531
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 1, height: 24, background: "#3a3a5a", margin: "0 6px" } }),
1532
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onFitView, style: btnBase, title: "Fit to view", children: "\u229E Fit" }),
1533
- /* @__PURE__ */ jsxRuntime.jsx(
1534
- "button",
1535
- {
1536
- onClick: onToggleGridOptions,
1537
- style: {
1538
- ...isGridOptionsOpen ? activeBtnStyle : btnBase,
1539
- borderColor: gridEnabled ? "#57b26f" : isGridOptionsOpen ? activeBtnStyle.borderColor : btnBase.borderColor,
1540
- boxShadow: gridEnabled ? "0 0 0 1px #57b26f inset" : "none"
1541
- },
1542
- title: "Show grid options",
1543
- children: "# Grid"
1544
- }
1545
- ),
1546
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 1, height: 24, background: "#3a3a5a", margin: "0 6px" } }),
1547
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onSave, style: btnBase, title: "Export venue as JSON", children: "\u2193 Save" }),
1548
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onLoad, style: btnBase, title: "Import venue from JSON", children: "\u2191 Load" })
1549
- ]
1550
- }
1551
- )
1713
+ tool.id
1714
+ )),
1715
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__toolbar-divider" }),
1716
+ /* @__PURE__ */ jsxRuntime.jsxs(
1717
+ "button",
1718
+ {
1719
+ type: "button",
1720
+ onClick: onUndo,
1721
+ disabled: !canUndo,
1722
+ className: getToolbarButtonClassName(),
1723
+ title: "Undo (Ctrl+Z)",
1724
+ children: [
1725
+ /* @__PURE__ */ jsxRuntime.jsx(UndoIcon, { className: "seatmap-editor__toolbar-icon" }),
1726
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Undo" })
1727
+ ]
1728
+ }
1729
+ ),
1730
+ /* @__PURE__ */ jsxRuntime.jsxs(
1731
+ "button",
1732
+ {
1733
+ type: "button",
1734
+ onClick: onRedo,
1735
+ disabled: !canRedo,
1736
+ className: getToolbarButtonClassName(),
1737
+ title: "Redo (Ctrl+Shift+Z)",
1738
+ children: [
1739
+ /* @__PURE__ */ jsxRuntime.jsx(RedoIcon, { className: "seatmap-editor__toolbar-icon" }),
1740
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Redo" })
1741
+ ]
1742
+ }
1743
+ ),
1744
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__toolbar-divider" }),
1745
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: onFitView, className: getToolbarButtonClassName(), title: "Fit to view", children: [
1746
+ /* @__PURE__ */ jsxRuntime.jsx(FitIcon, { className: "seatmap-editor__toolbar-icon" }),
1747
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Fit" })
1748
+ ] }),
1749
+ /* @__PURE__ */ jsxRuntime.jsxs(
1750
+ "button",
1751
+ {
1752
+ type: "button",
1753
+ onClick: onToggleGridOptions,
1754
+ className: getToolbarButtonClassName(isGridOptionsOpen, gridEnabled),
1755
+ title: "Show grid options",
1756
+ children: [
1757
+ /* @__PURE__ */ jsxRuntime.jsx(GridIcon, { className: "seatmap-editor__toolbar-icon" }),
1758
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Grid" })
1759
+ ]
1760
+ }
1761
+ ),
1762
+ /* @__PURE__ */ jsxRuntime.jsxs(
1763
+ "button",
1764
+ {
1765
+ type: "button",
1766
+ onClick: onToggleHints,
1767
+ className: getToolbarButtonClassName(showHints, showHints),
1768
+ title: "Toggle inline editor hints",
1769
+ children: [
1770
+ /* @__PURE__ */ jsxRuntime.jsx(HintIcon, { className: "seatmap-editor__toolbar-icon" }),
1771
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Hints" })
1772
+ ]
1773
+ }
1774
+ ),
1775
+ /* @__PURE__ */ jsxRuntime.jsxs(
1776
+ "button",
1777
+ {
1778
+ type: "button",
1779
+ onClick: onToggleEditorSettings,
1780
+ className: getToolbarButtonClassName(isEditorSettingsOpen, isEditorSettingsOpen),
1781
+ title: "Editor settings",
1782
+ children: [
1783
+ /* @__PURE__ */ jsxRuntime.jsx(SettingsIcon, { className: "seatmap-editor__toolbar-icon" }),
1784
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Settings" })
1785
+ ]
1786
+ }
1787
+ ),
1788
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__toolbar-divider" }),
1789
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: onSave, className: getToolbarButtonClassName(), title: "Export venue as JSON", children: [
1790
+ /* @__PURE__ */ jsxRuntime.jsx(SaveIcon, { className: "seatmap-editor__toolbar-icon" }),
1791
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Save" })
1792
+ ] }),
1793
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: onLoad, className: getToolbarButtonClassName(), title: "Import venue from JSON", children: [
1794
+ /* @__PURE__ */ jsxRuntime.jsx(LoadIcon, { className: "seatmap-editor__toolbar-icon" }),
1795
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Load" })
1796
+ ] }),
1797
+ showHints && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__toolbar-shortcuts-panel", "aria-label": "Keyboard shortcuts", children: [
1798
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__toolbar-shortcuts-title", children: "Keyboard shortcuts" }),
1799
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__toolbar-shortcuts-row", children: [
1800
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1801
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { children: "H" }),
1802
+ " / ",
1803
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { children: "1" }),
1804
+ " - Pan"
1805
+ ] }),
1806
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1807
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { children: "V" }),
1808
+ " / ",
1809
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { children: "2" }),
1810
+ " - Select"
1811
+ ] }),
1812
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1813
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { children: "S" }),
1814
+ " / ",
1815
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { children: "3" }),
1816
+ " - Add Section"
1817
+ ] })
1818
+ ] }),
1819
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__toolbar-shortcuts-row", children: [
1820
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1821
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { children: "R" }),
1822
+ " / ",
1823
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { children: "4" }),
1824
+ " - Add Row"
1825
+ ] }),
1826
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1827
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { children: "A" }),
1828
+ " / ",
1829
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { children: "5" }),
1830
+ " - Add Seat"
1831
+ ] }),
1832
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1833
+ /* @__PURE__ */ jsxRuntime.jsx("kbd", { children: "Space" }),
1834
+ " (toggle) - Toggle Pan"
1835
+ ] })
1836
+ ] })
1837
+ ] })
1838
+ ] })
1552
1839
  }
1553
1840
  );
1554
1841
  }
1555
- var labelStyle = {
1556
- fontSize: 11,
1557
- color: "#9e9e9e",
1558
- marginBottom: 2,
1559
- fontFamily: "system-ui"
1560
- };
1561
- var inputStyle = {
1562
- width: "100%",
1563
- padding: "4px 8px",
1564
- background: "#2a2a4a",
1565
- border: "1px solid #3a3a5a",
1566
- borderRadius: 4,
1567
- color: "#e0e0e0",
1568
- fontSize: 13,
1569
- fontFamily: "system-ui",
1570
- boxSizing: "border-box"
1571
- };
1572
- var selectStyle = { ...inputStyle, cursor: "pointer" };
1573
- var btnDanger = {
1574
- padding: "3px 8px",
1575
- border: "1px solid #5a2a2a",
1576
- borderRadius: 4,
1577
- background: "#3a1a1a",
1578
- color: "#f48888",
1579
- cursor: "pointer",
1580
- fontSize: 12,
1581
- fontFamily: "system-ui"
1582
- };
1583
- var btnSmall = {
1584
- padding: "3px 8px",
1585
- border: "1px solid #3a3a5a",
1586
- borderRadius: 4,
1587
- background: "#2a2a4a",
1588
- color: "#e0e0e0",
1589
- cursor: "pointer",
1590
- fontSize: 12,
1591
- fontFamily: "system-ui"
1592
- };
1593
1842
  function freshVenue(store) {
1594
1843
  return store.getState().venue;
1595
1844
  }
1596
1845
  function setVenue(store, venue) {
1597
1846
  store.getState().setVenue(venue);
1598
1847
  }
1848
+ function isSectionSeatLayoutLocked(section) {
1849
+ return seatmapCore.isStageSection(section) || seatmapCore.isDancefloorSection(section);
1850
+ }
1599
1851
  function PropertyPanel({
1600
1852
  venue,
1601
1853
  selectedSeatIds,
@@ -1668,7 +1920,9 @@ function PropertyPanel({
1668
1920
  const updateSectionCategory = (sectionId, categoryId) => {
1669
1921
  const v = freshVenue(store);
1670
1922
  if (!v) return;
1671
- const oldCatId = v.sections.find((s) => s.id === sectionId)?.categoryId ?? "";
1923
+ const targetSection = v.sections.find((s) => s.id === sectionId);
1924
+ if (!targetSection || seatmapCore.isStageSection(targetSection)) return;
1925
+ const oldCatId = targetSection.categoryId;
1672
1926
  history.execute({
1673
1927
  description: `Change section category`,
1674
1928
  execute: () => {
@@ -1743,12 +1997,15 @@ function PropertyPanel({
1743
1997
  if (sectionIds.length === 0) return;
1744
1998
  const v = freshVenue(store);
1745
1999
  if (!v) return;
1746
- const targetIds = new Set(sectionIds);
2000
+ const targetIds = new Set(
2001
+ v.sections.filter((section) => sectionIds.includes(section.id) && !seatmapCore.isStageSection(section)).map((section) => section.id)
2002
+ );
2003
+ if (targetIds.size === 0) return;
1747
2004
  const previousCategoryBySectionId = new Map(
1748
2005
  v.sections.filter((section) => targetIds.has(section.id)).map((section) => [section.id, section.categoryId])
1749
2006
  );
1750
2007
  history.execute({
1751
- description: `Change category for ${sectionIds.length} section(s)`,
2008
+ description: `Change category for ${targetIds.size} section(s)`,
1752
2009
  execute: () => {
1753
2010
  const cur = freshVenue(store);
1754
2011
  if (!cur) return;
@@ -1788,26 +2045,6 @@ function PropertyPanel({
1788
2045
  }
1789
2046
  });
1790
2047
  };
1791
- const deleteSection = (sectionId) => {
1792
- const v = freshVenue(store);
1793
- if (!v) return;
1794
- const removed = v.sections.find((s) => s.id === sectionId);
1795
- if (!removed) return;
1796
- history.execute({
1797
- description: `Delete section "${removed.label}"`,
1798
- execute: () => {
1799
- const cur = freshVenue(store);
1800
- if (!cur) return;
1801
- setVenue(store, { ...cur, sections: cur.sections.filter((s) => s.id !== sectionId) });
1802
- store.getState().clearSelection();
1803
- },
1804
- undo: () => {
1805
- const cur = freshVenue(store);
1806
- if (!cur) return;
1807
- setVenue(store, { ...cur, sections: [...cur.sections, removed] });
1808
- }
1809
- });
1810
- };
1811
2048
  const deleteRow = (sectionId, rowId) => {
1812
2049
  const v = freshVenue(store);
1813
2050
  if (!v) return;
@@ -1881,11 +2118,43 @@ function PropertyPanel({
1881
2118
  }
1882
2119
  });
1883
2120
  };
2121
+ const deleteSelectedObjects = () => {
2122
+ if (selectedSeatIds.size === 0 && selectedSectionIds.size === 0) return;
2123
+ const v = freshVenue(store);
2124
+ if (!v) return;
2125
+ const selectedSectionIdSet = new Set(selectedSectionIds);
2126
+ const selectedSeatIdSet = new Set(selectedSeatIds);
2127
+ const previousVenue = v;
2128
+ const nextVenue = {
2129
+ ...v,
2130
+ sections: v.sections.filter((section) => !selectedSectionIdSet.has(section.id)).map((section) => ({
2131
+ ...section,
2132
+ rows: section.rows.map((row) => ({
2133
+ ...row,
2134
+ seats: row.seats.filter((seat) => !selectedSeatIdSet.has(seat.id))
2135
+ }))
2136
+ })),
2137
+ tables: v.tables.map((table) => ({
2138
+ ...table,
2139
+ seats: table.seats.filter((seat) => !selectedSeatIdSet.has(seat.id))
2140
+ }))
2141
+ };
2142
+ history.execute({
2143
+ description: "Delete selected objects",
2144
+ execute: () => {
2145
+ setVenue(store, nextVenue);
2146
+ store.getState().clearSelection();
2147
+ },
2148
+ undo: () => {
2149
+ setVenue(store, previousVenue);
2150
+ }
2151
+ });
2152
+ };
1884
2153
  const addSingleSeat = (sectionId) => {
1885
2154
  const v = freshVenue(store);
1886
2155
  if (!v) return;
1887
2156
  const sec = v.sections.find((s) => s.id === sectionId);
1888
- if (!sec) return;
2157
+ if (!sec || isSectionSeatLayoutLocked(sec)) return;
1889
2158
  let targetRow = sec.rows[sec.rows.length - 1];
1890
2159
  const newSeat = {
1891
2160
  id: seatmapCore.generateId(),
@@ -1989,7 +2258,7 @@ function PropertyPanel({
1989
2258
  });
1990
2259
  };
1991
2260
  if (!venue) {
1992
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 16, color: "#9e9e9e", fontSize: 13, fontFamily: "system-ui", ...style }, children: "No venue loaded" });
2261
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel seatmap-editor__panel-muted", style, children: "No venue loaded" });
1993
2262
  }
1994
2263
  const selectedSeatsEverywhere = [];
1995
2264
  for (const section of venue.sections) {
@@ -2008,24 +2277,29 @@ function PropertyPanel({
2008
2277
  const isMixedSeatStatus = selectedSeatStatusIds.size > 1;
2009
2278
  const selectedSeatStatusId = selectedSeatStatusIds.size > 0 ? [...selectedSeatStatusIds][0] : seatmapCore.AVAILABLE_STATUS_ID;
2010
2279
  const hasMultipleSelectedSections = selectedSections.length > 1;
2280
+ const selectedNonStageSections = selectedSections.filter((section) => !seatmapCore.isStageSection(section));
2281
+ const hasSelectedStage = selectedSections.some((section) => seatmapCore.isStageSection(section));
2011
2282
  const selectedSectionIdsList = selectedSections.map((section) => section.id);
2012
2283
  const selectedSectionLabels = new Set(selectedSections.map((section) => section.label));
2013
- const selectedSectionCategoryIds = new Set(selectedSections.map((section) => section.categoryId));
2284
+ const selectedSectionCategoryIds = new Set(selectedNonStageSections.map((section) => section.categoryId));
2014
2285
  const sharedLabelValue = selectedSectionLabels.size === 1 ? selectedSections[0]?.label ?? "" : "";
2015
- const sharedCategoryValue = selectedSectionCategoryIds.size === 1 ? selectedSections[0]?.categoryId ?? "" : "__mixed__";
2016
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: 16, ...style }, children: [
2017
- selectedSeatIds.size > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 24 }, children: [
2018
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontWeight: 600, color: "#e0e0e0", fontSize: 14, fontFamily: "system-ui", marginBottom: 12 }, children: [
2019
- "Seat Config (",
2020
- selectedSeatIds.size,
2021
- " selected)"
2286
+ const sharedCategoryValue = selectedNonStageSections.length === 0 ? "" : selectedSectionCategoryIds.size === 1 ? selectedNonStageSections[0]?.categoryId ?? "" : "__mixed__";
2287
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel", style, children: [
2288
+ selectedSeatIds.size > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section", children: [
2289
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section-header", children: [
2290
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-title", children: [
2291
+ "Seat Config (",
2292
+ selectedSeatIds.size,
2293
+ " selected)"
2294
+ ] }),
2295
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: deleteSelectedObjects, className: "seatmap-editor__panel-button seatmap-editor__panel-button--danger", title: "Delete selected objects", children: "Delete Selected" })
2022
2296
  ] }),
2023
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 16 }, children: [
2024
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle, children: "Seat Status" }),
2297
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section", children: [
2298
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-label", children: "Seat Status" }),
2025
2299
  /* @__PURE__ */ jsxRuntime.jsxs(
2026
2300
  "select",
2027
2301
  {
2028
- style: selectStyle,
2302
+ className: "seatmap-editor__panel-select",
2029
2303
  value: isMixedSeatStatus ? "__mixed__" : selectedSeatStatusId,
2030
2304
  onChange: (e) => updateSelectedSeatStatus(e.target.value),
2031
2305
  children: [
@@ -2035,13 +2309,13 @@ function PropertyPanel({
2035
2309
  }
2036
2310
  )
2037
2311
  ] }),
2038
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 10 }, children: [
2039
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: labelStyle, children: [
2312
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section", children: [
2313
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-label", children: [
2040
2314
  "Selected Seats (",
2041
2315
  selectedSeatIds.size,
2042
2316
  ")"
2043
2317
  ] }),
2044
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { maxHeight: 200, overflowY: "auto" }, children: Array.from(selectedSeatIds).map((seatId) => {
2318
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-list seatmap-editor__panel-scroll seatmap-editor__panel-scroll--sm", children: Array.from(selectedSeatIds).map((seatId) => {
2045
2319
  let found = null;
2046
2320
  for (const section2 of venue.sections) {
2047
2321
  for (const row2 of section2.rows) {
@@ -2058,20 +2332,9 @@ function PropertyPanel({
2058
2332
  return /* @__PURE__ */ jsxRuntime.jsxs(
2059
2333
  "div",
2060
2334
  {
2061
- style: {
2062
- display: "flex",
2063
- alignItems: "center",
2064
- gap: 6,
2065
- padding: "2px 6px",
2066
- fontSize: 12,
2067
- fontFamily: "system-ui",
2068
- color: "#e0e0e0",
2069
- background: "#2a2a4a",
2070
- marginBottom: 2,
2071
- borderRadius: 4
2072
- },
2335
+ className: "seatmap-editor__panel-list-item",
2073
2336
  children: [
2074
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { flex: 1 }, children: [
2337
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "seatmap-editor__panel-text seatmap-editor__panel-content-grow", children: [
2075
2338
  section.label,
2076
2339
  " \xB7 Row ",
2077
2340
  row.label,
@@ -2082,7 +2345,7 @@ function PropertyPanel({
2082
2345
  "button",
2083
2346
  {
2084
2347
  onClick: () => deleteSeat(section.id, row.id, seat.id),
2085
- style: { ...btnDanger, padding: "1px 5px", fontSize: 11 },
2348
+ className: "seatmap-editor__panel-button seatmap-editor__panel-button--danger seatmap-editor__panel-button--tiny",
2086
2349
  title: "Delete seat",
2087
2350
  children: "\u2715"
2088
2351
  }
@@ -2093,20 +2356,23 @@ function PropertyPanel({
2093
2356
  );
2094
2357
  }) })
2095
2358
  ] }),
2096
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 1, background: "#2a2a4a", margin: "20px 0" } })
2359
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-divider" })
2097
2360
  ] }),
2098
- selectedSeatIds.size === 0 && selectedSections.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 24 }, children: [
2099
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontWeight: 600, color: "#e0e0e0", fontSize: 14, fontFamily: "system-ui" }, children: [
2100
- "Section Config",
2101
- selectedSections.length > 1 ? ` (${selectedSections.length} selected)` : ""
2102
- ] }) }),
2103
- hasMultipleSelectedSections && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 12, padding: 10, borderRadius: 6, background: "#26264a" }, children: [
2104
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 10 }, children: [
2105
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle, children: "Label (apply to all selected)" }),
2361
+ selectedSeatIds.size === 0 && selectedSections.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section", children: [
2362
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section-header", children: [
2363
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-title", children: [
2364
+ "Section / Stage / Dancefloor Config",
2365
+ selectedSections.length > 1 ? ` (${selectedSections.length} selected)` : ""
2366
+ ] }),
2367
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: deleteSelectedObjects, className: "seatmap-editor__panel-button seatmap-editor__panel-button--danger", title: "Delete selected objects", children: "Delete Selected" })
2368
+ ] }),
2369
+ hasMultipleSelectedSections && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section seatmap-editor__panel-section--card", children: [
2370
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section", children: [
2371
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-label", children: "Label (apply to all selected)" }),
2106
2372
  /* @__PURE__ */ jsxRuntime.jsx(
2107
2373
  "input",
2108
2374
  {
2109
- style: inputStyle,
2375
+ className: "seatmap-editor__panel-input",
2110
2376
  value: sharedLabelValue,
2111
2377
  placeholder: "Mixed labels",
2112
2378
  onChange: (e) => updateSelectedSectionsLabel(selectedSectionIdsList, e.target.value)
@@ -2114,129 +2380,125 @@ function PropertyPanel({
2114
2380
  )
2115
2381
  ] }),
2116
2382
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2117
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle, children: "Category (apply to all selected)" }),
2383
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-label", children: "Category (apply to all selected)" }),
2118
2384
  /* @__PURE__ */ jsxRuntime.jsxs(
2119
2385
  "select",
2120
2386
  {
2121
- style: selectStyle,
2387
+ className: "seatmap-editor__panel-select",
2122
2388
  value: sharedCategoryValue,
2123
2389
  onChange: (e) => updateSelectedSectionsCategory(selectedSectionIdsList, e.target.value),
2390
+ disabled: selectedNonStageSections.length === 0,
2124
2391
  children: [
2392
+ selectedNonStageSections.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: "Not applicable for stage" }),
2125
2393
  sharedCategoryValue === "__mixed__" && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "__mixed__", disabled: true, children: "Mixed" }),
2126
2394
  venue.categories.map((cat) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: cat.id, children: cat.name }, cat.id))
2127
2395
  ]
2128
2396
  }
2129
- )
2397
+ ),
2398
+ hasSelectedStage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-label", children: "Stage selection is excluded from category changes." })
2130
2399
  ] })
2131
2400
  ] }),
2132
- selectedSections.map((selectedSection) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 14, padding: 10, borderRadius: 6, background: "#222242" }, children: [
2133
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 10 }, children: [
2134
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "#c7c7df", fontSize: 12, fontFamily: "system-ui", fontWeight: 600 }, children: [
2135
- "ID: ",
2136
- selectedSection.id
2137
- ] }),
2138
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => deleteSection(selectedSection.id), style: btnDanger, title: "Delete section", children: "Delete" })
2139
- ] }),
2401
+ selectedSections.map((selectedSection) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section seatmap-editor__panel-section--card", children: [
2402
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-section-header", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-muted", children: [
2403
+ "ID: ",
2404
+ selectedSection.id
2405
+ ] }) }),
2140
2406
  !hasMultipleSelectedSections && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2141
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 10 }, children: [
2142
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle, children: "Label" }),
2407
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section", children: [
2408
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-label", children: "Label" }),
2143
2409
  /* @__PURE__ */ jsxRuntime.jsx(
2144
2410
  "input",
2145
2411
  {
2146
- style: inputStyle,
2412
+ className: "seatmap-editor__panel-input",
2147
2413
  value: selectedSection.label,
2148
2414
  onChange: (e) => updateSectionLabel(selectedSection.id, e.target.value)
2149
2415
  }
2150
2416
  )
2151
2417
  ] }),
2152
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 10 }, children: [
2153
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle, children: "Category" }),
2154
- /* @__PURE__ */ jsxRuntime.jsx(
2418
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section", children: [
2419
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-label", children: "Category" }),
2420
+ /* @__PURE__ */ jsxRuntime.jsxs(
2155
2421
  "select",
2156
2422
  {
2157
- style: selectStyle,
2423
+ className: "seatmap-editor__panel-select",
2158
2424
  value: selectedSection.categoryId,
2159
2425
  onChange: (e) => updateSectionCategory(selectedSection.id, e.target.value),
2160
- children: venue.categories.map((cat) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: cat.id, children: cat.name }, cat.id))
2426
+ disabled: seatmapCore.isStageSection(selectedSection),
2427
+ children: [
2428
+ seatmapCore.isStageSection(selectedSection) && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: "Not applicable for stage" }),
2429
+ venue.categories.map((cat) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: cat.id, children: cat.name }, cat.id))
2430
+ ]
2161
2431
  }
2162
- )
2432
+ ),
2433
+ seatmapCore.isStageSection(selectedSection) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-label", children: "Stage does not use pricing category." })
2163
2434
  ] })
2164
2435
  ] }),
2165
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginBottom: 10 }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
2166
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: labelStyle, children: [
2167
- "Rows (",
2168
- selectedSection.rows.length,
2169
- ") \xB7",
2170
- " ",
2171
- selectedSection.rows.reduce((t, r) => t + r.seats.length, 0),
2172
- " seats"
2173
- ] }),
2174
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => addSingleSeat(selectedSection.id), style: btnSmall, title: "Add a single seat to the last row", children: "+ Seat" })
2175
- ] }) }),
2176
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { maxHeight: 220, overflowY: "auto" }, children: selectedSection.rows.map((row) => /* @__PURE__ */ jsxRuntime.jsxs(
2177
- "div",
2178
- {
2179
- style: {
2180
- display: "flex",
2181
- alignItems: "center",
2182
- gap: 6,
2183
- padding: "3px 6px",
2184
- borderRadius: 4,
2185
- marginBottom: 2,
2186
- background: "#2a2a4a",
2187
- fontSize: 12,
2188
- fontFamily: "system-ui",
2189
- color: "#e0e0e0"
2436
+ seatmapCore.isStageSection(selectedSection) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-section seatmap-editor__panel-section--card seatmap-editor__panel-muted", children: "Stage areas do not support rows or seats." }) : seatmapCore.isDancefloorSection(selectedSection) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-section seatmap-editor__panel-section--card seatmap-editor__panel-muted", children: "Dancefloor works as one selectable area seat. Resize the section shape to adjust its footprint." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2437
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-section", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section-header", children: [
2438
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-label", children: [
2439
+ "Rows (",
2440
+ selectedSection.rows.length,
2441
+ ") \xB7",
2442
+ " ",
2443
+ selectedSection.rows.reduce((t, r) => t + r.seats.length, 0),
2444
+ " seats"
2445
+ ] }),
2446
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => addSingleSeat(selectedSection.id), className: "seatmap-editor__panel-button", title: "Add a single seat to the last row", children: "+ Seat" })
2447
+ ] }) }),
2448
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-list seatmap-editor__panel-scroll seatmap-editor__panel-scroll--md", children: selectedSection.rows.map((row) => /* @__PURE__ */ jsxRuntime.jsxs(
2449
+ "div",
2450
+ {
2451
+ className: "seatmap-editor__panel-list-item",
2452
+ children: [
2453
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "seatmap-editor__panel-text seatmap-editor__panel-text--strong seatmap-editor__panel-text--mono-min", children: [
2454
+ "Row ",
2455
+ row.label
2456
+ ] }),
2457
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "seatmap-editor__panel-muted seatmap-editor__panel-content-grow", children: [
2458
+ row.seats.length,
2459
+ " seats"
2460
+ ] }),
2461
+ /* @__PURE__ */ jsxRuntime.jsx(
2462
+ "button",
2463
+ {
2464
+ onClick: () => deleteRow(selectedSection.id, row.id),
2465
+ className: "seatmap-editor__panel-button seatmap-editor__panel-button--danger seatmap-editor__panel-button--tiny",
2466
+ title: `Delete row ${row.label}`,
2467
+ children: "\u2715"
2468
+ }
2469
+ )
2470
+ ]
2190
2471
  },
2191
- children: [
2192
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 600, minWidth: 24 }, children: [
2193
- "Row ",
2194
- row.label
2195
- ] }),
2196
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { flex: 1, color: "#9e9e9e" }, children: [
2197
- row.seats.length,
2198
- " seats"
2199
- ] }),
2200
- /* @__PURE__ */ jsxRuntime.jsx(
2201
- "button",
2202
- {
2203
- onClick: () => deleteRow(selectedSection.id, row.id),
2204
- style: { ...btnDanger, padding: "1px 5px", fontSize: 11 },
2205
- title: `Delete row ${row.label}`,
2206
- children: "\u2715"
2207
- }
2208
- )
2209
- ]
2210
- },
2211
- row.id
2212
- )) })
2472
+ row.id
2473
+ )) })
2474
+ ] })
2213
2475
  ] }, selectedSection.id))
2214
2476
  ] }),
2215
2477
  selectedSections.length === 0 && selectedSeatIds.size === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2216
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, color: "#e0e0e0", fontSize: 14, fontFamily: "system-ui", marginBottom: 12 }, children: "Venue Config" }),
2217
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 10 }, children: [
2218
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle, children: "Venue Name" }),
2478
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-title", children: "Venue Config" }),
2479
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section", children: [
2480
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-label", children: "Venue Name" }),
2219
2481
  /* @__PURE__ */ jsxRuntime.jsx(
2220
2482
  "input",
2221
2483
  {
2222
- style: inputStyle,
2484
+ className: "seatmap-editor__panel-input",
2223
2485
  value: venue.name,
2224
2486
  onChange: (e) => updateVenueName(e.target.value)
2225
2487
  }
2226
2488
  )
2227
2489
  ] }),
2228
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 16 }, children: [
2229
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle, children: "Venue ID" }),
2490
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section", children: [
2491
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-label", children: "Venue ID" }),
2230
2492
  /* @__PURE__ */ jsxRuntime.jsx(
2231
2493
  "input",
2232
2494
  {
2233
- style: inputStyle,
2495
+ className: "seatmap-editor__panel-input",
2234
2496
  value: venue.id,
2235
2497
  onChange: (e) => updateVenueId(e.target.value)
2236
2498
  }
2237
2499
  )
2238
2500
  ] }),
2239
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "#9e9e9e", fontSize: 11, fontFamily: "system-ui", marginBottom: 12 }, children: [
2501
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-muted seatmap-editor__panel-muted--small", children: [
2240
2502
  "Stats: ",
2241
2503
  venue.sections.length,
2242
2504
  " sections \xB7",
@@ -2244,31 +2506,11 @@ function PropertyPanel({
2244
2506
  venue.sections.reduce((t, s) => t + s.rows.reduce((rt, r) => rt + r.seats.length, 0), 0),
2245
2507
  " seats"
2246
2508
  ] }),
2247
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 1, background: "#2a2a4a", margin: "14px 0" } }),
2248
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle, children: "Background Image" }),
2249
- venue.backgroundImage ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 6 }, children: [
2250
- /* @__PURE__ */ jsxRuntime.jsx(
2251
- "div",
2252
- {
2253
- style: {
2254
- width: "100%",
2255
- height: 80,
2256
- borderRadius: 4,
2257
- border: "1px solid #3a3a5a",
2258
- overflow: "hidden",
2259
- marginBottom: 8
2260
- },
2261
- children: /* @__PURE__ */ jsxRuntime.jsx(
2262
- "img",
2263
- {
2264
- src: venue.backgroundImage,
2265
- alt: "Background",
2266
- style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
2267
- }
2268
- )
2269
- }
2270
- ),
2271
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...labelStyle, marginBottom: 4 }, children: [
2509
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-divider" }),
2510
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-label", children: "Background Image" }),
2511
+ venue.backgroundImage ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-section", children: [
2512
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-img-preview", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: venue.backgroundImage, alt: "Background" }) }),
2513
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-label", children: [
2272
2514
  "Opacity: ",
2273
2515
  Math.round((venue.backgroundImageOpacity ?? 0.5) * 100),
2274
2516
  "%"
@@ -2281,12 +2523,12 @@ function PropertyPanel({
2281
2523
  max: 100,
2282
2524
  value: Math.round((venue.backgroundImageOpacity ?? 0.5) * 100),
2283
2525
  onChange: (e) => onBackgroundOpacityChange?.(parseInt(e.target.value) / 100),
2284
- style: { width: "100%", accentColor: "#6a6aaa", cursor: "pointer" }
2526
+ className: "seatmap-editor__panel-range"
2285
2527
  }
2286
2528
  ),
2287
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, marginTop: 10 }, children: [
2288
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1 }, children: [
2289
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle, children: "Width" }),
2529
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-grid-2", children: [
2530
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2531
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-label", children: "Width" }),
2290
2532
  /* @__PURE__ */ jsxRuntime.jsx(
2291
2533
  "input",
2292
2534
  {
@@ -2297,12 +2539,12 @@ function PropertyPanel({
2297
2539
  onChange: (e) => onBackgroundSizeChange?.({
2298
2540
  width: Math.max(1, Number.parseInt(e.target.value, 10) || 1)
2299
2541
  }),
2300
- style: inputStyle
2542
+ className: "seatmap-editor__panel-input"
2301
2543
  }
2302
2544
  )
2303
2545
  ] }),
2304
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1 }, children: [
2305
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: labelStyle, children: "Height" }),
2546
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2547
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-label", children: "Height" }),
2306
2548
  /* @__PURE__ */ jsxRuntime.jsx(
2307
2549
  "input",
2308
2550
  {
@@ -2313,68 +2555,44 @@ function PropertyPanel({
2313
2555
  onChange: (e) => onBackgroundSizeChange?.({
2314
2556
  height: Math.max(1, Number.parseInt(e.target.value, 10) || 1)
2315
2557
  }),
2316
- style: inputStyle
2558
+ className: "seatmap-editor__panel-input"
2317
2559
  }
2318
2560
  )
2319
2561
  ] })
2320
2562
  ] }),
2321
- /* @__PURE__ */ jsxRuntime.jsxs(
2322
- "label",
2323
- {
2324
- style: {
2325
- display: "flex",
2326
- alignItems: "center",
2327
- gap: 8,
2328
- marginTop: 8,
2329
- color: "#c7c7df",
2330
- fontSize: 12,
2331
- fontFamily: "system-ui",
2332
- cursor: "pointer"
2333
- },
2334
- children: [
2335
- /* @__PURE__ */ jsxRuntime.jsx(
2336
- "input",
2337
- {
2338
- type: "checkbox",
2339
- checked: venue.backgroundImageKeepAspectRatio ?? true,
2340
- onChange: (e) => onBackgroundKeepAspectRatioChange?.(e.target.checked)
2341
- }
2342
- ),
2343
- "Keep aspect ratio"
2344
- ]
2345
- }
2346
- ),
2347
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, marginTop: 8 }, children: [
2348
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onUploadBackground, style: btnSmall, children: "Replace" }),
2349
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onRemoveBackground, style: btnDanger, children: "Remove" })
2563
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "seatmap-editor__panel-row seatmap-editor__panel-text", children: [
2564
+ /* @__PURE__ */ jsxRuntime.jsx(
2565
+ "input",
2566
+ {
2567
+ type: "checkbox",
2568
+ className: "seatmap-editor__panel-checkbox",
2569
+ checked: venue.backgroundImageKeepAspectRatio ?? true,
2570
+ onChange: (e) => onBackgroundKeepAspectRatioChange?.(e.target.checked)
2571
+ }
2572
+ ),
2573
+ "Keep aspect ratio"
2574
+ ] }),
2575
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-row", children: [
2576
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onUploadBackground, className: "seatmap-editor__panel-button", children: "Replace" }),
2577
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onRemoveBackground, className: "seatmap-editor__panel-button seatmap-editor__panel-button--danger", children: "Remove" })
2350
2578
  ] })
2351
2579
  ] }) : /* @__PURE__ */ jsxRuntime.jsx(
2352
2580
  "button",
2353
2581
  {
2354
2582
  onClick: onUploadBackground,
2355
- style: { ...btnSmall, marginTop: 6, width: "100%" },
2583
+ className: "seatmap-editor__panel-button seatmap-editor__panel-button--full",
2356
2584
  children: "Upload Image"
2357
2585
  }
2358
2586
  )
2359
2587
  ] })
2360
2588
  ] });
2361
2589
  }
2362
- var btnSmall2 = {
2363
- padding: "3px 8px",
2364
- border: "1px solid #3a3a5a",
2365
- borderRadius: 4,
2366
- background: "#2a2a4a",
2367
- color: "#e0e0e0",
2368
- cursor: "pointer",
2369
- fontSize: 12,
2370
- fontFamily: "system-ui"
2371
- };
2372
2590
  function replaceCategoryInVenue(venue, categoryId, replacementCategoryId) {
2373
2591
  return {
2374
2592
  ...venue,
2375
2593
  sections: venue.sections.map((section) => ({
2376
2594
  ...section,
2377
- categoryId: section.categoryId === categoryId ? replacementCategoryId : section.categoryId,
2595
+ categoryId: seatmapCore.isStageSection(section) ? "" : section.categoryId === categoryId ? replacementCategoryId : section.categoryId,
2378
2596
  rows: section.rows.map((row) => ({
2379
2597
  ...row,
2380
2598
  seats: row.seats.map(
@@ -2399,20 +2617,25 @@ function CategoryManager({
2399
2617
  history,
2400
2618
  store,
2401
2619
  fetchCategoryPrices,
2402
- style
2620
+ style,
2621
+ locale = "en-US",
2622
+ currency = "BYN"
2403
2623
  }) {
2404
2624
  const [newName, setNewName] = react.useState("");
2405
- const [newColor, setNewColor] = react.useState("#4caf50");
2625
+ const [newColor, setNewColor] = react.useState("#dfcd72");
2406
2626
  const [editingId, setEditingId] = react.useState(null);
2407
2627
  const [editingName, setEditingName] = react.useState("");
2408
- const [editingColor, setEditingColor] = react.useState("#4caf50");
2628
+ const [editingColor, setEditingColor] = react.useState("#dfcd72");
2409
2629
  const [isPriceManagerOpen, setIsPriceManagerOpen] = react.useState(false);
2410
2630
  const [isFetchingPrices, setIsFetchingPrices] = react.useState(false);
2411
2631
  const [fetchError, setFetchError] = react.useState(null);
2412
2632
  const [syncStatus, setSyncStatus] = react.useState("not-synced");
2413
2633
  const [overridePriceDrafts, setOverridePriceDrafts] = react.useState({});
2414
2634
  if (!venue) return null;
2415
- const formatPrice = (price) => `$${(Number.isFinite(price) ? price : 0).toFixed(2)}`;
2635
+ const formatPrice = (price) => new Intl.NumberFormat(locale, {
2636
+ style: "currency",
2637
+ currency
2638
+ }).format(Number.isFinite(price) ? price : 0);
2416
2639
  const effectivePrice = (category) => {
2417
2640
  if (category.isPriceOverridden && Number.isFinite(category.overriddenPrice)) {
2418
2641
  return category.overriddenPrice;
@@ -2546,7 +2769,7 @@ function CategoryManager({
2546
2769
  });
2547
2770
  setSyncStatus("synced");
2548
2771
  } catch (error) {
2549
- const message = error instanceof Error ? error.message : "Failed to fetch prices.";
2772
+ const message = error instanceof Error ? error.message : "Failed to load prices.";
2550
2773
  setFetchError(message);
2551
2774
  const currentVenue = store.getState().venue;
2552
2775
  if (currentVenue) {
@@ -2647,92 +2870,57 @@ function CategoryManager({
2647
2870
  };
2648
2871
  });
2649
2872
  };
2650
- const switchTrackBase = {
2651
- width: 34,
2652
- height: 20,
2653
- borderRadius: 999,
2654
- border: "1px solid #4a4a6a",
2655
- padding: 2,
2656
- display: "inline-flex",
2657
- alignItems: "center",
2658
- transition: "all 0.12s ease"
2659
- };
2660
- const switchThumbBase = {
2661
- width: 14,
2662
- height: 14,
2663
- borderRadius: "50%",
2664
- background: "#e0e0e0",
2665
- transition: "transform 0.12s ease"
2666
- };
2667
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: 16, ...style }, children: [
2668
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, color: "#e0e0e0", fontSize: 14, fontFamily: "system-ui", marginBottom: 12 }, children: "Pricing Categories" }),
2873
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel", style, children: [
2874
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-title", children: "Pricing categories" }),
2669
2875
  venue.categories.map((cat) => {
2670
2876
  const isEditing = editingId === cat.id;
2671
2877
  return /* @__PURE__ */ jsxRuntime.jsxs(
2672
2878
  "div",
2673
2879
  {
2674
- style: {
2675
- display: "flex",
2676
- alignItems: "center",
2677
- gap: 8,
2678
- flexWrap: "wrap",
2679
- marginBottom: 6,
2680
- padding: "4px 8px",
2681
- borderRadius: 4,
2682
- background: "#2a2a4a"
2683
- },
2880
+ className: "seatmap-editor__panel-list-item",
2684
2881
  children: [
2685
- /* @__PURE__ */ jsxRuntime.jsx(
2686
- "input",
2687
- {
2688
- type: "color",
2689
- value: isEditing ? editingColor : cat.color,
2690
- onChange: (e) => isEditing && setEditingColor(e.target.value),
2691
- disabled: !isEditing,
2692
- style: { width: 18, height: 18, border: "none", padding: 0, cursor: isEditing ? "pointer" : "default" }
2693
- }
2694
- ),
2882
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "seatmap-editor__color-picker-shell", children: [
2883
+ /* @__PURE__ */ jsxRuntime.jsx(
2884
+ "span",
2885
+ {
2886
+ "aria-hidden": "true",
2887
+ className: "seatmap-editor__color-picker-dot",
2888
+ style: { background: isEditing ? editingColor : cat.color }
2889
+ }
2890
+ ),
2891
+ /* @__PURE__ */ jsxRuntime.jsx(
2892
+ "input",
2893
+ {
2894
+ type: "color",
2895
+ value: isEditing ? editingColor : cat.color,
2896
+ onChange: (e) => isEditing && setEditingColor(e.target.value),
2897
+ disabled: !isEditing,
2898
+ className: "seatmap-editor__color-picker-input",
2899
+ "data-editable": isEditing ? "true" : "false",
2900
+ title: isEditing ? "Pick category color" : "Enable edit mode to change color"
2901
+ }
2902
+ )
2903
+ ] }),
2695
2904
  isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
2696
2905
  "input",
2697
2906
  {
2698
2907
  value: editingName,
2699
2908
  onChange: (e) => setEditingName(e.target.value),
2700
2909
  onKeyDown: (e) => e.key === "Enter" && saveEdit(),
2701
- style: {
2702
- flex: 1,
2703
- minWidth: 0,
2704
- padding: "2px 6px",
2705
- background: "#1f1f38",
2706
- border: "1px solid #3a3a5a",
2707
- borderRadius: 4,
2708
- color: "#e0e0e0",
2709
- fontSize: 12,
2710
- fontFamily: "system-ui"
2711
- }
2910
+ className: "seatmap-editor__panel-input seatmap-editor__panel-input--grow"
2712
2911
  }
2713
- ) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, color: "#e0e0e0", fontSize: 13, fontFamily: "system-ui" }, children: cat.name }),
2912
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-text seatmap-editor__panel-text--truncate", children: cat.name }),
2714
2913
  isEditing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2715
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: saveEdit, style: { ...btnSmall2, padding: "1px 6px", fontSize: 11 }, children: "Save" }),
2716
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setEditingId(null), style: { ...btnSmall2, padding: "1px 6px", fontSize: 11 }, children: "Cancel" })
2914
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: saveEdit, className: "seatmap-editor__panel-button seatmap-editor__panel-button--tiny", children: "Save" }),
2915
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setEditingId(null), className: "seatmap-editor__panel-button seatmap-editor__panel-button--tiny", children: "Cancel" })
2717
2916
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2718
- /* @__PURE__ */ jsxRuntime.jsx(
2719
- "span",
2720
- {
2721
- style: {
2722
- color: "#8f8fa4",
2723
- fontSize: 12,
2724
- fontFamily: "system-ui",
2725
- marginRight: 2
2726
- },
2727
- children: formatPrice(effectivePrice(cat))
2728
- }
2729
- ),
2730
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => startEdit(cat), style: { ...btnSmall2, padding: "1px 6px", fontSize: 11 }, children: "Edit" }),
2917
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__panel-muted seatmap-editor__panel-price", children: formatPrice(effectivePrice(cat)) }),
2918
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => startEdit(cat), className: "seatmap-editor__panel-button seatmap-editor__panel-button--tiny", children: "Edit" }),
2731
2919
  /* @__PURE__ */ jsxRuntime.jsx(
2732
2920
  "button",
2733
2921
  {
2734
2922
  onClick: () => removeCategory(cat.id),
2735
- style: { ...btnSmall2, padding: "1px 6px", fontSize: 11 },
2923
+ className: "seatmap-editor__panel-button seatmap-editor__panel-button--tiny",
2736
2924
  disabled: venue.categories.length <= 1,
2737
2925
  title: venue.categories.length <= 1 ? "At least one category is required" : "Delete category",
2738
2926
  children: "\u2715"
@@ -2744,16 +2932,21 @@ function CategoryManager({
2744
2932
  cat.id
2745
2933
  );
2746
2934
  }),
2747
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, marginTop: 10, alignItems: "center" }, children: [
2748
- /* @__PURE__ */ jsxRuntime.jsx(
2749
- "input",
2750
- {
2751
- type: "color",
2752
- value: newColor,
2753
- onChange: (e) => setNewColor(e.target.value),
2754
- style: { width: 28, height: 28, border: "none", padding: 0, cursor: "pointer" }
2755
- }
2756
- ),
2935
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-row seatmap-editor__panel-row--spaced", children: [
2936
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "seatmap-editor__color-picker-shell seatmap-editor__color-picker-shell--lg", children: [
2937
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "seatmap-editor__color-picker-dot", style: { background: newColor } }),
2938
+ /* @__PURE__ */ jsxRuntime.jsx(
2939
+ "input",
2940
+ {
2941
+ type: "color",
2942
+ value: newColor,
2943
+ onChange: (e) => setNewColor(e.target.value),
2944
+ className: "seatmap-editor__color-picker-input",
2945
+ "data-editable": "true",
2946
+ title: "Pick new category color"
2947
+ }
2948
+ )
2949
+ ] }),
2757
2950
  /* @__PURE__ */ jsxRuntime.jsx(
2758
2951
  "input",
2759
2952
  {
@@ -2761,300 +2954,158 @@ function CategoryManager({
2761
2954
  value: newName,
2762
2955
  onChange: (e) => setNewName(e.target.value),
2763
2956
  onKeyDown: (e) => e.key === "Enter" && addCategory(),
2764
- style: {
2765
- flex: 1,
2766
- padding: "4px 8px",
2767
- background: "#2a2a4a",
2768
- border: "1px solid #3a3a5a",
2769
- borderRadius: 4,
2770
- color: "#e0e0e0",
2771
- fontSize: 13,
2772
- fontFamily: "system-ui"
2773
- }
2957
+ className: "seatmap-editor__panel-input seatmap-editor__panel-input--grow"
2774
2958
  }
2775
2959
  ),
2776
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: addCategory, style: btnSmall2, children: "Add" })
2960
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: addCategory, className: "seatmap-editor__panel-button", children: "Add" })
2777
2961
  ] }),
2778
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: 10 }, children: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: openPriceManager, style: btnSmall2, children: "Manage Prices" }) }),
2962
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-actions-end", children: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: openPriceManager, className: "seatmap-editor__panel-button", children: "Manage prices" }) }),
2779
2963
  isPriceManagerOpen && /* @__PURE__ */ jsxRuntime.jsx(
2780
2964
  "div",
2781
2965
  {
2782
- style: {
2783
- position: "fixed",
2784
- inset: 0,
2785
- background: "rgba(8, 8, 18, 0.65)",
2786
- zIndex: 1e3,
2787
- display: "flex",
2788
- alignItems: "center",
2789
- justifyContent: "center",
2790
- padding: 16
2791
- },
2966
+ className: "seatmap-editor__modal-backdrop",
2792
2967
  onClick: () => setIsPriceManagerOpen(false),
2793
2968
  children: /* @__PURE__ */ jsxRuntime.jsxs(
2794
2969
  "div",
2795
2970
  {
2796
- style: {
2797
- width: "min(860px, 95vw)",
2798
- maxHeight: "80vh",
2799
- overflow: "auto",
2800
- background: "#17172b",
2801
- border: "1px solid #3a3a5a",
2802
- borderRadius: 10,
2803
- padding: 16
2804
- },
2971
+ className: "seatmap-editor__modal",
2805
2972
  onClick: (event) => event.stopPropagation(),
2806
2973
  children: [
2807
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, children: [
2808
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#e0e0e0", fontSize: 15, fontFamily: "system-ui", fontWeight: 600 }, children: "Category Pricing" }),
2809
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
2974
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__modal-header", children: [
2975
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-title", children: "Category prices" }),
2976
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__modal-actions", children: [
2810
2977
  /* @__PURE__ */ jsxRuntime.jsx(
2811
2978
  "button",
2812
2979
  {
2813
2980
  onClick: syncPricesFromBackend,
2814
2981
  disabled: !fetchCategoryPrices || isFetchingPrices,
2815
- style: {
2816
- ...btnSmall2,
2817
- opacity: !fetchCategoryPrices || isFetchingPrices ? 0.6 : 1,
2818
- cursor: !fetchCategoryPrices || isFetchingPrices ? "not-allowed" : "pointer"
2819
- },
2820
- title: fetchCategoryPrices ? "Fetch latest category prices from backend" : "No backend price fetch configured",
2821
- children: isFetchingPrices ? "Syncing..." : "Sync from Backend"
2982
+ className: "seatmap-editor__panel-button",
2983
+ title: fetchCategoryPrices ? "Load latest prices from backend" : "Backend price sync is not configured",
2984
+ children: isFetchingPrices ? "Syncing..." : "Sync with backend"
2822
2985
  }
2823
2986
  ),
2824
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setIsPriceManagerOpen(false), style: btnSmall2, children: "Close" })
2987
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setIsPriceManagerOpen(false), className: "seatmap-editor__panel-button", children: "Close" })
2825
2988
  ] })
2826
2989
  ] }),
2827
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#9e9eb4", fontSize: 12, fontFamily: "system-ui", marginBottom: 10 }, children: "Backend prices are read-only data. Override lets you temporarily use an overriden category price in this schema." }),
2990
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-muted seatmap-editor__panel-muted--spaced", children: "Backend prices are read-only. Override temporarily uses a custom category price for this seatmap." }),
2828
2991
  /* @__PURE__ */ jsxRuntime.jsxs(
2829
2992
  "div",
2830
2993
  {
2831
- style: {
2832
- marginBottom: 10,
2833
- color: syncStatus === "synced" ? "#8fd3a6" : syncStatus === "failed" ? "#ff9a9a" : "#b8b8cc",
2834
- fontSize: 12,
2835
- fontFamily: "system-ui"
2836
- },
2994
+ className: syncStatus === "synced" ? "seatmap-editor__status-line seatmap-editor__status-line--success" : syncStatus === "failed" ? "seatmap-editor__status-line seatmap-editor__status-line--error" : "seatmap-editor__status-line seatmap-editor__status-line--idle",
2837
2995
  children: [
2838
2996
  "Sync status:",
2839
2997
  " ",
2840
- syncStatus === "not-synced" ? "Not synced" : syncStatus === "syncing" ? "Syncing..." : syncStatus === "synced" ? "Synced" : "Failed"
2998
+ syncStatus === "not-synced" ? "Not synced" : syncStatus === "syncing" ? "Syncing..." : syncStatus === "synced" ? "Synced" : "Error"
2841
2999
  ]
2842
3000
  }
2843
3001
  ),
2844
- fetchError && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginBottom: 10, color: "#ff9a9a", fontSize: 12, fontFamily: "system-ui" }, children: fetchError }),
2845
- /* @__PURE__ */ jsxRuntime.jsxs(
2846
- "div",
2847
- {
2848
- style: {
2849
- display: "grid",
2850
- gridTemplateColumns: "minmax(160px, 1.6fr) minmax(92px, 0.9fr) minmax(88px, 0.8fr) minmax(188px, 1.4fr) minmax(96px, 0.9fr)",
2851
- gap: 8
2852
- },
2853
- children: [
2854
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#9e9eb4", fontSize: 11, fontFamily: "system-ui" }, children: "Category" }),
2855
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#9e9eb4", fontSize: 11, fontFamily: "system-ui" }, children: "Backend" }),
2856
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#9e9eb4", fontSize: 11, fontFamily: "system-ui" }, children: "Override" }),
2857
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#9e9eb4", fontSize: 11, fontFamily: "system-ui" }, children: "Override Price" }),
2858
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#9e9eb4", fontSize: 11, fontFamily: "system-ui" }, children: "Effective Price" }),
2859
- venue.categories.map((category) => /* @__PURE__ */ jsxRuntime.jsxs(
2860
- "div",
2861
- {
2862
- style: {
2863
- gridColumn: "1 / -1",
2864
- display: "grid",
2865
- gridTemplateColumns: "minmax(160px, 1.6fr) minmax(92px, 0.9fr) minmax(88px, 0.8fr) minmax(188px, 1.4fr) minmax(96px, 0.9fr)",
2866
- gap: 8,
2867
- alignItems: "center",
2868
- padding: "8px 10px",
2869
- borderRadius: 6,
2870
- background: "#222242"
2871
- },
2872
- children: [
2873
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, minWidth: 0 }, children: [
2874
- /* @__PURE__ */ jsxRuntime.jsx(
2875
- "span",
2876
- {
2877
- style: {
2878
- width: 10,
2879
- height: 10,
2880
- borderRadius: 2,
2881
- background: category.color,
2882
- flexShrink: 0
2883
- }
3002
+ fetchError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__status-line seatmap-editor__status-line--error", children: fetchError }),
3003
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__table-grid", children: [
3004
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__table-head", children: "Category" }),
3005
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__table-head", children: "Backend" }),
3006
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__table-head", children: "Override" }),
3007
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__table-head", children: "Override price" }),
3008
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__table-head", children: "Effective price" }),
3009
+ venue.categories.map((category) => /* @__PURE__ */ jsxRuntime.jsxs(
3010
+ "div",
3011
+ {
3012
+ className: "seatmap-editor__table-row",
3013
+ children: [
3014
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__table-category-cell", children: [
3015
+ /* @__PURE__ */ jsxRuntime.jsx(
3016
+ "span",
3017
+ {
3018
+ className: "seatmap-editor__table-category-swatch",
3019
+ "aria-hidden": "true",
3020
+ style: { background: category.color }
3021
+ }
3022
+ ),
3023
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__table-category-name", children: category.name })
3024
+ ] }),
3025
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__table-amount", children: formatPrice(category.backendPrice) }),
3026
+ /* @__PURE__ */ jsxRuntime.jsx(
3027
+ "button",
3028
+ {
3029
+ type: "button",
3030
+ role: "switch",
3031
+ "aria-checked": Boolean(category.isPriceOverridden),
3032
+ onClick: () => toggleOverride(category.id, !category.isPriceOverridden),
3033
+ className: `seatmap-editor__switch-track${category.isPriceOverridden ? " is-checked" : ""}`,
3034
+ title: category.isPriceOverridden ? "Disable override" : "Enable override",
3035
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__switch-thumb" })
3036
+ }
3037
+ ),
3038
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__override-editor", children: [
3039
+ /* @__PURE__ */ jsxRuntime.jsx(
3040
+ "input",
3041
+ {
3042
+ type: "text",
3043
+ inputMode: "decimal",
3044
+ value: overridePriceDrafts[category.id] ?? "",
3045
+ disabled: !category.isPriceOverridden,
3046
+ onChange: (event) => setOverridePriceDrafts((current) => ({ ...current, [category.id]: event.target.value })),
3047
+ onKeyDown: (event) => {
3048
+ if (event.key === "Enter") {
3049
+ commitOverridePrice(category.id);
2884
3050
  }
2885
- ),
2886
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#e0e0e0", fontSize: 13, fontFamily: "system-ui" }, children: category.name })
2887
- ] }),
2888
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#c7c7df", fontSize: 12, fontFamily: "system-ui" }, children: formatPrice(category.backendPrice) }),
3051
+ if (event.key === "Escape") {
3052
+ resetOverrideDraft(category);
3053
+ }
3054
+ },
3055
+ className: `seatmap-editor__override-input${category.isPriceOverridden ? "" : " is-disabled"}`
3056
+ }
3057
+ ),
3058
+ category.isPriceOverridden && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__override-actions", children: [
2889
3059
  /* @__PURE__ */ jsxRuntime.jsx(
2890
3060
  "button",
2891
3061
  {
2892
3062
  type: "button",
2893
- role: "switch",
2894
- "aria-checked": Boolean(category.isPriceOverridden),
2895
- onClick: () => toggleOverride(category.id, !category.isPriceOverridden),
2896
- style: {
2897
- ...switchTrackBase,
2898
- background: category.isPriceOverridden ? "#2d6a3d" : "#2a2a4a",
2899
- borderColor: category.isPriceOverridden ? "#57b26f" : "#4a4a6a",
2900
- cursor: "pointer"
2901
- },
2902
- title: category.isPriceOverridden ? "Disable override" : "Enable override",
2903
- children: /* @__PURE__ */ jsxRuntime.jsx(
2904
- "span",
2905
- {
2906
- style: {
2907
- ...switchThumbBase,
2908
- transform: category.isPriceOverridden ? "translateX(14px)" : "translateX(0)"
2909
- }
2910
- }
2911
- )
3063
+ onClick: () => adjustOverrideDraft(category.id, 0.01),
3064
+ className: "seatmap-editor__table-action-button",
3065
+ title: "Increase override price",
3066
+ children: "+"
2912
3067
  }
2913
3068
  ),
2914
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", width: "100%", minWidth: 0, maxWidth: "100%", overflow: "hidden" }, children: [
2915
- /* @__PURE__ */ jsxRuntime.jsx(
2916
- "input",
2917
- {
2918
- type: "text",
2919
- inputMode: "decimal",
2920
- value: overridePriceDrafts[category.id] ?? "",
2921
- disabled: !category.isPriceOverridden,
2922
- onChange: (event) => setOverridePriceDrafts((current) => ({ ...current, [category.id]: event.target.value })),
2923
- onKeyDown: (event) => {
2924
- if (event.key === "Enter") {
2925
- commitOverridePrice(category.id);
2926
- }
2927
- if (event.key === "Escape") {
2928
- resetOverrideDraft(category);
2929
- }
2930
- },
2931
- style: {
2932
- padding: "4px 92px 4px 8px",
2933
- background: category.isPriceOverridden ? "#2a2a4a" : "#2a2a4a88",
2934
- border: "1px solid #3a3a5a",
2935
- borderRadius: 4,
2936
- color: category.isPriceOverridden ? "#e0e0e0" : "#8f8fa4",
2937
- fontSize: 12,
2938
- fontFamily: "system-ui",
2939
- width: "100%",
2940
- maxWidth: "100%",
2941
- boxSizing: "border-box",
2942
- MozAppearance: "textfield"
2943
- }
2944
- }
2945
- ),
2946
- category.isPriceOverridden && /* @__PURE__ */ jsxRuntime.jsxs(
2947
- "div",
2948
- {
2949
- style: {
2950
- position: "absolute",
2951
- right: 3,
2952
- top: "50%",
2953
- transform: "translateY(-50%)",
2954
- display: "inline-flex",
2955
- gap: 2
2956
- },
2957
- children: [
2958
- /* @__PURE__ */ jsxRuntime.jsx(
2959
- "button",
2960
- {
2961
- type: "button",
2962
- onClick: () => adjustOverrideDraft(category.id, 0.01),
2963
- style: {
2964
- width: 20,
2965
- height: 20,
2966
- borderRadius: 4,
2967
- border: "1px solid #4a4a6a",
2968
- background: "#2a2a4a",
2969
- color: "#d8d8ee",
2970
- fontSize: 12,
2971
- lineHeight: 1,
2972
- padding: 0,
2973
- cursor: "pointer"
2974
- },
2975
- title: "Increase override price",
2976
- children: "+"
2977
- }
2978
- ),
2979
- /* @__PURE__ */ jsxRuntime.jsx(
2980
- "button",
2981
- {
2982
- type: "button",
2983
- onClick: () => adjustOverrideDraft(category.id, -0.01),
2984
- style: {
2985
- width: 20,
2986
- height: 20,
2987
- borderRadius: 4,
2988
- border: "1px solid #4a4a6a",
2989
- background: "#2a2a4a",
2990
- color: "#d8d8ee",
2991
- fontSize: 12,
2992
- lineHeight: 1,
2993
- padding: 0,
2994
- cursor: "pointer"
2995
- },
2996
- title: "Decrease override price",
2997
- children: "-"
2998
- }
2999
- ),
3000
- /* @__PURE__ */ jsxRuntime.jsx(
3001
- "button",
3002
- {
3003
- type: "button",
3004
- onClick: () => commitOverridePrice(category.id),
3005
- disabled: !isDraftChanged(category),
3006
- style: {
3007
- width: 20,
3008
- height: 20,
3009
- borderRadius: 4,
3010
- border: "1px solid #2f7b44",
3011
- background: isDraftChanged(category) ? "#2d6a3d" : "#244832",
3012
- color: "#d8ffe4",
3013
- fontSize: 11,
3014
- lineHeight: 1,
3015
- padding: 0,
3016
- opacity: isDraftChanged(category) ? 1 : 0.55,
3017
- cursor: isDraftChanged(category) ? "pointer" : "not-allowed"
3018
- },
3019
- title: "Apply override price",
3020
- children: "\u2713"
3021
- }
3022
- ),
3023
- /* @__PURE__ */ jsxRuntime.jsx(
3024
- "button",
3025
- {
3026
- type: "button",
3027
- onClick: () => resetOverrideDraft(category),
3028
- disabled: !isDraftChanged(category),
3029
- style: {
3030
- width: 20,
3031
- height: 20,
3032
- borderRadius: 4,
3033
- border: "1px solid #8b3f4d",
3034
- background: isDraftChanged(category) ? "#6f2c3b" : "#4d2730",
3035
- color: "#ffd9df",
3036
- fontSize: 11,
3037
- lineHeight: 1,
3038
- padding: 0,
3039
- opacity: isDraftChanged(category) ? 1 : 0.55,
3040
- cursor: isDraftChanged(category) ? "pointer" : "not-allowed"
3041
- },
3042
- title: "Cancel override price changes",
3043
- children: "\u2715"
3044
- }
3045
- )
3046
- ]
3047
- }
3048
- )
3049
- ] }),
3050
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#e0e0e0", fontSize: 12, fontFamily: "system-ui", fontWeight: 600, whiteSpace: "nowrap" }, children: formatPrice(effectivePrice(category)) })
3051
- ]
3052
- },
3053
- category.id
3054
- ))
3055
- ]
3056
- }
3057
- )
3069
+ /* @__PURE__ */ jsxRuntime.jsx(
3070
+ "button",
3071
+ {
3072
+ type: "button",
3073
+ onClick: () => adjustOverrideDraft(category.id, -0.01),
3074
+ className: "seatmap-editor__table-action-button",
3075
+ title: "Decrease override price",
3076
+ children: "-"
3077
+ }
3078
+ ),
3079
+ /* @__PURE__ */ jsxRuntime.jsx(
3080
+ "button",
3081
+ {
3082
+ type: "button",
3083
+ onClick: () => commitOverridePrice(category.id),
3084
+ disabled: !isDraftChanged(category),
3085
+ className: "seatmap-editor__table-action-button seatmap-editor__table-action-button--apply",
3086
+ title: "Apply override price",
3087
+ children: "\u2713"
3088
+ }
3089
+ ),
3090
+ /* @__PURE__ */ jsxRuntime.jsx(
3091
+ "button",
3092
+ {
3093
+ type: "button",
3094
+ onClick: () => resetOverrideDraft(category),
3095
+ disabled: !isDraftChanged(category),
3096
+ className: "seatmap-editor__table-action-button seatmap-editor__table-action-button--reset",
3097
+ title: "Revert override changes",
3098
+ children: "\u2715"
3099
+ }
3100
+ )
3101
+ ] })
3102
+ ] }),
3103
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__table-effective-price", children: formatPrice(effectivePrice(category)) })
3104
+ ]
3105
+ },
3106
+ category.id
3107
+ ))
3108
+ ] })
3058
3109
  ]
3059
3110
  }
3060
3111
  )
@@ -3079,81 +3130,57 @@ function LayerPanel({
3079
3130
  return null;
3080
3131
  };
3081
3132
  const selectedSectionIdFromSeats = selectedSeatIds.size > 0 ? findSectionForSeat([...selectedSeatIds][0]) : null;
3082
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: 16, ...style }, children: [
3083
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, color: "#e0e0e0", fontSize: 14, fontFamily: "system-ui", marginBottom: 12 }, children: "Layers" }),
3084
- venue.sections.map((section) => {
3085
- const seatCount = section.rows.reduce((t, r) => t + r.seats.length, 0);
3086
- const isActive = storeSelectedSectionIds.has(section.id) || section.id === selectedSectionIdFromSeats;
3087
- const catColor = venue.categories.find((c) => c.id === section.categoryId)?.color ?? "#666";
3088
- return /* @__PURE__ */ jsxRuntime.jsxs(
3089
- "div",
3090
- {
3091
- onClick: (event) => onSelectSection(section.id, {
3092
- multi: event.ctrlKey || event.metaKey
3093
- }),
3094
- style: {
3095
- display: "flex",
3096
- alignItems: "center",
3097
- gap: 8,
3098
- padding: "6px 8px",
3099
- borderRadius: 4,
3100
- marginBottom: 2,
3101
- cursor: "pointer",
3102
- background: isActive ? "#3a3a5a" : "transparent"
3103
- },
3104
- children: [
3105
- /* @__PURE__ */ jsxRuntime.jsx(
3106
- "div",
3107
- {
3108
- style: {
3109
- width: 10,
3110
- height: 10,
3111
- borderRadius: 2,
3112
- background: catColor,
3113
- flexShrink: 0
3114
- }
3115
- }
3116
- ),
3117
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
3118
- /* @__PURE__ */ jsxRuntime.jsx(
3119
- "div",
3120
- {
3121
- style: {
3122
- color: "#e0e0e0",
3123
- fontSize: 13,
3124
- fontFamily: "system-ui",
3125
- whiteSpace: "nowrap",
3126
- overflow: "hidden",
3127
- textOverflow: "ellipsis"
3128
- },
3129
- children: section.label
3130
- }
3131
- ),
3132
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "#9e9e9e", fontSize: 11, fontFamily: "system-ui" }, children: [
3133
- section.rows.length,
3134
- " rows, ",
3135
- seatCount,
3136
- " seats"
3137
- ] })
3138
- ] })
3139
- ]
3140
- },
3141
- section.id
3142
- );
3143
- }),
3144
- venue.sections.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#9e9e9e", fontSize: 13, fontFamily: "system-ui" }, children: "No sections yet. Use the Add Section tool." })
3133
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel", style, children: [
3134
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-title", children: "Layers" }),
3135
+ /* @__PURE__ */ jsxRuntime.jsx(
3136
+ "div",
3137
+ {
3138
+ className: "seatmap-editor__panel-list",
3139
+ role: "listbox",
3140
+ "aria-label": "Venue sections",
3141
+ "aria-multiselectable": "true",
3142
+ children: venue.sections.map((section) => {
3143
+ const seatCount = section.rows.reduce((t, r) => t + r.seats.length, 0);
3144
+ const isActive = storeSelectedSectionIds.has(section.id) || section.id === selectedSectionIdFromSeats;
3145
+ const catColor = venue.categories.find((c) => c.id === section.categoryId)?.color ?? "var(--seatmap-editor-text-muted, #666)";
3146
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3147
+ "button",
3148
+ {
3149
+ type: "button",
3150
+ role: "option",
3151
+ "aria-selected": isActive,
3152
+ onClick: (event) => onSelectSection(section.id, {
3153
+ multi: event.ctrlKey || event.metaKey
3154
+ }),
3155
+ className: `seatmap-editor__panel-list-item seatmap-editor__panel-list-item--interactive seatmap-editor__panel-list-item--interactive-button${isActive ? " is-active" : ""}`,
3156
+ children: [
3157
+ /* @__PURE__ */ jsxRuntime.jsx(
3158
+ "div",
3159
+ {
3160
+ className: "seatmap-editor__table-category-swatch",
3161
+ "aria-hidden": "true",
3162
+ style: { background: catColor }
3163
+ }
3164
+ ),
3165
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-content-grow", children: [
3166
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-text seatmap-editor__panel-text--truncate", children: section.label }),
3167
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-muted seatmap-editor__panel-muted--small", children: [
3168
+ section.rows.length,
3169
+ " rows, ",
3170
+ seatCount,
3171
+ " seats"
3172
+ ] })
3173
+ ] })
3174
+ ]
3175
+ },
3176
+ section.id
3177
+ );
3178
+ })
3179
+ }
3180
+ ),
3181
+ venue.sections.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-muted", children: "No sections yet. Use the Add Section tool." })
3145
3182
  ] });
3146
3183
  }
3147
- var btnSmall3 = {
3148
- padding: "3px 8px",
3149
- border: "1px solid #3a3a5a",
3150
- borderRadius: 4,
3151
- background: "#2a2a4a",
3152
- color: "#e0e0e0",
3153
- cursor: "pointer",
3154
- fontSize: 12,
3155
- fontFamily: "system-ui"
3156
- };
3157
3184
  function sanitizeStatusId(name) {
3158
3185
  const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3159
3186
  return slug || seatmapCore.generateId();
@@ -3180,10 +3207,10 @@ function replaceStatusInVenue(venue, statusId, replacementStatusId) {
3180
3207
  }
3181
3208
  function StatusManager({ venue, history, store, style }) {
3182
3209
  const [newName, setNewName] = react.useState("");
3183
- const [newColor, setNewColor] = react.useState("#4caf50");
3210
+ const [newColor, setNewColor] = react.useState("#dfcd72");
3184
3211
  const [editingId, setEditingId] = react.useState(null);
3185
3212
  const [editingName, setEditingName] = react.useState("");
3186
- const [editingColor, setEditingColor] = react.useState("#4caf50");
3213
+ const [editingColor, setEditingColor] = react.useState("#dfcd72");
3187
3214
  const statusIds = react.useMemo(() => new Set(venue?.seatStatuses.map((status) => status.id) ?? []), [venue]);
3188
3215
  if (!venue) return null;
3189
3216
  const addStatus = () => {
@@ -3276,63 +3303,56 @@ function StatusManager({ venue, history, store, style }) {
3276
3303
  }
3277
3304
  });
3278
3305
  };
3279
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: 16, ...style }, children: [
3280
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, color: "#e0e0e0", fontSize: 14, fontFamily: "system-ui", marginBottom: 12 }, children: "Seat Statuses" }),
3306
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel", style, children: [
3307
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-title", children: "Seat Statuses" }),
3281
3308
  venue.seatStatuses.map((status) => {
3282
3309
  const isEditing = editingId === status.id;
3283
3310
  return /* @__PURE__ */ jsxRuntime.jsxs(
3284
3311
  "div",
3285
3312
  {
3286
- style: {
3287
- display: "flex",
3288
- alignItems: "center",
3289
- gap: 8,
3290
- flexWrap: "wrap",
3291
- marginBottom: 6,
3292
- padding: "4px 8px",
3293
- borderRadius: 4,
3294
- background: "#2a2a4a"
3295
- },
3313
+ className: "seatmap-editor__panel-list-item",
3296
3314
  children: [
3297
- /* @__PURE__ */ jsxRuntime.jsx(
3298
- "input",
3299
- {
3300
- type: "color",
3301
- value: isEditing ? editingColor : status.color,
3302
- onChange: (e) => isEditing && setEditingColor(e.target.value),
3303
- disabled: !isEditing,
3304
- style: { width: 18, height: 18, border: "none", padding: 0, cursor: isEditing ? "pointer" : "default" }
3305
- }
3306
- ),
3315
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "seatmap-editor__color-picker-shell", children: [
3316
+ /* @__PURE__ */ jsxRuntime.jsx(
3317
+ "span",
3318
+ {
3319
+ "aria-hidden": "true",
3320
+ className: "seatmap-editor__color-picker-dot",
3321
+ style: { background: isEditing ? editingColor : status.color }
3322
+ }
3323
+ ),
3324
+ /* @__PURE__ */ jsxRuntime.jsx(
3325
+ "input",
3326
+ {
3327
+ type: "color",
3328
+ value: isEditing ? editingColor : status.color,
3329
+ onChange: (e) => isEditing && setEditingColor(e.target.value),
3330
+ disabled: !isEditing,
3331
+ className: "seatmap-editor__color-picker-input",
3332
+ "data-editable": isEditing ? "true" : "false",
3333
+ title: isEditing ? "Pick status color" : "Enable edit to change color"
3334
+ }
3335
+ )
3336
+ ] }),
3307
3337
  isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
3308
3338
  "input",
3309
3339
  {
3310
3340
  value: editingName,
3311
3341
  onChange: (e) => setEditingName(e.target.value),
3312
3342
  onKeyDown: (e) => e.key === "Enter" && saveEdit(),
3313
- style: {
3314
- flex: 1,
3315
- minWidth: 0,
3316
- padding: "2px 6px",
3317
- background: "#1f1f38",
3318
- border: "1px solid #3a3a5a",
3319
- borderRadius: 4,
3320
- color: "#e0e0e0",
3321
- fontSize: 12,
3322
- fontFamily: "system-ui"
3323
- }
3343
+ className: "seatmap-editor__panel-input seatmap-editor__panel-input--grow"
3324
3344
  }
3325
- ) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, color: "#e0e0e0", fontSize: 13, fontFamily: "system-ui" }, children: status.name }),
3345
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__panel-text seatmap-editor__panel-content-grow", children: status.name }),
3326
3346
  isEditing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3327
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: saveEdit, style: { ...btnSmall3, padding: "1px 6px", fontSize: 11 }, children: "Save" }),
3328
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setEditingId(null), style: { ...btnSmall3, padding: "1px 6px", fontSize: 11 }, children: "Cancel" })
3347
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: saveEdit, className: "seatmap-editor__panel-button seatmap-editor__panel-button--tiny", children: "Save" }),
3348
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setEditingId(null), className: "seatmap-editor__panel-button seatmap-editor__panel-button--tiny", children: "Cancel" })
3329
3349
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3330
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => startEdit(status), style: { ...btnSmall3, padding: "1px 6px", fontSize: 11 }, children: "Edit" }),
3350
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => startEdit(status), className: "seatmap-editor__panel-button seatmap-editor__panel-button--tiny", children: "Edit" }),
3331
3351
  /* @__PURE__ */ jsxRuntime.jsx(
3332
3352
  "button",
3333
3353
  {
3334
3354
  onClick: () => removeStatus(status.id),
3335
- style: { ...btnSmall3, padding: "1px 6px", fontSize: 11 },
3355
+ className: "seatmap-editor__panel-button seatmap-editor__panel-button--tiny",
3336
3356
  disabled: status.id === seatmapCore.AVAILABLE_STATUS_ID,
3337
3357
  title: status.id === seatmapCore.AVAILABLE_STATUS_ID ? "Available status cannot be removed" : "Delete status",
3338
3358
  children: "\u2715"
@@ -3344,16 +3364,21 @@ function StatusManager({ venue, history, store, style }) {
3344
3364
  status.id
3345
3365
  );
3346
3366
  }),
3347
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, marginTop: 10, alignItems: "center" }, children: [
3348
- /* @__PURE__ */ jsxRuntime.jsx(
3349
- "input",
3350
- {
3351
- type: "color",
3352
- value: newColor,
3353
- onChange: (e) => setNewColor(e.target.value),
3354
- style: { width: 28, height: 28, border: "none", padding: 0, cursor: "pointer" }
3355
- }
3356
- ),
3367
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__panel-row seatmap-editor__panel-row--spaced", children: [
3368
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "seatmap-editor__color-picker-shell seatmap-editor__color-picker-shell--lg", children: [
3369
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "seatmap-editor__color-picker-dot", style: { background: newColor } }),
3370
+ /* @__PURE__ */ jsxRuntime.jsx(
3371
+ "input",
3372
+ {
3373
+ type: "color",
3374
+ value: newColor,
3375
+ onChange: (e) => setNewColor(e.target.value),
3376
+ className: "seatmap-editor__color-picker-input",
3377
+ "data-editable": "true",
3378
+ title: "Pick new status color"
3379
+ }
3380
+ )
3381
+ ] }),
3357
3382
  /* @__PURE__ */ jsxRuntime.jsx(
3358
3383
  "input",
3359
3384
  {
@@ -3361,19 +3386,10 @@ function StatusManager({ venue, history, store, style }) {
3361
3386
  value: newName,
3362
3387
  onChange: (e) => setNewName(e.target.value),
3363
3388
  onKeyDown: (e) => e.key === "Enter" && addStatus(),
3364
- style: {
3365
- flex: 1,
3366
- padding: "4px 8px",
3367
- background: "#2a2a4a",
3368
- border: "1px solid #3a3a5a",
3369
- borderRadius: 4,
3370
- color: "#e0e0e0",
3371
- fontSize: 13,
3372
- fontFamily: "system-ui"
3373
- }
3389
+ className: "seatmap-editor__panel-input seatmap-editor__panel-input--grow"
3374
3390
  }
3375
3391
  ),
3376
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: addStatus, style: btnSmall3, children: "Add" })
3392
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: addStatus, className: "seatmap-editor__panel-button", children: "Add" })
3377
3393
  ] })
3378
3394
  ] });
3379
3395
  }
@@ -3387,6 +3403,26 @@ function fitBackgroundToBounds(boundsWidth, boundsHeight, imageWidth, imageHeigh
3387
3403
  aspectRatio: safeImageWidth / safeImageHeight
3388
3404
  };
3389
3405
  }
3406
+ function easeOutBack(t, overshoot) {
3407
+ const c1 = Math.max(0, overshoot);
3408
+ const c3 = c1 + 1;
3409
+ const p = t - 1;
3410
+ return 1 + c3 * p * p * p + c1 * p * p;
3411
+ }
3412
+ function getViewportStateForFitBounds(viewport, aabb, padding = 40) {
3413
+ const contentW = aabb.maxX - aabb.minX;
3414
+ const contentH = aabb.maxY - aabb.minY;
3415
+ if (contentW <= 0 || contentH <= 0) return null;
3416
+ if (viewport.screenWidth <= 0 || viewport.screenHeight <= 0) return null;
3417
+ const minZoom = 0.05;
3418
+ const maxZoom = 4;
3419
+ const scaleX = (viewport.screenWidth - padding * 2) / contentW;
3420
+ const scaleY = (viewport.screenHeight - padding * 2) / contentH;
3421
+ const zoom = Math.min(maxZoom, Math.max(minZoom, Math.min(scaleX, scaleY)));
3422
+ const x = -(aabb.minX + contentW / 2) + viewport.screenWidth / (2 * zoom);
3423
+ const y = -(aabb.minY + contentH / 2) + viewport.screenHeight / (2 * zoom);
3424
+ return { x, y, zoom };
3425
+ }
3390
3426
  function getBackgroundRectInWorld(venue) {
3391
3427
  const width = Math.max(1, venue.backgroundImageWidth ?? venue.bounds.width);
3392
3428
  const height = Math.max(1, venue.backgroundImageHeight ?? venue.bounds.height);
@@ -3399,6 +3435,129 @@ function getBackgroundRectInWorld(venue) {
3399
3435
  height
3400
3436
  };
3401
3437
  }
3438
+ var MOTION_SETTINGS_STORAGE_KEY = "seatmap-editor-motion-settings-v1";
3439
+ var DEFAULT_MOTION_SETTINGS = {
3440
+ sectionDrawJelly: 46,
3441
+ fitViewJelly: 52,
3442
+ panInertiaJelly: 55,
3443
+ pointerScrollZoomJelly: 52,
3444
+ useAdvancedMotion: false,
3445
+ sectionDrawDurationMs: 620,
3446
+ sectionDrawCenterPullPct: 22,
3447
+ sectionDrawZoomBoostPct: 5,
3448
+ sectionDrawOvershootPct: 72,
3449
+ fitViewDurationMs: 680,
3450
+ fitViewOvershootPct: 90,
3451
+ panInertiaCarryPct: 73,
3452
+ panInertiaFrictionPct: 92,
3453
+ panInertiaMinSpeedMilli: 10,
3454
+ panVelocityBlendPct: 30,
3455
+ panStopDeltaMilli: 300,
3456
+ panReleaseIdleMs: 90,
3457
+ pointerScrollZoomDurationMs: 180,
3458
+ pointerScrollZoomStrengthPct: 22,
3459
+ pointerScrollZoomDeltaDivisor: 680
3460
+ };
3461
+ function clampPercent(n) {
3462
+ return Math.max(0, Math.min(100, n));
3463
+ }
3464
+ function clampRange(n, min, max) {
3465
+ return Math.max(min, Math.min(max, n));
3466
+ }
3467
+ function loadMotionSettings() {
3468
+ if (typeof window === "undefined") return DEFAULT_MOTION_SETTINGS;
3469
+ try {
3470
+ const raw = window.localStorage.getItem(MOTION_SETTINGS_STORAGE_KEY);
3471
+ if (!raw) return DEFAULT_MOTION_SETTINGS;
3472
+ const parsed = JSON.parse(raw);
3473
+ return {
3474
+ sectionDrawJelly: clampPercent(Number(parsed.sectionDrawJelly ?? DEFAULT_MOTION_SETTINGS.sectionDrawJelly)),
3475
+ fitViewJelly: clampPercent(Number(parsed.fitViewJelly ?? DEFAULT_MOTION_SETTINGS.fitViewJelly)),
3476
+ panInertiaJelly: clampPercent(Number(parsed.panInertiaJelly ?? DEFAULT_MOTION_SETTINGS.panInertiaJelly)),
3477
+ pointerScrollZoomJelly: clampPercent(
3478
+ Number(parsed.pointerScrollZoomJelly ?? DEFAULT_MOTION_SETTINGS.pointerScrollZoomJelly)
3479
+ ),
3480
+ useAdvancedMotion: Boolean(parsed.useAdvancedMotion ?? DEFAULT_MOTION_SETTINGS.useAdvancedMotion),
3481
+ sectionDrawDurationMs: clampRange(
3482
+ Number(parsed.sectionDrawDurationMs ?? DEFAULT_MOTION_SETTINGS.sectionDrawDurationMs),
3483
+ 100,
3484
+ 3e3
3485
+ ),
3486
+ sectionDrawCenterPullPct: clampRange(
3487
+ Number(parsed.sectionDrawCenterPullPct ?? DEFAULT_MOTION_SETTINGS.sectionDrawCenterPullPct),
3488
+ 0,
3489
+ 100
3490
+ ),
3491
+ sectionDrawZoomBoostPct: clampRange(
3492
+ Number(parsed.sectionDrawZoomBoostPct ?? DEFAULT_MOTION_SETTINGS.sectionDrawZoomBoostPct),
3493
+ 0,
3494
+ 50
3495
+ ),
3496
+ sectionDrawOvershootPct: clampRange(
3497
+ Number(parsed.sectionDrawOvershootPct ?? DEFAULT_MOTION_SETTINGS.sectionDrawOvershootPct),
3498
+ 0,
3499
+ 180
3500
+ ),
3501
+ fitViewDurationMs: clampRange(
3502
+ Number(parsed.fitViewDurationMs ?? DEFAULT_MOTION_SETTINGS.fitViewDurationMs),
3503
+ 100,
3504
+ 3e3
3505
+ ),
3506
+ fitViewOvershootPct: clampRange(
3507
+ Number(parsed.fitViewOvershootPct ?? DEFAULT_MOTION_SETTINGS.fitViewOvershootPct),
3508
+ 0,
3509
+ 180
3510
+ ),
3511
+ panInertiaCarryPct: clampRange(
3512
+ Number(parsed.panInertiaCarryPct ?? DEFAULT_MOTION_SETTINGS.panInertiaCarryPct),
3513
+ 0,
3514
+ 95
3515
+ ),
3516
+ panInertiaFrictionPct: clampRange(
3517
+ Number(parsed.panInertiaFrictionPct ?? DEFAULT_MOTION_SETTINGS.panInertiaFrictionPct),
3518
+ 70,
3519
+ 99
3520
+ ),
3521
+ panInertiaMinSpeedMilli: clampRange(
3522
+ Number(parsed.panInertiaMinSpeedMilli ?? DEFAULT_MOTION_SETTINGS.panInertiaMinSpeedMilli),
3523
+ 1,
3524
+ 50
3525
+ ),
3526
+ panVelocityBlendPct: clampRange(
3527
+ Number(parsed.panVelocityBlendPct ?? DEFAULT_MOTION_SETTINGS.panVelocityBlendPct),
3528
+ 5,
3529
+ 95
3530
+ ),
3531
+ panStopDeltaMilli: clampRange(
3532
+ Number(parsed.panStopDeltaMilli ?? DEFAULT_MOTION_SETTINGS.panStopDeltaMilli),
3533
+ 0,
3534
+ 4e3
3535
+ ),
3536
+ panReleaseIdleMs: clampRange(
3537
+ Number(parsed.panReleaseIdleMs ?? DEFAULT_MOTION_SETTINGS.panReleaseIdleMs),
3538
+ 0,
3539
+ 400
3540
+ ),
3541
+ pointerScrollZoomDurationMs: clampRange(
3542
+ Number(parsed.pointerScrollZoomDurationMs ?? DEFAULT_MOTION_SETTINGS.pointerScrollZoomDurationMs),
3543
+ 60,
3544
+ 600
3545
+ ),
3546
+ pointerScrollZoomStrengthPct: clampRange(
3547
+ Number(parsed.pointerScrollZoomStrengthPct ?? DEFAULT_MOTION_SETTINGS.pointerScrollZoomStrengthPct),
3548
+ 8,
3549
+ 55
3550
+ ),
3551
+ pointerScrollZoomDeltaDivisor: clampRange(
3552
+ Number(parsed.pointerScrollZoomDeltaDivisor ?? DEFAULT_MOTION_SETTINGS.pointerScrollZoomDeltaDivisor),
3553
+ 250,
3554
+ 1400
3555
+ )
3556
+ };
3557
+ } catch {
3558
+ return DEFAULT_MOTION_SETTINGS;
3559
+ }
3560
+ }
3402
3561
  function PolygonPreviewOverlay({
3403
3562
  points,
3404
3563
  closeable,
@@ -3410,59 +3569,53 @@ function PolygonPreviewOverlay({
3410
3569
  const svgPoints = screenPoints.map((p) => `${p.x},${p.y}`).join(" ");
3411
3570
  const first = screenPoints[0];
3412
3571
  const last = screenPoints[screenPoints.length - 1];
3572
+ const isRectanglePreview = mode === "rectangle" && screenPoints.length >= 3;
3413
3573
  return /* @__PURE__ */ jsxRuntime.jsxs(
3414
3574
  "svg",
3415
3575
  {
3416
- style: {
3417
- position: "absolute",
3418
- inset: 0,
3419
- width: "100%",
3420
- height: "100%",
3421
- pointerEvents: "none",
3422
- zIndex: 10
3423
- },
3576
+ className: "seatmap-editor__overlay-svg seatmap-editor__overlay-svg--polygon",
3424
3577
  children: [
3425
- screenPoints.length >= 2 && /* @__PURE__ */ jsxRuntime.jsx(
3578
+ screenPoints.length >= 2 && (isRectanglePreview ? /* @__PURE__ */ jsxRuntime.jsx(
3579
+ "polygon",
3580
+ {
3581
+ className: "seatmap-editor__overlay-polygon",
3582
+ points: svgPoints
3583
+ }
3584
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
3426
3585
  "polyline",
3427
3586
  {
3428
- points: svgPoints,
3429
- fill: "rgba(100, 180, 255, 0.1)",
3430
- stroke: "rgba(100, 180, 255, 0.8)",
3431
- strokeWidth: 2,
3432
- strokeDasharray: "6 4"
3587
+ className: "seatmap-editor__overlay-polyline",
3588
+ points: svgPoints
3433
3589
  }
3434
- ),
3590
+ )),
3435
3591
  mode === "polygon" && screenPoints.length >= 3 && /* @__PURE__ */ jsxRuntime.jsx(
3436
3592
  "line",
3437
3593
  {
3594
+ className: `seatmap-editor__overlay-close-line ${closeable ? "seatmap-editor__overlay-close-line--active" : "seatmap-editor__overlay-close-line--inactive"}`,
3438
3595
  x1: last.x,
3439
3596
  y1: last.y,
3440
3597
  x2: first.x,
3441
3598
  y2: first.y,
3442
- stroke: closeable ? "rgba(100, 255, 100, 0.8)" : "rgba(100, 180, 255, 0.3)",
3443
- strokeWidth: closeable ? 2 : 1,
3444
3599
  strokeDasharray: "4 4"
3445
3600
  }
3446
3601
  ),
3447
3602
  mode === "polygon" && screenPoints.map((p, i) => /* @__PURE__ */ jsxRuntime.jsx(
3448
3603
  "circle",
3449
3604
  {
3605
+ className: `seatmap-editor__overlay-point${i === 0 && closeable ? " seatmap-editor__overlay-point--first-active" : ""}`,
3450
3606
  cx: p.x,
3451
3607
  cy: p.y,
3452
- r: i === 0 && closeable ? 8 : 4,
3453
- fill: i === 0 && closeable ? "rgba(100, 255, 100, 0.8)" : "rgba(100, 180, 255, 0.8)"
3608
+ r: i === 0 && closeable ? 8 : 4
3454
3609
  },
3455
3610
  i
3456
3611
  )),
3457
3612
  mode === "polygon" && points.length >= 2 && /* @__PURE__ */ jsxRuntime.jsxs(
3458
3613
  "text",
3459
3614
  {
3615
+ className: "seatmap-editor__overlay-label-text",
3460
3616
  x: (first.x + last.x) / 2,
3461
3617
  y: (first.y + last.y) / 2 - 10,
3462
- fill: "#e0e0e0",
3463
3618
  fontSize: 12,
3464
- fontFamily: "system-ui",
3465
- textAnchor: "middle",
3466
3619
  children: [
3467
3620
  points.length,
3468
3621
  " points ",
@@ -3487,35 +3640,23 @@ function DragPreviewOverlay({
3487
3640
  return /* @__PURE__ */ jsxRuntime.jsxs(
3488
3641
  "svg",
3489
3642
  {
3490
- style: {
3491
- position: "absolute",
3492
- inset: 0,
3493
- width: "100%",
3494
- height: "100%",
3495
- pointerEvents: "none",
3496
- zIndex: 14
3497
- },
3643
+ className: "seatmap-editor__overlay-svg seatmap-editor__overlay-svg--drag",
3498
3644
  children: [
3499
3645
  sectionScreenOutlines.filter((outline) => outline.length >= 3).map((outline, i) => /* @__PURE__ */ jsxRuntime.jsx(
3500
3646
  "polygon",
3501
3647
  {
3502
- points: outline.map((p) => `${p.x},${p.y}`).join(" "),
3503
- fill: "rgba(110, 190, 255, 0.16)",
3504
- stroke: "rgba(110, 190, 255, 0.95)",
3505
- strokeWidth: 2,
3506
- strokeDasharray: "8 6"
3648
+ className: "seatmap-editor__overlay-drag-outline",
3649
+ points: outline.map((p) => `${p.x},${p.y}`).join(" ")
3507
3650
  },
3508
3651
  i
3509
3652
  )),
3510
3653
  seatScreenPoints.map((p, i) => /* @__PURE__ */ jsxRuntime.jsx(
3511
3654
  "circle",
3512
3655
  {
3656
+ className: "seatmap-editor__overlay-drag-seat",
3513
3657
  cx: p.x,
3514
3658
  cy: p.y,
3515
- r: 5,
3516
- fill: "rgba(110, 190, 255, 0.35)",
3517
- stroke: "rgba(110, 190, 255, 0.95)",
3518
- strokeWidth: 1.5
3659
+ r: 5
3519
3660
  },
3520
3661
  i
3521
3662
  ))
@@ -3541,6 +3682,8 @@ function EditorInner({
3541
3682
  const backgroundResizeAnchorRef = react.useRef(null);
3542
3683
  const backgroundMoveOffsetRef = react.useRef(null);
3543
3684
  const historyRef = react.useRef(new seatmapCore.CommandHistory());
3685
+ const fitViewRafRef = react.useRef(0);
3686
+ const applyMotionSyncRafRef = react.useRef(0);
3544
3687
  const [canUndo, setCanUndo] = react.useState(false);
3545
3688
  const [canRedo, setCanRedo] = react.useState(false);
3546
3689
  const panTool = react.useMemo(() => new PanTool(), []);
@@ -3584,17 +3727,209 @@ function EditorInner({
3584
3727
  const sectionResizeReturnToAddSectionRef = react.useRef(false);
3585
3728
  const [, setDragPreviewVersion] = react.useState(0);
3586
3729
  const [sectionMode, setSectionMode] = react.useState("rectangle");
3730
+ const [sectionKind, setSectionKind] = react.useState("section");
3587
3731
  const [sectionResizeEnabled, setSectionResizeEnabled] = react.useState(false);
3732
+ const [autoFocusNewSection, setAutoFocusNewSection] = react.useState(true);
3588
3733
  const [gridEnabled, setGridEnabled] = react.useState(false);
3589
3734
  const [isGridOptionsOpen, setIsGridOptionsOpen] = react.useState(false);
3590
3735
  const [showCanvasGrid, setShowCanvasGrid] = react.useState(false);
3591
3736
  const [canvasGridStyle, setCanvasGridStyle] = react.useState("solid");
3592
3737
  const [showSectionGrid, setShowSectionGrid] = react.useState(true);
3593
3738
  const [sectionGridStyle, setSectionGridStyle] = react.useState("dots");
3739
+ const [showHints, setShowHints] = react.useState(true);
3740
+ const [isEditorSettingsOpen, setIsEditorSettingsOpen] = react.useState(false);
3741
+ const motionSettings = react.useMemo(() => loadMotionSettings(), []);
3742
+ const [sectionDrawJelly, setSectionDrawJelly] = react.useState(motionSettings.sectionDrawJelly);
3743
+ const [fitViewJelly, setFitViewJelly] = react.useState(motionSettings.fitViewJelly);
3744
+ const [panInertiaJelly, setPanInertiaJelly] = react.useState(motionSettings.panInertiaJelly);
3745
+ const [pointerScrollZoomJelly, setPointerScrollZoomJelly] = react.useState(motionSettings.pointerScrollZoomJelly);
3746
+ const [useAdvancedMotion, setUseAdvancedMotion] = react.useState(motionSettings.useAdvancedMotion);
3747
+ const [sectionDrawDurationMs, setSectionDrawDurationMs] = react.useState(motionSettings.sectionDrawDurationMs);
3748
+ const [sectionDrawCenterPullPct, setSectionDrawCenterPullPct] = react.useState(motionSettings.sectionDrawCenterPullPct);
3749
+ const [sectionDrawZoomBoostPct, setSectionDrawZoomBoostPct] = react.useState(motionSettings.sectionDrawZoomBoostPct);
3750
+ const [sectionDrawOvershootPct, setSectionDrawOvershootPct] = react.useState(motionSettings.sectionDrawOvershootPct);
3751
+ const [fitViewDurationMs, setFitViewDurationMs] = react.useState(motionSettings.fitViewDurationMs);
3752
+ const [fitViewOvershootPct, setFitViewOvershootPct] = react.useState(motionSettings.fitViewOvershootPct);
3753
+ const [panInertiaCarryPct, setPanInertiaCarryPct] = react.useState(motionSettings.panInertiaCarryPct);
3754
+ const [panInertiaFrictionPct, setPanInertiaFrictionPct] = react.useState(motionSettings.panInertiaFrictionPct);
3755
+ const [panInertiaMinSpeedMilli, setPanInertiaMinSpeedMilli] = react.useState(motionSettings.panInertiaMinSpeedMilli);
3756
+ const [panVelocityBlendPct, setPanVelocityBlendPct] = react.useState(motionSettings.panVelocityBlendPct);
3757
+ const [panStopDeltaMilli, setPanStopDeltaMilli] = react.useState(motionSettings.panStopDeltaMilli);
3758
+ const [panReleaseIdleMs, setPanReleaseIdleMs] = react.useState(motionSettings.panReleaseIdleMs);
3759
+ const [pointerScrollZoomDurationMs, setPointerScrollZoomDurationMs] = react.useState(motionSettings.pointerScrollZoomDurationMs);
3760
+ const [pointerScrollZoomStrengthPct, setPointerScrollZoomStrengthPct] = react.useState(motionSettings.pointerScrollZoomStrengthPct);
3761
+ const [pointerScrollZoomDeltaDivisor, setPointerScrollZoomDeltaDivisor] = react.useState(motionSettings.pointerScrollZoomDeltaDivisor);
3594
3762
  const [seatsPerRow, setSeatsPerRow] = react.useState(10);
3595
3763
  const [rowsCount, setRowsCount] = react.useState(1);
3596
3764
  const [rowOrientationDeg, setRowOrientationDeg] = react.useState(0);
3765
+ const [rowDirectionArrowMode, setRowDirectionArrowMode] = react.useState("row-direction");
3597
3766
  const [rowPreviewPoint, setRowPreviewPoint] = react.useState(null);
3767
+ const [cursorScreenPoint, setCursorScreenPoint] = react.useState(null);
3768
+ react.useEffect(() => {
3769
+ if (typeof window === "undefined") return;
3770
+ const payload = {
3771
+ sectionDrawJelly,
3772
+ fitViewJelly,
3773
+ panInertiaJelly,
3774
+ pointerScrollZoomJelly,
3775
+ useAdvancedMotion,
3776
+ sectionDrawDurationMs,
3777
+ sectionDrawCenterPullPct,
3778
+ sectionDrawZoomBoostPct,
3779
+ sectionDrawOvershootPct,
3780
+ fitViewDurationMs,
3781
+ fitViewOvershootPct,
3782
+ panInertiaCarryPct,
3783
+ panInertiaFrictionPct,
3784
+ panInertiaMinSpeedMilli,
3785
+ panVelocityBlendPct,
3786
+ panStopDeltaMilli,
3787
+ panReleaseIdleMs,
3788
+ pointerScrollZoomDurationMs,
3789
+ pointerScrollZoomStrengthPct,
3790
+ pointerScrollZoomDeltaDivisor
3791
+ };
3792
+ window.localStorage.setItem(MOTION_SETTINGS_STORAGE_KEY, JSON.stringify(payload));
3793
+ }, [
3794
+ sectionDrawJelly,
3795
+ fitViewJelly,
3796
+ panInertiaJelly,
3797
+ pointerScrollZoomJelly,
3798
+ useAdvancedMotion,
3799
+ sectionDrawDurationMs,
3800
+ sectionDrawCenterPullPct,
3801
+ sectionDrawZoomBoostPct,
3802
+ sectionDrawOvershootPct,
3803
+ fitViewDurationMs,
3804
+ fitViewOvershootPct,
3805
+ panInertiaCarryPct,
3806
+ panInertiaFrictionPct,
3807
+ panInertiaMinSpeedMilli,
3808
+ panVelocityBlendPct,
3809
+ panStopDeltaMilli,
3810
+ panReleaseIdleMs,
3811
+ pointerScrollZoomDurationMs,
3812
+ pointerScrollZoomStrengthPct,
3813
+ pointerScrollZoomDeltaDivisor
3814
+ ]);
3815
+ react.useEffect(() => {
3816
+ panTool.setInertiaOptions({
3817
+ panInertiaJelly,
3818
+ panInertiaCarry: useAdvancedMotion ? panInertiaCarryPct / 100 : void 0,
3819
+ panInertiaFriction: useAdvancedMotion ? panInertiaFrictionPct / 100 : void 0,
3820
+ panInertiaMinSpeed: useAdvancedMotion ? panInertiaMinSpeedMilli / 1e3 : void 0,
3821
+ panVelocityBlend: useAdvancedMotion ? panVelocityBlendPct / 100 : void 0,
3822
+ panStopDelta: useAdvancedMotion ? panStopDeltaMilli / 1e3 : void 0,
3823
+ panReleaseIdleMs: useAdvancedMotion ? panReleaseIdleMs : void 0
3824
+ });
3825
+ }, [
3826
+ panTool,
3827
+ panInertiaJelly,
3828
+ useAdvancedMotion,
3829
+ panInertiaCarryPct,
3830
+ panInertiaFrictionPct,
3831
+ panInertiaMinSpeedMilli,
3832
+ panVelocityBlendPct,
3833
+ panStopDeltaMilli,
3834
+ panReleaseIdleMs
3835
+ ]);
3836
+ const handleResetMotionSettings = react.useCallback(() => {
3837
+ setSectionDrawJelly(DEFAULT_MOTION_SETTINGS.sectionDrawJelly);
3838
+ setFitViewJelly(DEFAULT_MOTION_SETTINGS.fitViewJelly);
3839
+ setPanInertiaJelly(DEFAULT_MOTION_SETTINGS.panInertiaJelly);
3840
+ setPointerScrollZoomJelly(DEFAULT_MOTION_SETTINGS.pointerScrollZoomJelly);
3841
+ setUseAdvancedMotion(DEFAULT_MOTION_SETTINGS.useAdvancedMotion);
3842
+ setSectionDrawDurationMs(DEFAULT_MOTION_SETTINGS.sectionDrawDurationMs);
3843
+ setSectionDrawCenterPullPct(DEFAULT_MOTION_SETTINGS.sectionDrawCenterPullPct);
3844
+ setSectionDrawZoomBoostPct(DEFAULT_MOTION_SETTINGS.sectionDrawZoomBoostPct);
3845
+ setSectionDrawOvershootPct(DEFAULT_MOTION_SETTINGS.sectionDrawOvershootPct);
3846
+ setFitViewDurationMs(DEFAULT_MOTION_SETTINGS.fitViewDurationMs);
3847
+ setFitViewOvershootPct(DEFAULT_MOTION_SETTINGS.fitViewOvershootPct);
3848
+ setPanInertiaCarryPct(DEFAULT_MOTION_SETTINGS.panInertiaCarryPct);
3849
+ setPanInertiaFrictionPct(DEFAULT_MOTION_SETTINGS.panInertiaFrictionPct);
3850
+ setPanInertiaMinSpeedMilli(DEFAULT_MOTION_SETTINGS.panInertiaMinSpeedMilli);
3851
+ setPanVelocityBlendPct(DEFAULT_MOTION_SETTINGS.panVelocityBlendPct);
3852
+ setPanStopDeltaMilli(DEFAULT_MOTION_SETTINGS.panStopDeltaMilli);
3853
+ setPanReleaseIdleMs(DEFAULT_MOTION_SETTINGS.panReleaseIdleMs);
3854
+ setPointerScrollZoomDurationMs(DEFAULT_MOTION_SETTINGS.pointerScrollZoomDurationMs);
3855
+ setPointerScrollZoomStrengthPct(DEFAULT_MOTION_SETTINGS.pointerScrollZoomStrengthPct);
3856
+ setPointerScrollZoomDeltaDivisor(DEFAULT_MOTION_SETTINGS.pointerScrollZoomDeltaDivisor);
3857
+ }, []);
3858
+ const animateBasicKnobValues = react.useCallback((targets) => {
3859
+ if (applyMotionSyncRafRef.current) {
3860
+ cancelAnimationFrame(applyMotionSyncRafRef.current);
3861
+ applyMotionSyncRafRef.current = 0;
3862
+ }
3863
+ const start = {
3864
+ section: sectionDrawJelly,
3865
+ fit: fitViewJelly,
3866
+ pan: panInertiaJelly,
3867
+ pointerZoom: pointerScrollZoomJelly
3868
+ };
3869
+ const startedAt = performance.now();
3870
+ const durationMs = 260;
3871
+ const easeOutCubicLocal = (t) => 1 - (1 - t) * (1 - t) * (1 - t);
3872
+ const tick = (now) => {
3873
+ const progress = Math.min(1, (now - startedAt) / durationMs);
3874
+ const eased = easeOutCubicLocal(progress);
3875
+ const nextSection = Math.round(start.section + (targets.section - start.section) * eased);
3876
+ const nextFit = Math.round(start.fit + (targets.fit - start.fit) * eased);
3877
+ const nextPan = Math.round(start.pan + (targets.pan - start.pan) * eased);
3878
+ const nextPointerZoom = Math.round(start.pointerZoom + (targets.pointerZoom - start.pointerZoom) * eased);
3879
+ setSectionDrawJelly(clampPercent(nextSection));
3880
+ setFitViewJelly(clampPercent(nextFit));
3881
+ setPanInertiaJelly(clampPercent(nextPan));
3882
+ setPointerScrollZoomJelly(clampPercent(nextPointerZoom));
3883
+ if (progress < 1) {
3884
+ applyMotionSyncRafRef.current = requestAnimationFrame(tick);
3885
+ return;
3886
+ }
3887
+ applyMotionSyncRafRef.current = 0;
3888
+ };
3889
+ applyMotionSyncRafRef.current = requestAnimationFrame(tick);
3890
+ }, [sectionDrawJelly, fitViewJelly, panInertiaJelly, pointerScrollZoomJelly]);
3891
+ const handleApplyAdvancedToBasic = react.useCallback(() => {
3892
+ const sectionFromZoomBoost = (clampRange(sectionDrawZoomBoostPct, 0, 50) / 100 - 0.01) / 0.08;
3893
+ const sectionFromCenterPull = (clampRange(sectionDrawCenterPullPct, 0, 100) / 100 - 0.12) / 0.22;
3894
+ const sectionFromDuration = (clampRange(sectionDrawDurationMs, 100, 3e3) - 380) / 520;
3895
+ const sectionFromOvershoot = (clampRange(sectionDrawOvershootPct, 0, 180) / 100 - 0.08) / 0.48;
3896
+ const nextSectionDrawJelly = clampPercent(
3897
+ Math.round((sectionFromZoomBoost + sectionFromCenterPull + sectionFromDuration + sectionFromOvershoot) / 4 * 100)
3898
+ );
3899
+ const fitFromDuration = (clampRange(fitViewDurationMs, 100, 3e3) - 360) / 620;
3900
+ const fitFromOvershoot = (clampRange(fitViewOvershootPct, 0, 180) / 100 - 0.2) / 0.9;
3901
+ const nextFitViewJelly = clampPercent(Math.round((fitFromDuration + fitFromOvershoot) / 2 * 100));
3902
+ const panFromCarry = (clampRange(panInertiaCarryPct, 0, 95) / 100 - 0.58) / 0.28;
3903
+ const panFromFriction = (clampRange(panInertiaFrictionPct, 70, 99) / 100 - 0.88) / 0.08;
3904
+ const panFromMinSpeed = (0.012 - clampRange(panInertiaMinSpeedMilli, 1, 50) / 1e3) / 4e-3;
3905
+ const nextPanInertiaJelly = clampPercent(Math.round((panFromCarry + panFromFriction + panFromMinSpeed) / 3 * 100));
3906
+ const zoomFromDuration = (clampRange(pointerScrollZoomDurationMs, 60, 600) - 90) / 220;
3907
+ const zoomFromSensitivity = (clampRange(pointerScrollZoomDeltaDivisor, 250, 1400) - 520) / 380;
3908
+ const zoomFromStrength = (0.33 - clampRange(pointerScrollZoomStrengthPct, 8, 55) / 100) / 0.14;
3909
+ const nextPointerScrollZoomJelly = clampPercent(
3910
+ Math.round((zoomFromDuration + zoomFromSensitivity + zoomFromStrength) / 3 * 100)
3911
+ );
3912
+ animateBasicKnobValues({
3913
+ section: nextSectionDrawJelly,
3914
+ fit: nextFitViewJelly,
3915
+ pan: nextPanInertiaJelly,
3916
+ pointerZoom: nextPointerScrollZoomJelly
3917
+ });
3918
+ }, [
3919
+ animateBasicKnobValues,
3920
+ sectionDrawZoomBoostPct,
3921
+ sectionDrawCenterPullPct,
3922
+ sectionDrawDurationMs,
3923
+ sectionDrawOvershootPct,
3924
+ fitViewDurationMs,
3925
+ fitViewOvershootPct,
3926
+ panInertiaCarryPct,
3927
+ panInertiaFrictionPct,
3928
+ panInertiaMinSpeedMilli,
3929
+ pointerScrollZoomDurationMs,
3930
+ pointerScrollZoomStrengthPct,
3931
+ pointerScrollZoomDeltaDivisor
3932
+ ]);
3598
3933
  const handleSeatsPerRowChange = react.useCallback(
3599
3934
  (n) => {
3600
3935
  setSeatsPerRow(n);
@@ -3624,8 +3959,20 @@ function EditorInner({
3624
3959
  },
3625
3960
  [handleRowOrientationChange, rowOrientationDeg]
3626
3961
  );
3627
- const handleSectionModeChange = react.useCallback(
3628
- (mode) => {
3962
+ const rowOrientationKnobDeg = react.useMemo(
3963
+ () => rowDirectionArrowMode === "row-direction" ? ((rowOrientationDeg + 90) % 360 + 360) % 360 : rowOrientationDeg,
3964
+ [rowDirectionArrowMode, rowOrientationDeg]
3965
+ );
3966
+ const handleRowOrientationKnobChange = react.useCallback(
3967
+ (deg) => {
3968
+ const mapped = rowDirectionArrowMode === "row-direction" ? deg - 90 : deg;
3969
+ handleRowOrientationChange(mapped);
3970
+ },
3971
+ [rowDirectionArrowMode, handleRowOrientationChange]
3972
+ );
3973
+ const handleSectionToolVariantChange = react.useCallback(
3974
+ (kind, mode) => {
3975
+ setSectionKind(kind);
3629
3976
  setSectionMode(mode);
3630
3977
  },
3631
3978
  []
@@ -3633,6 +3980,72 @@ function EditorInner({
3633
3980
  react.useEffect(() => {
3634
3981
  addSectionTool.setMode(sectionMode);
3635
3982
  }, [addSectionTool, sectionMode]);
3983
+ react.useEffect(() => {
3984
+ addSectionTool.setSectionKind(sectionKind);
3985
+ }, [addSectionTool, sectionKind]);
3986
+ const focusSectionGently = react.useCallback(
3987
+ (sectionId) => {
3988
+ const currentVenue = store.getState().venue;
3989
+ if (!currentVenue) return;
3990
+ const section = currentVenue.sections.find((entry) => entry.id === sectionId);
3991
+ if (!section) return;
3992
+ if (viewport.screenWidth <= 0 || viewport.screenHeight <= 0) return;
3993
+ const startX = viewport.x;
3994
+ const startY = viewport.y;
3995
+ const startZoom = viewport.zoom;
3996
+ const jelly = Math.max(0, Math.min(100, sectionDrawJelly)) / 100;
3997
+ const zoomBoost = useAdvancedMotion ? clampRange(sectionDrawZoomBoostPct, 0, 50) / 100 : 0.01 + jelly * 0.08;
3998
+ const targetZoom = Math.min(4, Math.max(0.05, startZoom * (1 + zoomBoost)));
3999
+ const centeredX = viewport.screenWidth / (2 * targetZoom) - section.position.x;
4000
+ const centeredY = viewport.screenHeight / (2 * targetZoom) - section.position.y;
4001
+ const centerPull = useAdvancedMotion ? clampRange(sectionDrawCenterPullPct, 0, 100) / 100 : 0.12 + jelly * 0.22;
4002
+ const targetX = startX + (centeredX - startX) * centerPull;
4003
+ const targetY = startY + (centeredY - startY) * centerPull;
4004
+ const durationMs = useAdvancedMotion ? clampRange(sectionDrawDurationMs, 100, 3e3) : 380 + jelly * 520;
4005
+ const overshoot = useAdvancedMotion ? clampRange(sectionDrawOvershootPct, 0, 180) / 100 : 0.08 + jelly * 0.48;
4006
+ const startedAt = performance.now();
4007
+ const animate = (now) => {
4008
+ const elapsed = now - startedAt;
4009
+ const progress = Math.min(1, elapsed / durationMs);
4010
+ const eased = progress >= 1 ? 1 : easeOutBack(progress, overshoot);
4011
+ viewport.x = startX + (targetX - startX) * eased;
4012
+ viewport.y = startY + (targetY - startY) * eased;
4013
+ viewport.setZoom(startZoom + (targetZoom - startZoom) * eased);
4014
+ if (progress < 1) {
4015
+ requestAnimationFrame(animate);
4016
+ }
4017
+ };
4018
+ requestAnimationFrame(animate);
4019
+ },
4020
+ [store, viewport, sectionDrawJelly, useAdvancedMotion, sectionDrawZoomBoostPct, sectionDrawCenterPullPct, sectionDrawDurationMs, sectionDrawOvershootPct]
4021
+ );
4022
+ react.useEffect(() => {
4023
+ addSectionTool.onSectionCreated = (sectionId) => {
4024
+ if (!autoFocusNewSection) return;
4025
+ focusSectionGently(sectionId);
4026
+ };
4027
+ return () => {
4028
+ addSectionTool.onSectionCreated = void 0;
4029
+ };
4030
+ }, [addSectionTool, autoFocusNewSection, focusSectionGently]);
4031
+ const sectionHintText = react.useMemo(() => {
4032
+ if (activeToolName === "select" && sectionResizeEnabled) {
4033
+ return "Resize mode: drag inside section to move it, drag corners to resize, drag sides to move edges, click a side to add a polygon point.";
4034
+ }
4035
+ if (activeToolName !== "add-section") return null;
4036
+ if (sectionMode === "rectangle") {
4037
+ if (addSectionTool.hasPendingDraft()) {
4038
+ return "Click opposite corner to finish. Esc to cancel.";
4039
+ }
4040
+ return "Click first corner to start rectangle.";
4041
+ }
4042
+ if (addSectionTool.hasPendingDraft()) {
4043
+ return "Click to add points. Click first point to close. Esc to cancel.";
4044
+ }
4045
+ return "Click to place first polygon point.";
4046
+ }, [activeToolName, sectionMode, addSectionTool, sectionResizeEnabled]);
4047
+ const rowPresetRows = [1, 2, 3, 4];
4048
+ const rowPresetSeats = [8, 10, 12, 16];
3636
4049
  react.useEffect(() => {
3637
4050
  selectTool.setSectionResizeEnabled(sectionResizeEnabled);
3638
4051
  }, [selectTool, sectionResizeEnabled]);
@@ -3646,13 +4059,17 @@ function EditorInner({
3646
4059
  if (name !== "pan") {
3647
4060
  lastNonPanToolNameRef.current = name;
3648
4061
  }
4062
+ if (name !== activeToolName) {
4063
+ setIsGridOptionsOpen(false);
4064
+ setIsEditorSettingsOpen(false);
4065
+ }
3649
4066
  activeToolRef.current.onDeactivate();
3650
4067
  const tool = toolMap[name] ?? selectTool;
3651
4068
  tool.onActivate(viewport, store);
3652
4069
  activeToolRef.current = tool;
3653
4070
  setActiveToolName(name);
3654
4071
  },
3655
- [toolMap, selectTool, viewport, store]
4072
+ [activeToolName, toolMap, selectTool, viewport, store]
3656
4073
  );
3657
4074
  const handleToggleSectionResize = react.useCallback(
3658
4075
  (fromAddSection = false) => {
@@ -3723,10 +4140,52 @@ function EditorInner({
3723
4140
  };
3724
4141
  input.click();
3725
4142
  }, [store, spatialIndex, viewport]);
4143
+ const stopFitViewAnimation = react.useCallback(() => {
4144
+ if (fitViewRafRef.current) {
4145
+ cancelAnimationFrame(fitViewRafRef.current);
4146
+ fitViewRafRef.current = 0;
4147
+ }
4148
+ }, []);
4149
+ const animateFitView = react.useCallback(
4150
+ (bounds) => {
4151
+ const target = getViewportStateForFitBounds(viewport, bounds, 40);
4152
+ if (!target) return;
4153
+ stopFitViewAnimation();
4154
+ const startX = viewport.x;
4155
+ const startY = viewport.y;
4156
+ const startZoom = viewport.zoom;
4157
+ const jelly = Math.max(0, Math.min(100, fitViewJelly)) / 100;
4158
+ const durationMs = useAdvancedMotion ? clampRange(fitViewDurationMs, 100, 3e3) : 360 + jelly * 620;
4159
+ const overshoot = useAdvancedMotion ? clampRange(fitViewOvershootPct, 0, 180) / 100 : 0.2 + jelly * 0.9;
4160
+ const startedAt = performance.now();
4161
+ const animate = (now) => {
4162
+ const elapsed = now - startedAt;
4163
+ const progress = Math.min(1, elapsed / durationMs);
4164
+ const eased = progress >= 1 ? 1 : easeOutBack(progress, overshoot);
4165
+ viewport.x = startX + (target.x - startX) * eased;
4166
+ viewport.y = startY + (target.y - startY) * eased;
4167
+ viewport.setZoom(startZoom + (target.zoom - startZoom) * eased);
4168
+ if (progress < 1) {
4169
+ fitViewRafRef.current = requestAnimationFrame(animate);
4170
+ return;
4171
+ }
4172
+ fitViewRafRef.current = 0;
4173
+ };
4174
+ fitViewRafRef.current = requestAnimationFrame(animate);
4175
+ },
4176
+ [viewport, stopFitViewAnimation, fitViewJelly, useAdvancedMotion, fitViewDurationMs, fitViewOvershootPct]
4177
+ );
3726
4178
  const handleFitView = react.useCallback(() => {
3727
4179
  if (!venue) return;
3728
- viewport.fitBounds(seatmapCore.venueAABB(venue));
3729
- }, [venue, viewport]);
4180
+ animateFitView(seatmapCore.venueAABB(venue));
4181
+ }, [venue, animateFitView]);
4182
+ react.useEffect(() => () => stopFitViewAnimation(), [stopFitViewAnimation]);
4183
+ react.useEffect(() => () => {
4184
+ if (applyMotionSyncRafRef.current) {
4185
+ cancelAnimationFrame(applyMotionSyncRafRef.current);
4186
+ applyMotionSyncRafRef.current = 0;
4187
+ }
4188
+ }, []);
3730
4189
  const handleUploadBackground = react.useCallback(() => {
3731
4190
  const input = document.createElement("input");
3732
4191
  input.type = "file";
@@ -4020,6 +4479,7 @@ function EditorInner({
4020
4479
  }, [isBackgroundMoving, store, viewport]);
4021
4480
  const renderBackgroundResizeOverlay = () => {
4022
4481
  if (!venue?.backgroundImage) return null;
4482
+ if (activeToolName !== "select") return null;
4023
4483
  const rectWorld = getBackgroundRectInWorld(venue);
4024
4484
  const topLeft = viewport.worldToScreen(rectWorld.x, rectWorld.y);
4025
4485
  const topRight = viewport.worldToScreen(rectWorld.x + rectWorld.width, rectWorld.y);
@@ -4039,19 +4499,16 @@ function EditorInner({
4039
4499
  { left: midTop.x, top: midTop.y, cursor: "ns-resize", handle: "n" },
4040
4500
  { left: midBottom.x, top: midBottom.y, cursor: "ns-resize", handle: "s" }
4041
4501
  ];
4042
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "absolute", inset: 0, pointerEvents: "none", zIndex: 15 }, children: [
4502
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__background-overlay", children: [
4043
4503
  /* @__PURE__ */ jsxRuntime.jsx(
4044
4504
  "div",
4045
4505
  {
4506
+ className: "seatmap-editor__background-frame",
4046
4507
  style: {
4047
- position: "absolute",
4048
4508
  left: topLeft.x,
4049
4509
  top: topLeft.y,
4050
4510
  width: Math.max(1, topRight.x - topLeft.x),
4051
4511
  height: Math.max(1, bottomLeft.y - topLeft.y),
4052
- border: "1px dashed rgba(130, 190, 255, 0.9)",
4053
- boxShadow: "0 0 0 1px rgba(30, 30, 50, 0.8) inset",
4054
- pointerEvents: "auto",
4055
4512
  cursor: isBackgroundMoving ? "grabbing" : "grab"
4056
4513
  },
4057
4514
  onPointerDown: (e) => {
@@ -4077,17 +4534,11 @@ function EditorInner({
4077
4534
  handles.map((handle, i) => /* @__PURE__ */ jsxRuntime.jsx(
4078
4535
  "div",
4079
4536
  {
4537
+ className: "seatmap-editor__background-handle",
4080
4538
  style: {
4081
- position: "absolute",
4082
4539
  left: handle.left - 5,
4083
4540
  top: handle.top - 5,
4084
- width: 10,
4085
- height: 10,
4086
- borderRadius: 2,
4087
- background: "#82beff",
4088
- border: "1px solid #1f2f5f",
4089
- cursor: handle.cursor,
4090
- pointerEvents: "auto"
4541
+ cursor: handle.cursor
4091
4542
  },
4092
4543
  onPointerDown: (e) => {
4093
4544
  if (e.button !== 0) return;
@@ -4127,48 +4578,34 @@ function EditorInner({
4127
4578
  return /* @__PURE__ */ jsxRuntime.jsxs(
4128
4579
  "svg",
4129
4580
  {
4130
- style: {
4131
- position: "absolute",
4132
- inset: 0,
4133
- width: "100%",
4134
- height: "100%",
4135
- pointerEvents: "none",
4136
- zIndex: 16
4137
- },
4581
+ className: "seatmap-editor__overlay-svg seatmap-editor__overlay-svg--section-resize",
4138
4582
  children: [
4139
4583
  /* @__PURE__ */ jsxRuntime.jsx(
4140
4584
  "polygon",
4141
4585
  {
4142
- points: outlinePoints,
4143
- fill: "rgba(255, 193, 110, 0.08)",
4144
- stroke: "rgba(255, 193, 110, 0.9)",
4145
- strokeWidth: 1.5,
4146
- strokeDasharray: "7 4"
4586
+ className: "seatmap-editor__overlay-section-outline",
4587
+ points: outlinePoints
4147
4588
  }
4148
4589
  ),
4149
4590
  sideMidpoints.map((p, i) => /* @__PURE__ */ jsxRuntime.jsx(
4150
4591
  "rect",
4151
4592
  {
4593
+ className: "seatmap-editor__overlay-section-side",
4152
4594
  x: p.x - 5,
4153
4595
  y: p.y - 5,
4154
4596
  width: 10,
4155
4597
  height: 10,
4156
- rx: 2,
4157
- fill: "rgba(255, 193, 110, 0.9)",
4158
- stroke: "rgba(45, 36, 20, 0.95)",
4159
- strokeWidth: 1
4598
+ rx: 2
4160
4599
  },
4161
4600
  `side-${i}`
4162
4601
  )),
4163
4602
  corners.map((p, i) => /* @__PURE__ */ jsxRuntime.jsx(
4164
4603
  "circle",
4165
4604
  {
4605
+ className: "seatmap-editor__overlay-section-corner",
4166
4606
  cx: p.x,
4167
4607
  cy: p.y,
4168
- r: 5,
4169
- fill: "#ffd38a",
4170
- stroke: "rgba(45, 36, 20, 0.95)",
4171
- strokeWidth: 1.2
4608
+ r: 5
4172
4609
  },
4173
4610
  `corner-${i}`
4174
4611
  )),
@@ -4176,25 +4613,20 @@ function EditorInner({
4176
4613
  /* @__PURE__ */ jsxRuntime.jsx(
4177
4614
  "rect",
4178
4615
  {
4616
+ className: "seatmap-editor__overlay-label-box",
4179
4617
  x: hint.x - 88,
4180
4618
  y: hint.y - 32,
4181
4619
  width: 176,
4182
4620
  height: 20,
4183
- rx: 6,
4184
- fill: "rgba(15, 15, 25, 0.9)",
4185
- stroke: "rgba(255, 193, 110, 0.65)",
4186
- strokeWidth: 1
4621
+ rx: 6
4187
4622
  }
4188
4623
  ),
4189
4624
  /* @__PURE__ */ jsxRuntime.jsx(
4190
4625
  "text",
4191
4626
  {
4627
+ className: "seatmap-editor__overlay-label-text",
4192
4628
  x: hint.x,
4193
4629
  y: hint.y - 18,
4194
- fill: "#ffd38a",
4195
- fontSize: 11,
4196
- fontFamily: "system-ui",
4197
- textAnchor: "middle",
4198
4630
  children: resizeOverlay.mergeHint?.message
4199
4631
  }
4200
4632
  )
@@ -4207,42 +4639,37 @@ function EditorInner({
4207
4639
  if (activeToolName !== "add-row" || !rowPreviewPoint || !venue) return null;
4208
4640
  const preview = addRowTool.getPlacementPreview(rowPreviewPoint.x, rowPreviewPoint.y, venue);
4209
4641
  if (!preview) return null;
4642
+ const displayAngleRad = rowDirectionArrowMode === "row-direction" ? preview.worldAngleRad + Math.PI / 2 : preview.worldAngleRad;
4210
4643
  const origin = viewport.worldToScreen(preview.worldX, preview.worldY);
4211
4644
  const lineLengthPx = 78;
4212
4645
  const end = {
4213
- x: origin.x + Math.cos(preview.worldAngleRad) * lineLengthPx,
4214
- y: origin.y + Math.sin(preview.worldAngleRad) * lineLengthPx
4646
+ x: origin.x + Math.cos(displayAngleRad) * lineLengthPx,
4647
+ y: origin.y + Math.sin(displayAngleRad) * lineLengthPx
4215
4648
  };
4216
4649
  const arrowSizePx = 11;
4217
4650
  const leftWing = {
4218
- x: end.x - Math.cos(preview.worldAngleRad - Math.PI / 6) * arrowSizePx,
4219
- y: end.y - Math.sin(preview.worldAngleRad - Math.PI / 6) * arrowSizePx
4651
+ x: end.x - Math.cos(displayAngleRad - Math.PI / 6) * arrowSizePx,
4652
+ y: end.y - Math.sin(displayAngleRad - Math.PI / 6) * arrowSizePx
4220
4653
  };
4221
4654
  const rightWing = {
4222
- x: end.x - Math.cos(preview.worldAngleRad + Math.PI / 6) * arrowSizePx,
4223
- y: end.y - Math.sin(preview.worldAngleRad + Math.PI / 6) * arrowSizePx
4655
+ x: end.x - Math.cos(displayAngleRad + Math.PI / 6) * arrowSizePx,
4656
+ y: end.y - Math.sin(displayAngleRad + Math.PI / 6) * arrowSizePx
4224
4657
  };
4225
- const worldAngleDeg = ((preview.worldAngleRad * 180 / Math.PI + 90) % 360 + 360) % 360;
4658
+ const displayAngleDeg = ((displayAngleRad * 180 / Math.PI + 90) % 360 + 360) % 360;
4659
+ const orientationLabel = rowDirectionArrowMode === "row-direction" ? "Row direction" : "Viewer direction";
4226
4660
  return /* @__PURE__ */ jsxRuntime.jsxs(
4227
4661
  "svg",
4228
4662
  {
4229
- style: {
4230
- position: "absolute",
4231
- inset: 0,
4232
- width: "100%",
4233
- height: "100%",
4234
- pointerEvents: "none",
4235
- zIndex: 17
4236
- },
4663
+ className: "seatmap-editor__overlay-svg seatmap-editor__overlay-svg--row-direction",
4237
4664
  children: [
4238
4665
  /* @__PURE__ */ jsxRuntime.jsx(
4239
4666
  "line",
4240
4667
  {
4668
+ className: "seatmap-editor__overlay-row-direction-line",
4241
4669
  x1: origin.x,
4242
4670
  y1: origin.y,
4243
4671
  x2: end.x,
4244
4672
  y2: end.y,
4245
- stroke: "rgba(255, 213, 122, 0.95)",
4246
4673
  strokeWidth: 3,
4247
4674
  strokeLinecap: "round"
4248
4675
  }
@@ -4250,44 +4677,38 @@ function EditorInner({
4250
4677
  /* @__PURE__ */ jsxRuntime.jsx(
4251
4678
  "polygon",
4252
4679
  {
4253
- points: `${end.x},${end.y} ${leftWing.x},${leftWing.y} ${rightWing.x},${rightWing.y}`,
4254
- fill: "rgba(255, 213, 122, 0.95)"
4680
+ className: "seatmap-editor__overlay-row-direction-arrow",
4681
+ points: `${end.x},${end.y} ${leftWing.x},${leftWing.y} ${rightWing.x},${rightWing.y}`
4255
4682
  }
4256
4683
  ),
4257
4684
  /* @__PURE__ */ jsxRuntime.jsx(
4258
4685
  "circle",
4259
4686
  {
4687
+ className: "seatmap-editor__overlay-row-direction-origin",
4260
4688
  cx: origin.x,
4261
4689
  cy: origin.y,
4262
4690
  r: 5,
4263
- fill: "rgba(255, 213, 122, 0.28)",
4264
- stroke: "rgba(255, 213, 122, 0.95)",
4265
4691
  strokeWidth: 1.5
4266
4692
  }
4267
4693
  ),
4268
4694
  /* @__PURE__ */ jsxRuntime.jsx(
4269
4695
  "rect",
4270
4696
  {
4697
+ className: "seatmap-editor__overlay-label-box",
4271
4698
  x: origin.x + 10,
4272
4699
  y: origin.y - 28,
4273
4700
  width: 90,
4274
4701
  height: 20,
4275
- rx: 5,
4276
- fill: "rgba(15, 15, 25, 0.9)",
4277
- stroke: "rgba(255, 213, 122, 0.65)",
4278
- strokeWidth: 1
4702
+ rx: 5
4279
4703
  }
4280
4704
  ),
4281
4705
  /* @__PURE__ */ jsxRuntime.jsx(
4282
4706
  "text",
4283
4707
  {
4708
+ className: "seatmap-editor__overlay-label-text",
4284
4709
  x: origin.x + 55,
4285
4710
  y: origin.y - 14,
4286
- fill: "#ffd57a",
4287
- fontSize: 11,
4288
- fontFamily: "system-ui",
4289
- textAnchor: "middle",
4290
- children: `Row angle ${Math.round(worldAngleDeg)}deg`
4711
+ children: `${orientationLabel} ${Math.round(displayAngleDeg)}\xB0`
4291
4712
  }
4292
4713
  )
4293
4714
  ]
@@ -4306,719 +4727,808 @@ function EditorInner({
4306
4727
  },
4307
4728
  [venue, store]
4308
4729
  );
4309
- const renderActiveToolOptionsOverlay = () => {
4310
- const switchTrackBase = {
4311
- width: 34,
4312
- height: 20,
4313
- borderRadius: 999,
4314
- border: "1px solid #4a4a6a",
4315
- padding: 2,
4316
- display: "inline-flex",
4317
- alignItems: "center",
4318
- transition: "all 0.12s ease"
4319
- };
4320
- const switchThumbBase = {
4321
- width: 14,
4322
- height: 14,
4323
- borderRadius: "50%",
4324
- background: "#e0e0e0",
4325
- transition: "transform 0.12s ease"
4730
+ const handleDeleteSelectedObjects = react.useCallback(() => {
4731
+ const state = store.getState();
4732
+ const currentVenue = state.venue;
4733
+ if (!currentVenue) return;
4734
+ const selectedSeatIdSet = new Set(state.selectedSeatIds);
4735
+ const selectedSectionIdSet = new Set(state.selectedSectionIds);
4736
+ if (selectedSeatIdSet.size === 0 && selectedSectionIdSet.size === 0) return;
4737
+ const previousVenue = currentVenue;
4738
+ const nextVenue = {
4739
+ ...currentVenue,
4740
+ sections: currentVenue.sections.filter((section) => !selectedSectionIdSet.has(section.id)).map((section) => ({
4741
+ ...section,
4742
+ rows: section.rows.map((row) => ({
4743
+ ...row,
4744
+ seats: row.seats.filter((seat) => !selectedSeatIdSet.has(seat.id))
4745
+ }))
4746
+ })),
4747
+ tables: currentVenue.tables.map((table) => ({
4748
+ ...table,
4749
+ seats: table.seats.filter((seat) => !selectedSeatIdSet.has(seat.id))
4750
+ }))
4326
4751
  };
4327
- const renderSwitch = (label, checked, onToggle) => /* @__PURE__ */ jsxRuntime.jsxs(
4328
- "label",
4752
+ historyRef.current.execute({
4753
+ description: "Delete selected objects",
4754
+ execute: () => {
4755
+ store.getState().setVenue(nextVenue);
4756
+ store.getState().clearSelection();
4757
+ },
4758
+ undo: () => {
4759
+ store.getState().setVenue(previousVenue);
4760
+ }
4761
+ });
4762
+ }, [store]);
4763
+ const renderActiveToolOptionsOverlay = () => {
4764
+ const stopPointerPropagation = (e) => e.stopPropagation();
4765
+ const renderOverlay = (content) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__tool-options-overlay", children: /* @__PURE__ */ jsxRuntime.jsx(
4766
+ "div",
4329
4767
  {
4330
- style: {
4331
- display: "inline-flex",
4332
- alignItems: "center",
4333
- gap: 8,
4334
- color: "#d0d0e0",
4335
- fontSize: 12,
4336
- fontFamily: "system-ui",
4337
- userSelect: "none"
4338
- },
4339
- children: [
4340
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }),
4341
- /* @__PURE__ */ jsxRuntime.jsx(
4342
- "button",
4343
- {
4344
- type: "button",
4345
- role: "switch",
4346
- "aria-checked": checked,
4347
- onClick: onToggle,
4348
- style: {
4349
- ...switchTrackBase,
4350
- background: checked ? "#2d6a3d" : "#2a2a4a",
4351
- borderColor: checked ? "#57b26f" : "#4a4a6a",
4352
- cursor: "pointer"
4353
- },
4354
- children: /* @__PURE__ */ jsxRuntime.jsx(
4355
- "span",
4768
+ className: "seatmap-editor__tool-options-shell",
4769
+ onPointerDown: stopPointerPropagation,
4770
+ onPointerMove: stopPointerPropagation,
4771
+ onPointerUp: stopPointerPropagation,
4772
+ children: content
4773
+ }
4774
+ ) });
4775
+ const renderSwitch = (label, checked, onToggle) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "seatmap-editor__switch", children: [
4776
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }),
4777
+ /* @__PURE__ */ jsxRuntime.jsx(
4778
+ "button",
4779
+ {
4780
+ type: "button",
4781
+ role: "switch",
4782
+ "aria-checked": checked,
4783
+ onClick: onToggle,
4784
+ className: `seatmap-editor__switch-track${checked ? " is-checked" : ""}`,
4785
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__switch-thumb" })
4786
+ }
4787
+ )
4788
+ ] });
4789
+ const renderRange = (label, value, onChange2, hint, options) => {
4790
+ const min = options?.min ?? 0;
4791
+ const max = options?.max ?? 100;
4792
+ const step = options?.step ?? 1;
4793
+ const displayValue = options?.valueFormatter ? options.valueFormatter(value) : Math.round(value);
4794
+ const normalized = max > min ? (value - min) / (max - min) : 0;
4795
+ const clampedNormalized = clampRange(normalized, 0, 1);
4796
+ const isDial360 = options?.knobMode === "dial360";
4797
+ const knobFillStartDeg = options?.knobFillStartDeg ?? (isDial360 ? -90 : -130);
4798
+ const knobAngle = isDial360 ? knobFillStartDeg + clampedNormalized * 360 : -130 + clampedNormalized * 260;
4799
+ const knobFillPercent = isDial360 ? clampedNormalized * 100 : clampedNormalized * (260 / 360 * 100);
4800
+ const knobDisabled = Boolean(options?.disabled);
4801
+ const showKnob = Boolean(options?.displayAsKnob);
4802
+ const valuePlacement = options?.valuePlacement ?? "header";
4803
+ const compactKnobLayout = Boolean(options?.compactKnobLayout) && showKnob;
4804
+ const isLabelValueKnobLayout = showKnob && options?.knobLayout === "label-value-knob";
4805
+ const knobNamespace = options?.knobNamespace ?? "motion";
4806
+ const tooltipText = knobDisabled ? "Disabled while advanced overrides are enabled." : hint;
4807
+ const commitClampedValue = (raw) => {
4808
+ const clamped = clampRange(raw, min, max);
4809
+ const stepped = Math.round((clamped - min) / step) * step + min;
4810
+ onChange2(clampRange(stepped, min, max));
4811
+ };
4812
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4813
+ "label",
4814
+ {
4815
+ className: `seatmap-editor__motion-slider${compactKnobLayout ? " seatmap-editor__motion-slider--compact-knob" : ""}${isLabelValueKnobLayout ? " seatmap-editor__motion-slider--label-value-knob" : ""}${knobDisabled ? " is-disabled" : ""}`,
4816
+ children: [
4817
+ !isLabelValueKnobLayout && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__motion-slider-header", children: [
4818
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__label", title: label, children: label }),
4819
+ valuePlacement === "header" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__motion-slider-value", children: displayValue })
4820
+ ] }),
4821
+ showKnob ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `seatmap-editor__motion-knob-row${isLabelValueKnobLayout ? " seatmap-editor__motion-knob-row--label-value-knob" : ""}`, children: [
4822
+ isLabelValueKnobLayout && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__label", title: label, children: label }),
4823
+ isLabelValueKnobLayout && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__motion-slider-value", children: displayValue }),
4824
+ /* @__PURE__ */ jsxRuntime.jsxs(
4825
+ "button",
4356
4826
  {
4357
- style: {
4358
- ...switchThumbBase,
4359
- transform: checked ? "translateX(14px)" : "translateX(0)"
4360
- }
4827
+ type: "button",
4828
+ className: "seatmap-editor__motion-knob",
4829
+ "aria-label": label,
4830
+ title: tooltipText,
4831
+ disabled: knobDisabled,
4832
+ onPointerDown: (e) => {
4833
+ if (knobDisabled) return;
4834
+ e.preventDefault();
4835
+ const pointerId = e.pointerId;
4836
+ const target = e.currentTarget;
4837
+ target.setPointerCapture(pointerId);
4838
+ const range = max - min;
4839
+ const getDialValue = (clientX, clientY) => {
4840
+ const rect = target.getBoundingClientRect();
4841
+ const centerX = rect.left + rect.width / 2;
4842
+ const centerY = rect.top + rect.height / 2;
4843
+ const angle = Math.atan2(clientY - centerY, clientX - centerX);
4844
+ const clockwiseFromUpDeg = ((angle * 180 / Math.PI + 90) % 360 + 360) % 360;
4845
+ return min + clockwiseFromUpDeg / 360 * range;
4846
+ };
4847
+ let handleMove;
4848
+ if (isDial360) {
4849
+ commitClampedValue(getDialValue(e.clientX, e.clientY));
4850
+ handleMove = (ev) => {
4851
+ commitClampedValue(getDialValue(ev.clientX, ev.clientY));
4852
+ };
4853
+ } else {
4854
+ const startY = e.clientY;
4855
+ const startValue = value;
4856
+ const dragHeightPx = 180;
4857
+ handleMove = (ev) => {
4858
+ const deltaY = startY - ev.clientY;
4859
+ commitClampedValue(startValue + deltaY / dragHeightPx * range);
4860
+ };
4861
+ }
4862
+ const handleUp = () => {
4863
+ target.removeEventListener("pointermove", handleMove);
4864
+ target.removeEventListener("pointerup", handleUp);
4865
+ target.removeEventListener("pointercancel", handleUp);
4866
+ };
4867
+ target.addEventListener("pointermove", handleMove);
4868
+ target.addEventListener("pointerup", handleUp);
4869
+ target.addEventListener("pointercancel", handleUp);
4870
+ },
4871
+ children: [
4872
+ /* @__PURE__ */ jsxRuntime.jsx(
4873
+ "span",
4874
+ {
4875
+ className: `seatmap-editor__motion-knob-ring${knobNamespace === "tool" ? " seatmap-editor__motion-knob-ring--tool" : " seatmap-editor__motion-knob-ring--motion"}`,
4876
+ style: {
4877
+ [knobNamespace === "tool" ? "--tool-knob-fill" : "--motion-knob-fill"]: `${Math.round(knobFillPercent)}%`,
4878
+ [knobNamespace === "tool" ? "--tool-knob-start-angle" : "--motion-knob-start-angle"]: `${knobFillStartDeg}deg`
4879
+ }
4880
+ }
4881
+ ),
4882
+ /* @__PURE__ */ jsxRuntime.jsx(
4883
+ "span",
4884
+ {
4885
+ className: "seatmap-editor__motion-knob-indicator",
4886
+ style: { transform: `translate(-50%, -100%) rotate(${knobAngle}deg)` }
4887
+ }
4888
+ )
4889
+ ]
4361
4890
  }
4362
- )
4891
+ ),
4892
+ valuePlacement === "knob" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__motion-slider-value seatmap-editor__motion-slider-value--knob", children: displayValue }),
4893
+ options?.knobRightContent && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__motion-knob-right-content", children: options.knobRightContent })
4894
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
4895
+ "input",
4896
+ {
4897
+ type: "range",
4898
+ min,
4899
+ max,
4900
+ step,
4901
+ value,
4902
+ onChange: (e) => commitClampedValue(Number(e.target.value) || 0),
4903
+ disabled: knobDisabled,
4904
+ className: "seatmap-editor__panel-range",
4905
+ title: tooltipText
4906
+ }
4907
+ )
4908
+ ]
4909
+ }
4910
+ );
4911
+ };
4912
+ const renderOptionCard = (title, body, className) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `seatmap-editor__option-card${className ? ` ${className}` : ""}`, children: [
4913
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__option-card-title seatmap-editor__option-card-title--group", children: title }),
4914
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__option-card-subdivider" }),
4915
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__option-card-body", children: body })
4916
+ ] });
4917
+ const renderGridOptionsCard = () => renderOptionCard(
4918
+ "Grid options",
4919
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4920
+ renderSwitch("Grid", gridEnabled, () => setGridEnabled((v) => !v)),
4921
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__option-card-divider" }),
4922
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__option-row", children: [
4923
+ renderSwitch("Canvas grid", showCanvasGrid, () => setShowCanvasGrid((v) => !v)),
4924
+ /* @__PURE__ */ jsxRuntime.jsxs(
4925
+ "select",
4926
+ {
4927
+ value: canvasGridStyle,
4928
+ onChange: (e) => setCanvasGridStyle(e.target.value),
4929
+ className: "seatmap-editor__select",
4930
+ disabled: !gridEnabled || !showCanvasGrid,
4931
+ children: [
4932
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "solid", children: "Solid" }),
4933
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "dashed", children: "Dashed" }),
4934
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "dotted", children: "Dotted" })
4935
+ ]
4363
4936
  }
4364
4937
  )
4365
- ]
4366
- }
4938
+ ] }),
4939
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__option-row", children: [
4940
+ renderSwitch("Section grid", showSectionGrid, () => setShowSectionGrid((v) => !v)),
4941
+ /* @__PURE__ */ jsxRuntime.jsxs(
4942
+ "select",
4943
+ {
4944
+ value: sectionGridStyle,
4945
+ onChange: (e) => setSectionGridStyle(e.target.value),
4946
+ className: "seatmap-editor__select",
4947
+ disabled: !gridEnabled || !showSectionGrid,
4948
+ children: [
4949
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "dots", children: "Dots" }),
4950
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "cross", children: "Cross" })
4951
+ ]
4952
+ }
4953
+ )
4954
+ ] })
4955
+ ] })
4367
4956
  );
4368
- const selectStyle2 = {
4369
- padding: "4px 8px",
4370
- background: "#2a2a4a",
4371
- border: "1px solid #3a3a5a",
4372
- borderRadius: 4,
4373
- color: "#e0e0e0",
4374
- fontSize: 12,
4375
- fontFamily: "system-ui",
4376
- cursor: "pointer"
4377
- };
4378
- const renderGridOptionsCard = () => /* @__PURE__ */ jsxRuntime.jsxs(
4379
- "div",
4380
- {
4381
- style: {
4382
- display: "flex",
4383
- flexDirection: "column",
4384
- alignItems: "flex-start",
4385
- gap: 8,
4386
- padding: "8px 10px",
4387
- border: "1px solid #3a3a5a",
4388
- borderRadius: 6,
4389
- background: "rgba(42, 42, 74, 0.65)"
4390
- },
4391
- children: [
4392
- /* @__PURE__ */ jsxRuntime.jsx(
4393
- "span",
4957
+ const renderMotionSettingsCard = () => renderOptionCard(
4958
+ "Editor motion",
4959
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__motion-layout", children: [
4960
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__motion-column seatmap-editor__motion-column--basic", children: [
4961
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__option-card-title", children: "Basic" }),
4962
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__motion-control-grid", children: [
4963
+ renderRange(
4964
+ "Section draw zoom jelly",
4965
+ sectionDrawJelly,
4966
+ setSectionDrawJelly,
4967
+ "Controls how floaty auto-focus feels after drawing a section.",
4968
+ { disabled: useAdvancedMotion, displayAsKnob: true, knobLayout: "label-value-knob" }
4969
+ ),
4970
+ renderRange(
4971
+ "Fit zoom jelly",
4972
+ fitViewJelly,
4973
+ setFitViewJelly,
4974
+ "Controls smoothness and duration of Fit action.",
4975
+ { disabled: useAdvancedMotion, displayAsKnob: true, knobLayout: "label-value-knob" }
4976
+ ),
4977
+ renderRange(
4978
+ "Canvas pan inertia",
4979
+ panInertiaJelly,
4980
+ setPanInertiaJelly,
4981
+ "Controls glide amount after you release a pan drag.",
4982
+ { disabled: useAdvancedMotion, displayAsKnob: true, knobLayout: "label-value-knob" }
4983
+ ),
4984
+ renderRange(
4985
+ "Pointer scroll zoom jelly",
4986
+ pointerScrollZoomJelly,
4987
+ setPointerScrollZoomJelly,
4988
+ "Controls how smooth pointer wheel zoom feels.",
4989
+ { disabled: useAdvancedMotion, displayAsKnob: true, knobLayout: "label-value-knob" }
4990
+ )
4991
+ ] }),
4992
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__option-row seatmap-editor__option-row--end seatmap-editor__motion-basic-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
4993
+ "button",
4394
4994
  {
4395
- style: {
4396
- color: "#c7c7df",
4397
- fontSize: 12,
4398
- fontFamily: "system-ui",
4399
- fontWeight: 600
4400
- },
4401
- children: "Grid options"
4995
+ type: "button",
4996
+ className: "seatmap-editor__panel-button seatmap-editor__panel-button--tiny",
4997
+ onClick: handleResetMotionSettings,
4998
+ children: "Reset to defaults"
4402
4999
  }
4403
- ),
4404
- renderSwitch("Grid", gridEnabled, () => setGridEnabled((v) => !v)),
4405
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: "100%", height: 1, background: "#3a3a5a" } }),
4406
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
4407
- renderSwitch("Canvas grid", showCanvasGrid, () => setShowCanvasGrid((v) => !v)),
4408
- /* @__PURE__ */ jsxRuntime.jsxs(
4409
- "select",
5000
+ ) }),
5001
+ renderSwitch("Use advanced overrides", useAdvancedMotion, () => setUseAdvancedMotion((v) => !v))
5002
+ ] }),
5003
+ useAdvancedMotion && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__motion-column seatmap-editor__motion-column--advanced", children: [
5004
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__motion-advanced-header", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__option-card-title", children: "Advanced" }) }),
5005
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__option-card-title seatmap-editor__option-card-title--subtle", children: "Section" }),
5006
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__motion-control-grid is-knob-grid", children: [
5007
+ renderRange(
5008
+ "Draw duration",
5009
+ sectionDrawDurationMs,
5010
+ setSectionDrawDurationMs,
5011
+ "Animation duration in milliseconds.",
4410
5012
  {
4411
- value: canvasGridStyle,
4412
- onChange: (e) => setCanvasGridStyle(e.target.value),
4413
- style: selectStyle2,
4414
- disabled: !gridEnabled || !showCanvasGrid,
4415
- children: [
4416
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "solid", children: "Solid" }),
4417
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "dashed", children: "Dashed" }),
4418
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "dotted", children: "Dotted" })
4419
- ]
5013
+ min: 100,
5014
+ max: 3e3,
5015
+ step: 10,
5016
+ valueFormatter: (n) => `${Math.round(n)}ms`,
5017
+ displayAsKnob: true,
5018
+ compactKnobLayout: true
5019
+ }
5020
+ ),
5021
+ renderRange(
5022
+ "Center pull",
5023
+ sectionDrawCenterPullPct,
5024
+ setSectionDrawCenterPullPct,
5025
+ "How strongly section draw focus moves toward section center.",
5026
+ {
5027
+ min: 0,
5028
+ max: 100,
5029
+ step: 1,
5030
+ valueFormatter: (n) => `${Math.round(n)}%`,
5031
+ displayAsKnob: true,
5032
+ compactKnobLayout: true
5033
+ }
5034
+ ),
5035
+ renderRange(
5036
+ "Zoom boost",
5037
+ sectionDrawZoomBoostPct,
5038
+ setSectionDrawZoomBoostPct,
5039
+ "Additional zoom applied during section draw focus.",
5040
+ {
5041
+ min: 0,
5042
+ max: 50,
5043
+ step: 1,
5044
+ valueFormatter: (n) => `${Math.round(n)}%`,
5045
+ displayAsKnob: true,
5046
+ compactKnobLayout: true
5047
+ }
5048
+ ),
5049
+ renderRange(
5050
+ "Overshoot",
5051
+ sectionDrawOvershootPct,
5052
+ setSectionDrawOvershootPct,
5053
+ "Spring amount near the end of section auto-focus.",
5054
+ {
5055
+ min: 0,
5056
+ max: 180,
5057
+ step: 1,
5058
+ valueFormatter: (n) => `${Math.round(n)}%`,
5059
+ displayAsKnob: true,
5060
+ compactKnobLayout: true
4420
5061
  }
4421
5062
  )
4422
5063
  ] }),
4423
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
4424
- renderSwitch("Section grid", showSectionGrid, () => setShowSectionGrid((v) => !v)),
4425
- /* @__PURE__ */ jsxRuntime.jsxs(
4426
- "select",
5064
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__motion-group-divider" }),
5065
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__option-card-title seatmap-editor__option-card-title--subtle", children: "Fit" }),
5066
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__motion-control-grid is-knob-grid", children: [
5067
+ renderRange(
5068
+ "Duration",
5069
+ fitViewDurationMs,
5070
+ setFitViewDurationMs,
5071
+ "Animation duration for Fit action.",
4427
5072
  {
4428
- value: sectionGridStyle,
4429
- onChange: (e) => setSectionGridStyle(e.target.value),
4430
- style: selectStyle2,
4431
- disabled: !gridEnabled || !showSectionGrid,
4432
- children: [
4433
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "dots", children: "Dots" }),
4434
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "cross", children: "Cross" })
4435
- ]
5073
+ min: 100,
5074
+ max: 3e3,
5075
+ step: 10,
5076
+ valueFormatter: (n) => `${Math.round(n)}ms`,
5077
+ displayAsKnob: true,
5078
+ compactKnobLayout: true
5079
+ }
5080
+ ),
5081
+ renderRange(
5082
+ "Overshoot",
5083
+ fitViewOvershootPct,
5084
+ setFitViewOvershootPct,
5085
+ "Spring amount near the end of Fit movement.",
5086
+ {
5087
+ min: 0,
5088
+ max: 180,
5089
+ step: 1,
5090
+ valueFormatter: (n) => `${Math.round(n)}%`,
5091
+ displayAsKnob: true,
5092
+ compactKnobLayout: true
4436
5093
  }
4437
5094
  )
4438
- ] })
4439
- ]
4440
- }
5095
+ ] }),
5096
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__motion-group-divider" }),
5097
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__option-card-title seatmap-editor__option-card-title--subtle", children: "Pan" }),
5098
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__motion-control-grid is-knob-grid", children: [
5099
+ renderRange(
5100
+ "Inertia carry",
5101
+ panInertiaCarryPct,
5102
+ setPanInertiaCarryPct,
5103
+ "Velocity retained at pan release.",
5104
+ {
5105
+ min: 0,
5106
+ max: 95,
5107
+ step: 1,
5108
+ valueFormatter: (n) => `${Math.round(n)}%`,
5109
+ displayAsKnob: true,
5110
+ compactKnobLayout: true
5111
+ }
5112
+ ),
5113
+ renderRange(
5114
+ "Inertia friction",
5115
+ panInertiaFrictionPct,
5116
+ setPanInertiaFrictionPct,
5117
+ "Per-frame damping (higher = longer glide).",
5118
+ {
5119
+ min: 70,
5120
+ max: 99,
5121
+ step: 1,
5122
+ valueFormatter: (n) => `${Math.round(n)}%`,
5123
+ displayAsKnob: true,
5124
+ compactKnobLayout: true
5125
+ }
5126
+ ),
5127
+ renderRange(
5128
+ "Stop speed",
5129
+ panInertiaMinSpeedMilli,
5130
+ setPanInertiaMinSpeedMilli,
5131
+ "Stop threshold in px/ms x1000.",
5132
+ {
5133
+ min: 1,
5134
+ max: 50,
5135
+ step: 1,
5136
+ valueFormatter: (n) => `${Math.round(n)}`,
5137
+ displayAsKnob: true,
5138
+ compactKnobLayout: true
5139
+ }
5140
+ ),
5141
+ renderRange(
5142
+ "Velocity blend",
5143
+ panVelocityBlendPct,
5144
+ setPanVelocityBlendPct,
5145
+ "How quickly release velocity follows latest drag samples.",
5146
+ {
5147
+ min: 5,
5148
+ max: 95,
5149
+ step: 1,
5150
+ valueFormatter: (n) => `${Math.round(n)}%`,
5151
+ displayAsKnob: true,
5152
+ compactKnobLayout: true
5153
+ }
5154
+ ),
5155
+ renderRange(
5156
+ "Stop delta",
5157
+ panStopDeltaMilli,
5158
+ setPanStopDeltaMilli,
5159
+ "Treat movement below this px x1000 as stopped while dragging.",
5160
+ {
5161
+ min: 0,
5162
+ max: 4e3,
5163
+ step: 10,
5164
+ valueFormatter: (n) => `${Math.round(n)}`,
5165
+ displayAsKnob: true,
5166
+ compactKnobLayout: true
5167
+ }
5168
+ ),
5169
+ renderRange(
5170
+ "Release idle",
5171
+ panReleaseIdleMs,
5172
+ setPanReleaseIdleMs,
5173
+ "If pointer pauses this long before release, inertia is dropped.",
5174
+ {
5175
+ min: 0,
5176
+ max: 400,
5177
+ step: 5,
5178
+ valueFormatter: (n) => `${Math.round(n)}ms`,
5179
+ displayAsKnob: true,
5180
+ compactKnobLayout: true
5181
+ }
5182
+ )
5183
+ ] }),
5184
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__motion-group-divider" }),
5185
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__option-card-title seatmap-editor__option-card-title--subtle", children: "Pointer scroll zoom" }),
5186
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__motion-control-grid is-knob-grid", children: [
5187
+ renderRange(
5188
+ "Duration",
5189
+ pointerScrollZoomDurationMs,
5190
+ setPointerScrollZoomDurationMs,
5191
+ "Wheel zoom easing duration.",
5192
+ {
5193
+ min: 60,
5194
+ max: 600,
5195
+ step: 5,
5196
+ valueFormatter: (n) => `${Math.round(n)}ms`,
5197
+ displayAsKnob: true,
5198
+ compactKnobLayout: true
5199
+ }
5200
+ ),
5201
+ renderRange(
5202
+ "Strength",
5203
+ pointerScrollZoomStrengthPct,
5204
+ setPointerScrollZoomStrengthPct,
5205
+ "Per-frame zoom blend amount (higher = snappier response).",
5206
+ {
5207
+ min: 8,
5208
+ max: 55,
5209
+ step: 1,
5210
+ valueFormatter: (n) => `${Math.round(n)}%`,
5211
+ displayAsKnob: true,
5212
+ compactKnobLayout: true
5213
+ }
5214
+ ),
5215
+ renderRange(
5216
+ "Sensitivity",
5217
+ pointerScrollZoomDeltaDivisor,
5218
+ setPointerScrollZoomDeltaDivisor,
5219
+ "Wheel delta divisor (higher = less sensitive).",
5220
+ {
5221
+ min: 250,
5222
+ max: 1400,
5223
+ step: 10,
5224
+ valueFormatter: (n) => `${Math.round(n)}`,
5225
+ displayAsKnob: true,
5226
+ compactKnobLayout: true
5227
+ }
5228
+ )
5229
+ ] }),
5230
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__motion-advanced-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
5231
+ "button",
5232
+ {
5233
+ type: "button",
5234
+ className: "seatmap-editor__panel-button seatmap-editor__panel-button--tiny",
5235
+ onClick: handleApplyAdvancedToBasic,
5236
+ title: "Recalculate basic knobs from advanced settings",
5237
+ children: "Apply to basic"
5238
+ }
5239
+ ) })
5240
+ ] })
5241
+ ] }),
5242
+ `seatmap-editor__option-card--motion${useAdvancedMotion ? " is-advanced-open" : ""}`
4441
5243
  );
4442
5244
  if (activeToolName === "add-section") {
4443
- return /* @__PURE__ */ jsxRuntime.jsx(
4444
- "div",
4445
- {
4446
- style: {
4447
- position: "absolute",
4448
- top: 12,
4449
- left: 12,
4450
- right: 12,
4451
- zIndex: 20,
4452
- pointerEvents: "none"
4453
- },
4454
- children: /* @__PURE__ */ jsxRuntime.jsxs(
4455
- "div",
5245
+ return renderOverlay(
5246
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5247
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__tool-options-title", children: "Tool Options" }),
5248
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__tool-options-divider" }),
5249
+ renderOptionCard("Section", /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__option-row", children: [
5250
+ /* @__PURE__ */ jsxRuntime.jsx(
5251
+ "button",
5252
+ {
5253
+ onClick: () => handleSectionToolVariantChange("section", "rectangle"),
5254
+ className: `seatmap-editor__segmented-button${sectionKind === "section" && sectionMode === "rectangle" ? " is-active" : ""}`,
5255
+ children: "Rectangle"
5256
+ }
5257
+ ),
5258
+ /* @__PURE__ */ jsxRuntime.jsx(
5259
+ "button",
5260
+ {
5261
+ onClick: () => handleSectionToolVariantChange("section", "polygon"),
5262
+ className: `seatmap-editor__segmented-button${sectionKind === "section" && sectionMode === "polygon" ? " is-active" : ""}`,
5263
+ children: "Polygon"
5264
+ }
5265
+ )
5266
+ ] })),
5267
+ renderOptionCard("Stage", /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__option-row", children: [
5268
+ /* @__PURE__ */ jsxRuntime.jsx(
5269
+ "button",
5270
+ {
5271
+ onClick: () => handleSectionToolVariantChange("stage", "rectangle"),
5272
+ className: `seatmap-editor__segmented-button${sectionKind === "stage" && sectionMode === "rectangle" ? " is-active" : ""}`,
5273
+ children: "Rectangle"
5274
+ }
5275
+ ),
5276
+ /* @__PURE__ */ jsxRuntime.jsx(
5277
+ "button",
5278
+ {
5279
+ onClick: () => handleSectionToolVariantChange("stage", "polygon"),
5280
+ className: `seatmap-editor__segmented-button${sectionKind === "stage" && sectionMode === "polygon" ? " is-active" : ""}`,
5281
+ children: "Polygon"
5282
+ }
5283
+ )
5284
+ ] })),
5285
+ renderOptionCard("Dancefloor", /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__option-row", children: [
5286
+ /* @__PURE__ */ jsxRuntime.jsx(
5287
+ "button",
5288
+ {
5289
+ onClick: () => handleSectionToolVariantChange("dancefloor", "rectangle"),
5290
+ className: `seatmap-editor__segmented-button${sectionKind === "dancefloor" && sectionMode === "rectangle" ? " is-active" : ""}`,
5291
+ children: "Rectangle"
5292
+ }
5293
+ ),
5294
+ /* @__PURE__ */ jsxRuntime.jsx(
5295
+ "button",
5296
+ {
5297
+ onClick: () => handleSectionToolVariantChange("dancefloor", "polygon"),
5298
+ className: `seatmap-editor__segmented-button${sectionKind === "dancefloor" && sectionMode === "polygon" ? " is-active" : ""}`,
5299
+ children: "Polygon"
5300
+ }
5301
+ )
5302
+ ] })),
5303
+ renderOptionCard("Section resize", /* @__PURE__ */ jsxRuntime.jsx(
5304
+ "button",
4456
5305
  {
4457
- style: {
4458
- display: "flex",
4459
- alignItems: "center",
4460
- gap: 10,
4461
- padding: "8px 12px",
4462
- border: "1px solid #3a3a5a",
4463
- borderRadius: 8,
4464
- background: "rgba(21, 21, 40, 0.92)",
4465
- backdropFilter: "blur(2px)",
4466
- pointerEvents: "auto"
4467
- },
4468
- onPointerDown: (e) => e.stopPropagation(),
4469
- onPointerMove: (e) => e.stopPropagation(),
4470
- onPointerUp: (e) => e.stopPropagation(),
4471
- children: [
5306
+ onClick: () => handleToggleSectionResize(true),
5307
+ className: `seatmap-editor__segmented-button${sectionResizeEnabled ? " is-active" : ""}`,
5308
+ title: "Enable section corner/side resizing",
5309
+ children: sectionResizeEnabled ? "Resize On" : "Resize Off"
5310
+ }
5311
+ )),
5312
+ renderOptionCard("Auto focus", renderSwitch(
5313
+ "Zoom to new section",
5314
+ autoFocusNewSection,
5315
+ () => setAutoFocusNewSection((current) => !current)
5316
+ )),
5317
+ isGridOptionsOpen && renderGridOptionsCard(),
5318
+ isEditorSettingsOpen && renderMotionSettingsCard()
5319
+ ] })
5320
+ );
5321
+ }
5322
+ if (activeToolName === "select") {
5323
+ return renderOverlay(
5324
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5325
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__tool-options-title", children: "Tool Options" }),
5326
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__tool-options-divider" }),
5327
+ renderOptionCard("Section resize", /* @__PURE__ */ jsxRuntime.jsx(
5328
+ "button",
5329
+ {
5330
+ onClick: () => handleToggleSectionResize(false),
5331
+ className: `seatmap-editor__segmented-button${sectionResizeEnabled ? " is-active" : ""}`,
5332
+ children: sectionResizeEnabled ? "Resize On" : "Resize Off"
5333
+ }
5334
+ )),
5335
+ isGridOptionsOpen && renderGridOptionsCard(),
5336
+ isEditorSettingsOpen && renderMotionSettingsCard()
5337
+ ] })
5338
+ );
5339
+ }
5340
+ if (activeToolName !== "add-row") {
5341
+ if (!isGridOptionsOpen && !isEditorSettingsOpen) return null;
5342
+ return renderOverlay(
5343
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5344
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__tool-options-title", children: "Tool Options" }),
5345
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__tool-options-divider" }),
5346
+ isGridOptionsOpen && renderGridOptionsCard(),
5347
+ isEditorSettingsOpen && renderMotionSettingsCard()
5348
+ ] })
5349
+ );
5350
+ }
5351
+ return renderOverlay(
5352
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5353
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "seatmap-editor__tool-options-title", children: "Tool Options" }),
5354
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__tool-options-divider" }),
5355
+ renderOptionCard("Row layout", /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__row-layout-body", children: [
5356
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__row-layout-controls", children: [
5357
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "seatmap-editor__label seatmap-editor__option-row", children: [
5358
+ "Seats:",
5359
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__input-stepper", children: [
4472
5360
  /* @__PURE__ */ jsxRuntime.jsx(
4473
- "span",
5361
+ "input",
4474
5362
  {
4475
- style: {
4476
- color: "#c7c7df",
4477
- fontSize: 12,
4478
- fontFamily: "system-ui",
4479
- fontWeight: 600,
4480
- letterSpacing: 0.2
4481
- },
4482
- children: "Tool Options"
5363
+ type: "number",
5364
+ min: 1,
5365
+ max: 100,
5366
+ value: seatsPerRow,
5367
+ onChange: (e) => handleSeatsPerRowChange(Math.max(1, Math.min(100, parseInt(e.target.value) || 1))),
5368
+ className: "seatmap-editor__input seatmap-editor__input--stepper"
4483
5369
  }
4484
5370
  ),
4485
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 1, height: 18, background: "#3a3a5a" } }),
4486
- /* @__PURE__ */ jsxRuntime.jsxs(
4487
- "div",
5371
+ /* @__PURE__ */ jsxRuntime.jsx(
5372
+ "button",
4488
5373
  {
4489
- style: {
4490
- display: "flex",
4491
- flexDirection: "column",
4492
- alignItems: "flex-start",
4493
- gap: 8,
4494
- padding: "8px 10px",
4495
- border: "1px solid #3a3a5a",
4496
- borderRadius: 6,
4497
- background: "rgba(42, 42, 74, 0.65)"
4498
- },
4499
- children: [
4500
- /* @__PURE__ */ jsxRuntime.jsx(
4501
- "span",
4502
- {
4503
- style: {
4504
- color: "#c7c7df",
4505
- fontSize: 12,
4506
- fontFamily: "system-ui",
4507
- fontWeight: 600
4508
- },
4509
- children: "Section shape"
4510
- }
4511
- ),
4512
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
4513
- /* @__PURE__ */ jsxRuntime.jsx(
4514
- "button",
4515
- {
4516
- onClick: () => handleSectionModeChange("rectangle"),
4517
- style: {
4518
- padding: "4px 8px",
4519
- borderRadius: 6,
4520
- border: "1px solid #3a3a5a",
4521
- background: sectionMode === "rectangle" ? "#4a4a7a" : "#2a2a4a",
4522
- color: sectionMode === "rectangle" ? "#ffffff" : "#d0d0e0",
4523
- cursor: "pointer",
4524
- fontSize: 12,
4525
- fontFamily: "system-ui",
4526
- fontWeight: 600
4527
- },
4528
- children: "Rectangle"
4529
- }
4530
- ),
4531
- /* @__PURE__ */ jsxRuntime.jsx(
4532
- "button",
4533
- {
4534
- onClick: () => handleSectionModeChange("polygon"),
4535
- style: {
4536
- padding: "4px 8px",
4537
- borderRadius: 6,
4538
- border: "1px solid #3a3a5a",
4539
- background: sectionMode === "polygon" ? "#4a4a7a" : "#2a2a4a",
4540
- color: sectionMode === "polygon" ? "#ffffff" : "#d0d0e0",
4541
- cursor: "pointer",
4542
- fontSize: 12,
4543
- fontFamily: "system-ui",
4544
- fontWeight: 600
4545
- },
4546
- children: "Polygon"
4547
- }
4548
- )
4549
- ] })
4550
- ]
5374
+ type: "button",
5375
+ className: "seatmap-editor__stepper-button",
5376
+ "aria-label": "Increase seats per row",
5377
+ onClick: () => handleSeatsPerRowChange(seatsPerRow + 1),
5378
+ children: "+"
4551
5379
  }
4552
5380
  ),
4553
- /* @__PURE__ */ jsxRuntime.jsxs(
4554
- "div",
5381
+ /* @__PURE__ */ jsxRuntime.jsx(
5382
+ "button",
4555
5383
  {
4556
- style: {
4557
- display: "flex",
4558
- flexDirection: "column",
4559
- alignItems: "flex-start",
4560
- gap: 8,
4561
- padding: "8px 10px",
4562
- border: "1px solid #3a3a5a",
4563
- borderRadius: 6,
4564
- background: "rgba(42, 42, 74, 0.65)"
4565
- },
4566
- children: [
4567
- /* @__PURE__ */ jsxRuntime.jsx(
4568
- "span",
4569
- {
4570
- style: {
4571
- color: "#c7c7df",
4572
- fontSize: 12,
4573
- fontFamily: "system-ui",
4574
- fontWeight: 600
4575
- },
4576
- children: "Section resize"
4577
- }
4578
- ),
4579
- /* @__PURE__ */ jsxRuntime.jsx(
4580
- "button",
4581
- {
4582
- onClick: () => handleToggleSectionResize(true),
4583
- style: {
4584
- padding: "4px 8px",
4585
- borderRadius: 6,
4586
- border: "1px solid #3a3a5a",
4587
- background: sectionResizeEnabled ? "#8a6a32" : "#2a2a4a",
4588
- color: sectionResizeEnabled ? "#fff3d8" : "#d0d0e0",
4589
- cursor: "pointer",
4590
- fontSize: 12,
4591
- fontFamily: "system-ui",
4592
- fontWeight: 600
4593
- },
4594
- title: "Enable section corner/side resizing",
4595
- children: sectionResizeEnabled ? "Resize On" : "Resize Off"
4596
- }
4597
- )
4598
- ]
5384
+ type: "button",
5385
+ className: "seatmap-editor__stepper-button",
5386
+ "aria-label": "Decrease seats per row",
5387
+ onClick: () => handleSeatsPerRowChange(seatsPerRow - 1),
5388
+ children: "-"
4599
5389
  }
4600
- ),
4601
- isGridOptionsOpen && renderGridOptionsCard()
4602
- ]
4603
- }
4604
- )
4605
- }
4606
- );
4607
- }
4608
- if (activeToolName === "select") {
4609
- return /* @__PURE__ */ jsxRuntime.jsx(
4610
- "div",
4611
- {
4612
- style: {
4613
- position: "absolute",
4614
- top: 12,
4615
- left: 12,
4616
- right: 12,
4617
- zIndex: 20,
4618
- pointerEvents: "none"
4619
- },
4620
- children: /* @__PURE__ */ jsxRuntime.jsxs(
4621
- "div",
4622
- {
4623
- style: {
4624
- display: "flex",
4625
- alignItems: "center",
4626
- gap: 10,
4627
- padding: "8px 12px",
4628
- border: "1px solid #3a3a5a",
4629
- borderRadius: 8,
4630
- background: "rgba(21, 21, 40, 0.92)",
4631
- backdropFilter: "blur(2px)",
4632
- pointerEvents: "auto"
4633
- },
4634
- onPointerDown: (e) => e.stopPropagation(),
4635
- onPointerMove: (e) => e.stopPropagation(),
4636
- onPointerUp: (e) => e.stopPropagation(),
4637
- children: [
5390
+ )
5391
+ ] })
5392
+ ] }),
5393
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "seatmap-editor__label seatmap-editor__option-row", children: [
5394
+ "Rows:",
5395
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__input-stepper", children: [
4638
5396
  /* @__PURE__ */ jsxRuntime.jsx(
4639
- "span",
5397
+ "input",
4640
5398
  {
4641
- style: {
4642
- color: "#c7c7df",
4643
- fontSize: 12,
4644
- fontFamily: "system-ui",
4645
- fontWeight: 600
4646
- },
4647
- children: "Tool Options"
5399
+ type: "number",
5400
+ min: 1,
5401
+ max: 100,
5402
+ value: rowsCount,
5403
+ onChange: (e) => handleRowsCountChange(Math.max(1, Math.min(100, parseInt(e.target.value) || 1))),
5404
+ className: "seatmap-editor__input seatmap-editor__input--stepper"
4648
5405
  }
4649
5406
  ),
4650
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 1, height: 18, background: "#3a3a5a" } }),
4651
- /* @__PURE__ */ jsxRuntime.jsxs(
4652
- "div",
5407
+ /* @__PURE__ */ jsxRuntime.jsx(
5408
+ "button",
4653
5409
  {
4654
- style: {
4655
- display: "flex",
4656
- flexDirection: "column",
4657
- alignItems: "flex-start",
4658
- gap: 8,
4659
- padding: "8px 10px",
4660
- border: "1px solid #3a3a5a",
4661
- borderRadius: 6,
4662
- background: "rgba(42, 42, 74, 0.65)"
4663
- },
4664
- children: [
4665
- /* @__PURE__ */ jsxRuntime.jsx(
4666
- "span",
4667
- {
4668
- style: {
4669
- color: "#c7c7df",
4670
- fontSize: 12,
4671
- fontFamily: "system-ui",
4672
- fontWeight: 600
4673
- },
4674
- children: "Section resize"
4675
- }
4676
- ),
4677
- /* @__PURE__ */ jsxRuntime.jsx(
4678
- "button",
4679
- {
4680
- onClick: () => handleToggleSectionResize(false),
4681
- style: {
4682
- padding: "4px 8px",
4683
- borderRadius: 6,
4684
- border: "1px solid #3a3a5a",
4685
- background: sectionResizeEnabled ? "#8a6a32" : "#2a2a4a",
4686
- color: sectionResizeEnabled ? "#fff3d8" : "#d0d0e0",
4687
- cursor: "pointer",
4688
- fontSize: 12,
4689
- fontFamily: "system-ui",
4690
- fontWeight: 600
4691
- },
4692
- children: sectionResizeEnabled ? "Resize On" : "Resize Off"
4693
- }
4694
- )
4695
- ]
5410
+ type: "button",
5411
+ className: "seatmap-editor__stepper-button",
5412
+ "aria-label": "Increase rows",
5413
+ onClick: () => handleRowsCountChange(rowsCount + 1),
5414
+ children: "+"
4696
5415
  }
4697
5416
  ),
4698
- isGridOptionsOpen && renderGridOptionsCard()
4699
- ]
4700
- }
4701
- )
4702
- }
4703
- );
4704
- }
4705
- if (activeToolName !== "add-row") {
4706
- if (!isGridOptionsOpen) return null;
4707
- return /* @__PURE__ */ jsxRuntime.jsx(
4708
- "div",
4709
- {
4710
- style: {
4711
- position: "absolute",
4712
- top: 12,
4713
- left: 12,
4714
- right: 12,
4715
- zIndex: 20,
4716
- pointerEvents: "none"
4717
- },
4718
- children: /* @__PURE__ */ jsxRuntime.jsxs(
4719
- "div",
4720
- {
4721
- style: {
4722
- display: "flex",
4723
- alignItems: "center",
4724
- gap: 10,
4725
- padding: "8px 12px",
4726
- border: "1px solid #3a3a5a",
4727
- borderRadius: 8,
4728
- background: "rgba(21, 21, 40, 0.92)",
4729
- backdropFilter: "blur(2px)",
4730
- pointerEvents: "auto"
4731
- },
4732
- onPointerDown: (e) => e.stopPropagation(),
4733
- onPointerMove: (e) => e.stopPropagation(),
4734
- onPointerUp: (e) => e.stopPropagation(),
4735
- children: [
4736
5417
  /* @__PURE__ */ jsxRuntime.jsx(
4737
- "span",
5418
+ "button",
4738
5419
  {
4739
- style: {
4740
- color: "#c7c7df",
4741
- fontSize: 12,
4742
- fontFamily: "system-ui",
4743
- fontWeight: 600
4744
- },
4745
- children: "Tool Options"
5420
+ type: "button",
5421
+ className: "seatmap-editor__stepper-button",
5422
+ "aria-label": "Decrease rows",
5423
+ onClick: () => handleRowsCountChange(rowsCount - 1),
5424
+ children: "-"
4746
5425
  }
4747
- ),
4748
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 1, height: 18, background: "#3a3a5a" } }),
4749
- renderGridOptionsCard()
4750
- ]
4751
- }
4752
- )
4753
- }
4754
- );
4755
- }
4756
- return /* @__PURE__ */ jsxRuntime.jsx(
4757
- "div",
4758
- {
4759
- style: {
4760
- position: "absolute",
4761
- top: 12,
4762
- left: 12,
4763
- right: 12,
4764
- zIndex: 20,
4765
- pointerEvents: "none"
4766
- },
4767
- children: /* @__PURE__ */ jsxRuntime.jsxs(
4768
- "div",
4769
- {
4770
- style: {
4771
- display: "flex",
4772
- alignItems: "center",
4773
- gap: 10,
4774
- padding: "8px 12px",
4775
- border: "1px solid #3a3a5a",
4776
- borderRadius: 8,
4777
- background: "rgba(21, 21, 40, 0.92)",
4778
- backdropFilter: "blur(2px)",
4779
- pointerEvents: "auto"
4780
- },
4781
- onPointerDown: (e) => e.stopPropagation(),
4782
- onPointerMove: (e) => e.stopPropagation(),
4783
- onPointerUp: (e) => e.stopPropagation(),
4784
- children: [
4785
- /* @__PURE__ */ jsxRuntime.jsx(
4786
- "span",
4787
- {
4788
- style: {
4789
- color: "#c7c7df",
4790
- fontSize: 12,
4791
- fontFamily: "system-ui",
4792
- fontWeight: 600,
4793
- letterSpacing: 0.2
4794
- },
4795
- children: "Tool Options"
4796
- }
4797
- ),
4798
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 1, height: 18, background: "#3a3a5a" } }),
4799
- /* @__PURE__ */ jsxRuntime.jsxs(
4800
- "div",
5426
+ )
5427
+ ] })
5428
+ ] }),
5429
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__option-card-title", children: [
5430
+ "Total seats to add: ",
5431
+ seatsPerRow * Math.max(1, rowsCount)
5432
+ ] })
5433
+ ] }),
5434
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__row-layout-separator" }),
5435
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__row-presets", children: /* @__PURE__ */ jsxRuntime.jsx("table", { className: "seatmap-editor__row-presets-table", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rowPresetRows.map((rowPreset) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
5436
+ /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "row", className: "seatmap-editor__row-preset-row-label", children: rowPreset }),
5437
+ rowPresetSeats.map((seatPreset) => {
5438
+ const isActive = rowsCount === rowPreset && seatsPerRow === seatPreset;
5439
+ return /* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx(
5440
+ "button",
4801
5441
  {
4802
- style: {
4803
- display: "flex",
4804
- flexDirection: "column",
4805
- alignItems: "flex-start",
4806
- gap: 8,
4807
- padding: "8px 10px",
4808
- border: "1px solid #3a3a5a",
4809
- borderRadius: 6,
4810
- background: "rgba(42, 42, 74, 0.65)"
5442
+ type: "button",
5443
+ className: `seatmap-editor__row-preset-button${isActive ? " is-active" : ""}`,
5444
+ onClick: () => {
5445
+ handleRowsCountChange(rowPreset);
5446
+ handleSeatsPerRowChange(seatPreset);
4811
5447
  },
4812
- children: [
4813
- /* @__PURE__ */ jsxRuntime.jsx(
4814
- "span",
4815
- {
4816
- style: {
4817
- color: "#c7c7df",
4818
- fontSize: 12,
4819
- fontFamily: "system-ui",
4820
- fontWeight: 600
4821
- },
4822
- children: "Row layout"
4823
- }
4824
- ),
4825
- /* @__PURE__ */ jsxRuntime.jsxs(
4826
- "label",
4827
- {
4828
- style: {
4829
- color: "#9e9e9e",
4830
- fontSize: 12,
4831
- fontFamily: "system-ui",
4832
- display: "flex",
4833
- alignItems: "center",
4834
- gap: 6
4835
- },
4836
- children: [
4837
- "Seats/row:",
4838
- /* @__PURE__ */ jsxRuntime.jsx(
4839
- "input",
4840
- {
4841
- type: "number",
4842
- min: 1,
4843
- max: 100,
4844
- value: seatsPerRow,
4845
- onChange: (e) => handleSeatsPerRowChange(Math.max(1, Math.min(100, parseInt(e.target.value) || 1))),
4846
- style: {
4847
- width: 56,
4848
- padding: "3px 6px",
4849
- background: "#2a2a4a",
4850
- border: "1px solid #3a3a5a",
4851
- borderRadius: 4,
4852
- color: "#e0e0e0",
4853
- fontSize: 13,
4854
- fontFamily: "system-ui"
4855
- }
4856
- }
4857
- )
4858
- ]
4859
- }
4860
- ),
4861
- /* @__PURE__ */ jsxRuntime.jsxs(
4862
- "label",
4863
- {
4864
- style: {
4865
- color: "#9e9e9e",
4866
- fontSize: 12,
4867
- fontFamily: "system-ui",
4868
- display: "flex",
4869
- alignItems: "center",
4870
- gap: 6
4871
- },
4872
- children: [
4873
- "Rows:",
4874
- /* @__PURE__ */ jsxRuntime.jsx(
4875
- "input",
4876
- {
4877
- type: "number",
4878
- min: 1,
4879
- max: 100,
4880
- value: rowsCount,
4881
- onChange: (e) => handleRowsCountChange(Math.max(1, Math.min(100, parseInt(e.target.value) || 1))),
4882
- style: {
4883
- width: 56,
4884
- padding: "3px 6px",
4885
- background: "#2a2a4a",
4886
- border: "1px solid #3a3a5a",
4887
- borderRadius: 4,
4888
- color: "#e0e0e0",
4889
- fontSize: 13,
4890
- fontFamily: "system-ui"
4891
- }
4892
- }
4893
- )
4894
- ]
4895
- }
4896
- ),
4897
- /* @__PURE__ */ jsxRuntime.jsxs(
4898
- "div",
4899
- {
4900
- style: {
4901
- color: "#c7c7df",
4902
- fontSize: 12,
4903
- fontFamily: "system-ui",
4904
- fontWeight: 600
4905
- },
4906
- children: [
4907
- "Total seats to add: ",
4908
- seatsPerRow * Math.max(1, rowsCount)
4909
- ]
4910
- }
4911
- )
4912
- ]
5448
+ children: seatPreset
4913
5449
  }
4914
- ),
4915
- /* @__PURE__ */ jsxRuntime.jsxs(
4916
- "div",
5450
+ ) }, `${rowPreset}-${seatPreset}`);
5451
+ })
5452
+ ] }, `row-preset-${rowPreset}`)) }) }) })
5453
+ ] })),
5454
+ renderOptionCard("Orientation", /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5455
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__option-row seatmap-editor__orientation-toggle", children: [
5456
+ /* @__PURE__ */ jsxRuntime.jsx(
5457
+ "span",
5458
+ {
5459
+ className: `seatmap-editor__orientation-label${rowDirectionArrowMode === "viewer-direction" ? " is-active" : ""}`,
5460
+ children: "Viewer direction"
5461
+ }
5462
+ ),
5463
+ /* @__PURE__ */ jsxRuntime.jsx(
5464
+ "button",
5465
+ {
5466
+ type: "button",
5467
+ role: "switch",
5468
+ "aria-checked": rowDirectionArrowMode === "row-direction",
5469
+ onClick: () => setRowDirectionArrowMode(
5470
+ (mode) => mode === "row-direction" ? "viewer-direction" : "row-direction"
5471
+ ),
5472
+ className: `seatmap-editor__switch-track seatmap-editor__switch-track--wide${rowDirectionArrowMode === "row-direction" ? " is-checked" : ""}`,
5473
+ title: "Toggle arrow orientation mode",
5474
+ children: /* @__PURE__ */ jsxRuntime.jsx(
5475
+ "span",
5476
+ {
5477
+ className: "seatmap-editor__switch-thumb seatmap-editor__switch-thumb--wide",
5478
+ style: {
5479
+ transform: rowDirectionArrowMode === "row-direction" ? "translateX(22px)" : "translateX(0)"
5480
+ }
5481
+ }
5482
+ )
5483
+ }
5484
+ ),
5485
+ /* @__PURE__ */ jsxRuntime.jsx(
5486
+ "span",
5487
+ {
5488
+ className: `seatmap-editor__orientation-label${rowDirectionArrowMode === "row-direction" ? " is-active" : ""}`,
5489
+ children: "Row direction"
5490
+ }
5491
+ )
5492
+ ] }),
5493
+ renderRange(
5494
+ "Orientation",
5495
+ rowOrientationKnobDeg,
5496
+ handleRowOrientationKnobChange,
5497
+ "Drag vertically on the knob to set row orientation.",
5498
+ {
5499
+ min: 0,
5500
+ max: 359,
5501
+ step: 1,
5502
+ valueFormatter: (n) => `${Math.round(n)}\xB0`,
5503
+ displayAsKnob: true,
5504
+ knobMode: "dial360",
5505
+ knobFillStartDeg: 0,
5506
+ knobNamespace: "tool",
5507
+ valuePlacement: "knob",
5508
+ knobRightContent: /* @__PURE__ */ jsxRuntime.jsx(
5509
+ "button",
4917
5510
  {
4918
- style: {
4919
- display: "flex",
4920
- flexDirection: "column",
4921
- alignItems: "flex-start",
4922
- gap: 8,
4923
- padding: "8px 10px",
4924
- border: "1px solid #3a3a5a",
4925
- borderRadius: 6,
4926
- background: "rgba(42, 42, 74, 0.65)"
4927
- },
4928
- children: [
4929
- /* @__PURE__ */ jsxRuntime.jsx(
4930
- "span",
4931
- {
4932
- style: {
4933
- color: "#c7c7df",
4934
- fontSize: 12,
4935
- fontFamily: "system-ui",
4936
- fontWeight: 600
4937
- },
4938
- children: "Orientation"
4939
- }
4940
- ),
4941
- /* @__PURE__ */ jsxRuntime.jsxs(
4942
- "label",
4943
- {
4944
- style: {
4945
- color: "#9e9e9e",
4946
- fontSize: 12,
4947
- fontFamily: "system-ui",
4948
- display: "flex",
4949
- alignItems: "center",
4950
- gap: 6
4951
- },
4952
- children: [
4953
- "Orientation:",
4954
- /* @__PURE__ */ jsxRuntime.jsx(
4955
- "input",
4956
- {
4957
- type: "number",
4958
- min: 0,
4959
- max: 359,
4960
- value: rowOrientationDeg,
4961
- onChange: (e) => handleRowOrientationChange(parseInt(e.target.value, 10) || 0),
4962
- style: {
4963
- width: 56,
4964
- padding: "3px 6px",
4965
- background: "#2a2a4a",
4966
- border: "1px solid #3a3a5a",
4967
- borderRadius: 4,
4968
- color: "#e0e0e0",
4969
- fontSize: 13,
4970
- fontFamily: "system-ui"
4971
- }
4972
- }
4973
- ),
4974
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#9e9e9e" }, children: "deg" }),
4975
- /* @__PURE__ */ jsxRuntime.jsx(
4976
- "button",
4977
- {
4978
- onClick: handleRotateRowOrientationQuarterTurn,
4979
- style: {
4980
- padding: "3px 6px",
4981
- borderRadius: 4,
4982
- border: "1px solid #3a3a5a",
4983
- background: "#2a2a4a",
4984
- color: "#d0d0e0",
4985
- cursor: "pointer",
4986
- fontSize: 12,
4987
- fontFamily: "system-ui",
4988
- fontWeight: 600
4989
- },
4990
- title: "Rotate row direction by 90 degrees",
4991
- children: "+90deg"
4992
- }
4993
- )
4994
- ]
4995
- }
4996
- ),
4997
- /* @__PURE__ */ jsxRuntime.jsx(
4998
- "div",
4999
- {
5000
- style: {
5001
- color: "#9e9e9e",
5002
- fontSize: 11,
5003
- fontFamily: "system-ui"
5004
- },
5005
- children: "0deg = up, 90deg = right"
5006
- }
5007
- )
5008
- ]
5511
+ onClick: handleRotateRowOrientationQuarterTurn,
5512
+ className: "seatmap-editor__segmented-button",
5513
+ title: "Rotate row direction by 90 degrees",
5514
+ children: "+90\xB0"
5009
5515
  }
5010
- ),
5011
- isGridOptionsOpen && renderGridOptionsCard()
5012
- ]
5013
- }
5014
- )
5015
- }
5516
+ )
5517
+ }
5518
+ ),
5519
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__hint-text", children: rowDirectionArrowMode === "row-direction" ? "Arrow: row direction (viewer +90\xB0)" : "Arrow: viewer direction (0\xB0 = up, 90\xB0 = right)" })
5520
+ ] })),
5521
+ isGridOptionsOpen && renderGridOptionsCard(),
5522
+ isEditorSettingsOpen && renderMotionSettingsCard()
5523
+ ] })
5016
5524
  );
5017
5525
  };
5018
5526
  react.useEffect(() => {
5019
5527
  const isTyping = () => {
5020
- const tag = document.activeElement?.tagName;
5021
- return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
5528
+ const activeElement = document.activeElement;
5529
+ if (!activeElement) return false;
5530
+ const tag = activeElement.tagName;
5531
+ return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || activeElement.isContentEditable;
5022
5532
  };
5023
5533
  const handler = (e) => {
5024
5534
  if ((e.metaKey || e.ctrlKey) && e.key === "z") {
@@ -5028,8 +5538,18 @@ function EditorInner({
5028
5538
  return;
5029
5539
  }
5030
5540
  if (isTyping()) return;
5031
- if (e.key === "v" || e.key === "1") setActiveTool("select");
5032
- if (e.key === "h" || e.key === "2") setActiveTool("pan");
5541
+ if (e.key === "Delete" || e.key === "Backspace") {
5542
+ e.preventDefault();
5543
+ handleDeleteSelectedObjects();
5544
+ return;
5545
+ }
5546
+ if (e.key === "Escape" && activeToolName === "add-section") {
5547
+ e.preventDefault();
5548
+ addSectionTool.cancelDrawing();
5549
+ return;
5550
+ }
5551
+ if (e.key === "v" || e.key === "2") setActiveTool("select");
5552
+ if (e.key === "h" || e.key === "1") setActiveTool("pan");
5033
5553
  if (e.key === "s" || e.key === "3") setActiveTool("add-section");
5034
5554
  if (e.key === "r" || e.key === "4") setActiveTool("add-row");
5035
5555
  if (e.key === "a" || e.key === "5") setActiveTool("add-seat");
@@ -5050,7 +5570,7 @@ function EditorInner({
5050
5570
  window.removeEventListener("keydown", handler);
5051
5571
  window.removeEventListener("keyup", upHandler);
5052
5572
  };
5053
- }, [setActiveTool, activeToolName]);
5573
+ }, [setActiveTool, activeToolName, handleDeleteSelectedObjects, addSectionTool]);
5054
5574
  const toToolPointerEvent = react.useCallback(
5055
5575
  (e) => {
5056
5576
  const rect = e.currentTarget.getBoundingClientRect();
@@ -5097,6 +5617,11 @@ function EditorInner({
5097
5617
  (e) => {
5098
5618
  const toolEvent = toToolPointerEvent(e);
5099
5619
  activeToolRef.current.onPointerMove(toolEvent, viewport, store);
5620
+ const rect = e.currentTarget.getBoundingClientRect();
5621
+ setCursorScreenPoint({
5622
+ x: e.clientX - rect.left,
5623
+ y: e.clientY - rect.top
5624
+ });
5100
5625
  if (activeToolName === "add-row") {
5101
5626
  setRowPreviewPoint({ x: toolEvent.worldX, y: toolEvent.worldY });
5102
5627
  }
@@ -5112,14 +5637,7 @@ function EditorInner({
5112
5637
  (e) => handlePointerRelease(e),
5113
5638
  [handlePointerRelease]
5114
5639
  );
5115
- const sidebarStyle = {
5116
- width: 260,
5117
- background: "#1a1a2e",
5118
- borderLeft: "1px solid #2a2a4a",
5119
- overflowY: "auto",
5120
- flexShrink: 0
5121
- };
5122
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", width: "100%", height: "100%" }, children: [
5640
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor seatmap-editor--root", children: [
5123
5641
  /* @__PURE__ */ jsxRuntime.jsx(
5124
5642
  Toolbar,
5125
5643
  {
@@ -5134,19 +5652,25 @@ function EditorInner({
5134
5652
  onRedo: () => historyRef.current.redo(),
5135
5653
  onFitView: handleFitView,
5136
5654
  onSave: handleSave,
5137
- onLoad: handleLoad
5655
+ onLoad: handleLoad,
5656
+ showHints,
5657
+ onToggleHints: () => setShowHints((current) => !current),
5658
+ isEditorSettingsOpen,
5659
+ onToggleEditorSettings: () => setIsEditorSettingsOpen((current) => !current)
5138
5660
  }
5139
5661
  ),
5140
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flex: 1, overflow: "hidden" }, children: [
5662
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__canvas-layout", children: [
5141
5663
  /* @__PURE__ */ jsxRuntime.jsxs(
5142
5664
  "div",
5143
5665
  {
5144
5666
  ref: canvasAreaRef,
5145
- style: { flex: 1, position: "relative", cursor: (toolMap[activeToolName] ?? selectTool).cursor },
5667
+ className: "seatmap-editor__canvas-area",
5668
+ style: { cursor: (toolMap[activeToolName] ?? selectTool).cursor },
5146
5669
  onPointerDown: handleCanvasPointerDown,
5147
5670
  onPointerMove: handleCanvasPointerMove,
5148
5671
  onPointerUp: handleCanvasPointerUp,
5149
5672
  onPointerCancel: handleCanvasPointerCancel,
5673
+ onPointerLeave: () => setCursorScreenPoint(null),
5150
5674
  children: [
5151
5675
  /* @__PURE__ */ jsxRuntime.jsx(
5152
5676
  seatmapReact.SeatmapCanvas,
@@ -5155,7 +5679,15 @@ function EditorInner({
5155
5679
  showGridLines: gridEnabled && showCanvasGrid,
5156
5680
  showSectionGridDots: gridEnabled && showSectionGrid,
5157
5681
  canvasGridLineStyle: canvasGridStyle,
5158
- sectionGridMarkerStyle: sectionGridStyle
5682
+ sectionGridMarkerStyle: sectionGridStyle,
5683
+ panInertiaJelly,
5684
+ panInertiaCarry: useAdvancedMotion ? panInertiaCarryPct / 100 : void 0,
5685
+ panInertiaFriction: useAdvancedMotion ? panInertiaFrictionPct / 100 : void 0,
5686
+ panInertiaMinSpeed: useAdvancedMotion ? panInertiaMinSpeedMilli / 1e3 : void 0,
5687
+ pointerScrollZoomJelly,
5688
+ pointerScrollZoomDurationMs: useAdvancedMotion ? pointerScrollZoomDurationMs : void 0,
5689
+ pointerScrollZoomStrengthPct: useAdvancedMotion ? pointerScrollZoomStrengthPct : void 0,
5690
+ pointerScrollZoomDeltaDivisor: useAdvancedMotion ? pointerScrollZoomDeltaDivisor : void 0
5159
5691
  }
5160
5692
  ),
5161
5693
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -5178,11 +5710,22 @@ function EditorInner({
5178
5710
  mode: sectionMode,
5179
5711
  viewport
5180
5712
  }
5713
+ ),
5714
+ showHints && sectionHintText && cursorScreenPoint && /* @__PURE__ */ jsxRuntime.jsx(
5715
+ "div",
5716
+ {
5717
+ className: "seatmap-editor__hint-bubble",
5718
+ style: {
5719
+ left: cursorScreenPoint.x + 14,
5720
+ top: cursorScreenPoint.y + 14
5721
+ },
5722
+ children: sectionHintText
5723
+ }
5181
5724
  )
5182
5725
  ]
5183
5726
  }
5184
5727
  ),
5185
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sidebarStyle, children: [
5728
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "seatmap-editor__sidebar", children: [
5186
5729
  /* @__PURE__ */ jsxRuntime.jsx(
5187
5730
  PropertyPanel,
5188
5731
  {
@@ -5199,7 +5742,7 @@ function EditorInner({
5199
5742
  }
5200
5743
  ),
5201
5744
  selectedSeatIds.size === 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5202
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 1, background: "#2a2a4a", margin: "0 16px" } }),
5745
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__sidebar-separator" }),
5203
5746
  /* @__PURE__ */ jsxRuntime.jsx(
5204
5747
  LayerPanel,
5205
5748
  {
@@ -5209,7 +5752,7 @@ function EditorInner({
5209
5752
  onSelectSection: handleSelectSection
5210
5753
  }
5211
5754
  ),
5212
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 1, background: "#2a2a4a", margin: "0 16px" } }),
5755
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__sidebar-separator" }),
5213
5756
  /* @__PURE__ */ jsxRuntime.jsx(
5214
5757
  CategoryManager,
5215
5758
  {
@@ -5221,7 +5764,7 @@ function EditorInner({
5221
5764
  )
5222
5765
  ] }),
5223
5766
  selectedSeatIds.size === 0 && selectedSectionIds.size === 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5224
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 1, background: "#2a2a4a", margin: "0 16px" } }),
5767
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "seatmap-editor__sidebar-separator" }),
5225
5768
  /* @__PURE__ */ jsxRuntime.jsx(
5226
5769
  StatusManager,
5227
5770
  {
@@ -5236,7 +5779,7 @@ function EditorInner({
5236
5779
  ] });
5237
5780
  }
5238
5781
  function SeatmapEditor({ venue, onChange, onSave, fetchCategoryPrices, className }) {
5239
- return /* @__PURE__ */ jsxRuntime.jsx(seatmapReact.SeatmapProvider, { venue, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(EditorInner, { onChange, onSave, fetchCategoryPrices }) }) });
5782
+ return /* @__PURE__ */ jsxRuntime.jsx(seatmapReact.SeatmapProvider, { venue, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: className ? `seatmap-editor__host ${className}` : "seatmap-editor__host", children: /* @__PURE__ */ jsxRuntime.jsx(EditorInner, { onChange, onSave, fetchCategoryPrices }) }) });
5240
5783
  }
5241
5784
  var DrawGATool = class extends BaseTool {
5242
5785
  constructor(history) {