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