@seatlayer/core 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -65,6 +65,7 @@ var init_es = __esm({
65
65
  "picker.seatsLeftInSection.one": "{count} asiento disponible",
66
66
  "picker.seatsLeftInSection.other": "{count} asientos disponibles",
67
67
  "picker.overview": "Descripci\xF3n general",
68
+ "picker.entrance": "Entrada",
68
69
  "picker.tapSeatHint": "Toca cualquier asiento para ver su vista",
69
70
  "picker.ticketTierFor": "Categor\xEDa de entrada para {label}",
70
71
  "picker.viewFromSeat": "Vista desde el asiento {label}",
@@ -123,6 +124,7 @@ var init_de = __esm({
123
124
  "picker.seatsLeftInSection.one": "{count} Platz verf\xFCgbar",
124
125
  "picker.seatsLeftInSection.other": "{count} Pl\xE4tze verf\xFCgbar",
125
126
  "picker.overview": "\xDCbersicht",
127
+ "picker.entrance": "Eingang",
126
128
  "picker.tapSeatHint": "Tippen Sie auf einen Platz, um die Ansicht zu pr\xFCfen",
127
129
  "picker.ticketTierFor": "Ticketklasse f\xFCr {label}",
128
130
  "picker.viewFromSeat": "Ansicht von Platz {label}",
@@ -181,6 +183,7 @@ var init_fr = __esm({
181
183
  "picker.seatsLeftInSection.one": "{count} si\xE8ge restant",
182
184
  "picker.seatsLeftInSection.other": "{count} si\xE8ges restants",
183
185
  "picker.overview": "Vue d'ensemble",
186
+ "picker.entrance": "Entr\xE9e",
184
187
  "picker.tapSeatHint": "Appuyez sur un si\xE8ge pour v\xE9rifier sa vue",
185
188
  "picker.ticketTierFor": "Cat\xE9gorie de billet pour {label}",
186
189
  "picker.viewFromSeat": "Vue depuis le si\xE8ge {label}",
@@ -211,6 +214,7 @@ __export(index_exports, {
211
214
  MAX_EVENT_INVENTORY: () => MAX_EVENT_INVENTORY,
212
215
  MAX_GA_CAPACITY: () => MAX_GA_CAPACITY,
213
216
  PickerController: () => PickerController,
217
+ RENDERED_QUALITY_REPORT_VERSION: () => RENDERED_QUALITY_REPORT_VERSION,
214
218
  SUPPORTED_LOCALES: () => SUPPORTED_LOCALES,
215
219
  SeatmapRenderer: () => SeatmapRenderer,
216
220
  UNGROUPED_ID: () => UNGROUPED_ID,
@@ -238,6 +242,7 @@ __export(index_exports, {
238
242
  generateSeatThumb: () => generateSeatThumb,
239
243
  getLocale: () => getLocale,
240
244
  hiddenObjectIds: () => hiddenObjectIds,
245
+ inspectRenderedQualityEvidence: () => inspectRenderedQualityEvidence,
241
246
  isGaUnitLabel: () => isGaUnitLabel,
242
247
  isSectionHidden: () => isSectionHidden,
243
248
  layerOf: () => layerOf,
@@ -983,20 +988,6 @@ function applyHidden(doc, hidden) {
983
988
  return { ...doc, objects };
984
989
  }
985
990
 
986
- // src/engine/SeatmapRenderer.ts
987
- var import_Core = require("konva/lib/Core");
988
- var import_Stage = require("konva/lib/Stage");
989
- var import_Layer = require("konva/lib/Layer");
990
- var import_Group = require("konva/lib/Group");
991
- var import_Circle = require("konva/lib/shapes/Circle");
992
- var import_Rect = require("konva/lib/shapes/Rect");
993
- var import_Ellipse = require("konva/lib/shapes/Ellipse");
994
- var import_Line = require("konva/lib/shapes/Line");
995
- var import_Text = require("konva/lib/shapes/Text");
996
- var import_Path = require("konva/lib/shapes/Path");
997
- var import_Image = require("konva/lib/shapes/Image");
998
- var import_Shape = require("konva/lib/Shape");
999
-
1000
991
  // src/core/chartRenderRules.ts
1001
992
  var SEAT_LABEL_FONT_SIZE = 7;
1002
993
  var BOOTH_LABEL_FONT_SIZE = 10;
@@ -1053,6 +1044,470 @@ function stateAwareBookableLabelInk(fill, preferred) {
1053
1044
  return darkContrast >= lightContrast ? DARK_BOOKABLE_LABEL_INK : LIGHT_BOOKABLE_LABEL_INK;
1054
1045
  }
1055
1046
 
1047
+ // src/core/renderedQuality.ts
1048
+ var RENDERED_QUALITY_REPORT_VERSION = 3;
1049
+ var MIN_TEXT_CONTRAST = 4.5;
1050
+ var MIN_GRAPHICAL_CONTRAST = 3;
1051
+ var MIN_POINTER_TARGET_PX = 24;
1052
+ var MAX_SAMPLES_PER_FINDING = 20;
1053
+ function visibleWithBox(items) {
1054
+ return items.filter((item) => item.visible && Boolean(item.screenBox));
1055
+ }
1056
+ function intersects(left, right) {
1057
+ return left.x < right.x + right.width && left.x + left.width > right.x && left.y < right.y + right.height && left.y + left.height > right.y;
1058
+ }
1059
+ function minimum(values) {
1060
+ return values.length ? Math.round(Math.min(...values) * 100) / 100 : null;
1061
+ }
1062
+ function finding(code, message, samples) {
1063
+ if (!samples.length) return null;
1064
+ return {
1065
+ code,
1066
+ message,
1067
+ count: samples.length,
1068
+ samples: samples.slice(0, MAX_SAMPLES_PER_FINDING)
1069
+ };
1070
+ }
1071
+ function labelSample(label, measured, minimumValue) {
1072
+ return {
1073
+ primaryId: label.seatId,
1074
+ primaryLabel: label.label,
1075
+ ...measured == null ? {} : { measured: Math.round(measured * 100) / 100 },
1076
+ ...minimumValue == null ? {} : { minimum: minimumValue }
1077
+ };
1078
+ }
1079
+ function hierarchySample(label, measured, minimumValue) {
1080
+ return {
1081
+ primaryId: label.id,
1082
+ primaryLabel: label.label,
1083
+ ...measured == null ? {} : { measured: Math.round(measured * 100) / 100 },
1084
+ ...minimumValue == null ? {} : { minimum: minimumValue }
1085
+ };
1086
+ }
1087
+ function freeTextSample(label, measured, minimumValue) {
1088
+ return {
1089
+ primaryId: label.objectId,
1090
+ primaryLabel: label.text,
1091
+ ...measured == null ? {} : { measured: Math.round(measured * 100) / 100 },
1092
+ ...minimumValue == null ? {} : { minimum: minimumValue }
1093
+ };
1094
+ }
1095
+ function collisionSample(primaryId, primaryLabel, secondaryId, secondaryLabel) {
1096
+ return { primaryId, primaryLabel, secondaryId, secondaryLabel };
1097
+ }
1098
+ function inspectRenderedQualityEvidence(evidence, state = "overview", targetCategoryKey = null) {
1099
+ const bookable = visibleWithBox(evidence.labels);
1100
+ const hierarchy = visibleWithBox(evidence.hierarchyLabels);
1101
+ const freeText = visibleWithBox(evidence.freeTextLabels);
1102
+ const visibleGA = evidence.gaAreas.filter((area) => area.visible);
1103
+ const bookableContrast = bookable.map((label) => renderedTextContrast(label.ink, label.fill) ?? 0);
1104
+ const hierarchyContrast = hierarchy.map((label) => renderedTextContrast(label.ink, label.fill) ?? 0);
1105
+ const freeTextContrast = freeText.map((label) => renderedTextContrast(label.ink, label.background) ?? 0);
1106
+ const gaContrast = visibleGA.map((area) => renderedTextContrast(area.effectiveBackground, evidence.canvasBackground) ?? 0);
1107
+ const targetLabels = targetCategoryKey ? evidence.labels.filter((label) => label.categoryKey === targetCategoryKey) : evidence.labels;
1108
+ const targetGAAreas = targetCategoryKey ? evidence.gaAreas.filter((area) => area.categoryKey === targetCategoryKey) : evidence.gaAreas;
1109
+ const selected = targetLabels.filter((label) => label.selected);
1110
+ const held = targetLabels.filter((label) => label.status === "held");
1111
+ const booked = targetLabels.filter((label) => label.status === "booked");
1112
+ const activePointerTargets = targetLabels.filter((label) => label.pointerTarget.active);
1113
+ const activeCategoryKeys = /* @__PURE__ */ new Set([
1114
+ ...evidence.labels.filter((label) => label.visible && (label.opacity > 0.25 || label.selected || label.status !== "free")).map((label) => label.categoryKey),
1115
+ ...evidence.gaAreas.filter((area) => area.visible && area.opacity > 0.1).map((area) => area.categoryKey)
1116
+ ]);
1117
+ const selectedRingContrast = selected.length ? minimum(selected.flatMap((label) => [
1118
+ renderedTextContrast(evidence.selectionRingColor, evidence.canvasBackground) ?? 0,
1119
+ renderedTextContrast(evidence.selectionRingColor, label.fill) ?? 0
1120
+ ])) : null;
1121
+ const findings = [];
1122
+ const overviewCountSamples = (count, label) => Array.from({ length: count }, (_, index) => ({
1123
+ primaryId: `overview:${index + 1}`,
1124
+ primaryLabel: label
1125
+ }));
1126
+ if (state === "overview" && evidence.rung === "sections") {
1127
+ findings.push(finding(
1128
+ "overview-section-category-paint",
1129
+ "Section overview shells must use the neutral hierarchy palette, not category paint",
1130
+ overviewCountSamples(evidence.overviewStyle.categoryPaintedSectionShells, "category-painted section shell")
1131
+ ));
1132
+ findings.push(finding(
1133
+ "overview-category-detail-visible",
1134
+ "Category-tinted section detail must wait until section focus or seat zoom",
1135
+ overviewCountSamples(evidence.overviewStyle.visibleCategoryDetailOutlines, "category detail outline")
1136
+ ));
1137
+ findings.push(finding(
1138
+ "overview-row-hints-visible",
1139
+ "Row and seat patterns must not clutter the section overview",
1140
+ overviewCountSamples(evidence.overviewStyle.visibleSectionRowHints, "section row hint")
1141
+ ));
1142
+ findings.push(finding(
1143
+ "overview-availability-clutter",
1144
+ "Live availability counts belong in focused detail, not on section overview shells",
1145
+ overviewCountSamples(evidence.overviewStyle.visibleSectionAvailabilityLabels, "section availability label")
1146
+ ));
1147
+ findings.push(finding(
1148
+ "overview-ga-detail-visible",
1149
+ "Section-contained standing paint belongs in focused detail, not on section overview shells",
1150
+ overviewCountSamples(evidence.overviewStyle.visibleSectionGADetails, "section-contained GA detail")
1151
+ ));
1152
+ }
1153
+ findings.push(finding(
1154
+ "bookable-label-undersized",
1155
+ "Visible bookable labels must meet the rendered small-text size floor",
1156
+ bookable.filter((label) => label.renderedFontPx < evidence.minimumVisibleLabelPx).map((label) => labelSample(label, label.renderedFontPx, evidence.minimumVisibleLabelPx))
1157
+ ));
1158
+ if (state === "interaction") {
1159
+ const labelledInventory = targetLabels.length > 0;
1160
+ const gaInventory = targetGAAreas.length > 0;
1161
+ const detailInventory = labelledInventory || gaInventory;
1162
+ const visibleTargetGA = targetGAAreas.filter((area) => area.visible);
1163
+ const sections = /* @__PURE__ */ new Set([
1164
+ ...targetLabels.flatMap((label) => label.sectionId ? [label.sectionId] : []),
1165
+ ...targetGAAreas.flatMap((area) => area.sectionId ? [area.sectionId] : [])
1166
+ ]);
1167
+ const categories = /* @__PURE__ */ new Set([
1168
+ ...evidence.labels.map((label) => label.categoryKey),
1169
+ ...evidence.gaAreas.map((area) => area.categoryKey)
1170
+ ]);
1171
+ const inViewport = (label) => label.screenCenter.x >= 0 && label.screenCenter.x <= evidence.viewport.width && label.screenCenter.y >= 0 && label.screenCenter.y <= evidence.viewport.height;
1172
+ findings.push(finding(
1173
+ "detail-rung-missing",
1174
+ "Interaction evidence with bookable inventory must render the seat-detail rung",
1175
+ detailInventory && evidence.rung !== "seats" ? [{ primaryId: "renderer", primaryLabel: evidence.rung }] : []
1176
+ ));
1177
+ findings.push(finding(
1178
+ "detail-inventory-not-visible",
1179
+ "Interaction evidence must frame at least one real bookable unit",
1180
+ detailInventory && !targetLabels.some(inViewport) && !visibleTargetGA.length ? [{ primaryId: "renderer", primaryLabel: "no target inventory in viewport" }] : []
1181
+ ));
1182
+ findings.push(finding(
1183
+ "pointer-target-inactive",
1184
+ "Interaction evidence must expose a live production pointer target",
1185
+ labelledInventory && !activePointerTargets.some(inViewport) || !labelledInventory && gaInventory && !visibleTargetGA.some((area) => area.interactive) ? [{ primaryId: "renderer", primaryLabel: "no active pointer target in viewport" }] : []
1186
+ ));
1187
+ findings.push(finding(
1188
+ "pointer-target-undersized",
1189
+ "Every active production pointer target must reach at least 24 CSS pixels",
1190
+ activePointerTargets.filter((label) => inViewport(label) && label.pointerTarget.effectiveMinimumPx < MIN_POINTER_TARGET_PX).map((label) => labelSample(label, label.pointerTarget.effectiveMinimumPx, MIN_POINTER_TARGET_PX))
1191
+ ));
1192
+ findings.push(finding(
1193
+ "selected-state-missing",
1194
+ "Interaction evidence must paint a selected unit and its renderer-owned ring",
1195
+ labelledInventory && (!selected.length || !selected.some((label) => evidence.selectionRingSeatIds.includes(label.seatId))) ? [{ primaryId: "renderer", primaryLabel: "selected state" }] : []
1196
+ ));
1197
+ findings.push(finding(
1198
+ "selected-state-contrast-low",
1199
+ "The selected-state ring must maintain 3:1 graphical contrast with the canvas",
1200
+ selected.length && (selectedRingContrast ?? 0) < MIN_GRAPHICAL_CONTRAST ? [{
1201
+ primaryId: selected[0].seatId,
1202
+ primaryLabel: selected[0].label,
1203
+ measured: Math.round((selectedRingContrast ?? 0) * 100) / 100,
1204
+ minimum: MIN_GRAPHICAL_CONTRAST
1205
+ }] : []
1206
+ ));
1207
+ findings.push(finding(
1208
+ "held-state-missing",
1209
+ "Interaction evidence must paint a held state when the floor has at least two status-managed units",
1210
+ targetLabels.length >= 2 && !held.length ? [{ primaryId: "renderer", primaryLabel: "held state" }] : []
1211
+ ));
1212
+ findings.push(finding(
1213
+ "booked-state-missing",
1214
+ "Interaction evidence must paint a taken state when the floor has at least three status-managed units",
1215
+ targetLabels.length >= 3 && !booked.length ? [{ primaryId: "renderer", primaryLabel: "booked state" }] : []
1216
+ ));
1217
+ const heldSignatures = new Set(held.map((label) => `${label.fill.toLowerCase()}:${label.opacity}`));
1218
+ const bookedSignatures = new Set(booked.map((label) => `${label.fill.toLowerCase()}:${label.opacity}`));
1219
+ findings.push(finding(
1220
+ "status-state-indistinct",
1221
+ "Held and taken evidence must resolve to distinct renderer paint",
1222
+ held.length && booked.length && [...heldSignatures].some((signature) => bookedSignatures.has(signature)) ? [{ primaryId: held[0].seatId, primaryLabel: held[0].label, secondaryId: booked[0].seatId, secondaryLabel: booked[0].label }] : []
1223
+ ));
1224
+ findings.push(finding(
1225
+ "section-focus-missing",
1226
+ "Interaction evidence must exercise section focus and its backdrop when section membership exists",
1227
+ sections.size && (!evidence.focusedSectionId || !evidence.focusBackdropVisible) ? [{ primaryId: "renderer", primaryLabel: "section focus" }] : []
1228
+ ));
1229
+ findings.push(finding(
1230
+ "category-filter-missing",
1231
+ "Interaction evidence must exercise a category filter when multiple categories exist",
1232
+ categories.size >= 2 && (!evidence.categoryFilterKeys || !evidence.categoryFilterKeys.length) ? [{ primaryId: "renderer", primaryLabel: "category filter" }] : []
1233
+ ));
1234
+ const excludedFree = evidence.labels.filter((label) => label.status === "free" && !label.selected && evidence.categoryFilterKeys != null && !evidence.categoryFilterKeys.includes(label.categoryKey));
1235
+ const excludedGA = evidence.gaAreas.filter((area) => evidence.categoryFilterKeys != null && !evidence.categoryFilterKeys.includes(area.categoryKey));
1236
+ findings.push(finding(
1237
+ "category-filter-ineffective",
1238
+ "The active category filter must visibly dim excluded free inventory",
1239
+ (excludedFree.length || excludedGA.length) && !excludedFree.some((label) => label.opacity <= 0.25) && !excludedGA.some((area) => area.opacity <= 0.1) ? [
1240
+ ...excludedFree.map((label) => labelSample(label, label.opacity)),
1241
+ ...excludedGA.map((area) => ({ primaryId: area.areaId, primaryLabel: area.label, measured: area.opacity }))
1242
+ ] : []
1243
+ ));
1244
+ findings.push(finding(
1245
+ "target-category-not-visible",
1246
+ "A category-specific interaction scene must visibly paint its exact target category",
1247
+ targetCategoryKey && !activeCategoryKeys.has(targetCategoryKey) ? [{ primaryId: targetCategoryKey, primaryLabel: targetCategoryKey }] : []
1248
+ ));
1249
+ findings.push(finding(
1250
+ "target-category-filter-mismatch",
1251
+ "A category-specific interaction scene must bind its filter to only the exact target category",
1252
+ targetCategoryKey && (evidence.categoryFilterKeys?.length !== 1 || evidence.categoryFilterKeys[0] !== targetCategoryKey) ? [{ primaryId: targetCategoryKey, primaryLabel: targetCategoryKey }] : []
1253
+ ));
1254
+ }
1255
+ findings.push(finding(
1256
+ "bookable-label-contrast-low",
1257
+ "Visible bookable labels must meet 4.5:1 contrast against their actual paint",
1258
+ bookable.flatMap((label) => {
1259
+ const ratio = renderedTextContrast(label.ink, label.fill) ?? 0;
1260
+ return ratio < MIN_TEXT_CONTRAST ? [labelSample(label, ratio, MIN_TEXT_CONTRAST)] : [];
1261
+ })
1262
+ ));
1263
+ const bookableCollisions = [];
1264
+ for (let index = 0; index < bookable.length; index += 1) {
1265
+ for (let other = 0; other < index; other += 1) {
1266
+ if (intersects(bookable[index].screenBox, bookable[other].screenBox)) {
1267
+ bookableCollisions.push(collisionSample(
1268
+ bookable[other].seatId,
1269
+ bookable[other].label,
1270
+ bookable[index].seatId,
1271
+ bookable[index].label
1272
+ ));
1273
+ }
1274
+ }
1275
+ }
1276
+ findings.push(finding(
1277
+ "bookable-label-collision",
1278
+ "Visible bookable labels must not overlap",
1279
+ bookableCollisions
1280
+ ));
1281
+ findings.push(finding(
1282
+ "hierarchy-label-undersized",
1283
+ "Visible section and zone labels must meet the rendered small-text size floor",
1284
+ hierarchy.filter((label) => label.renderedFontPx < MIN_VISIBLE_BOOKABLE_LABEL_PX).map((label) => hierarchySample(label, label.renderedFontPx, MIN_VISIBLE_BOOKABLE_LABEL_PX))
1285
+ ));
1286
+ findings.push(finding(
1287
+ "hierarchy-label-contrast-low",
1288
+ "Visible section and zone labels must meet 4.5:1 contrast against their backing paint",
1289
+ hierarchy.flatMap((label) => {
1290
+ const ratio = renderedTextContrast(label.ink, label.fill) ?? 0;
1291
+ return ratio < MIN_TEXT_CONTRAST ? [hierarchySample(label, ratio, MIN_TEXT_CONTRAST)] : [];
1292
+ })
1293
+ ));
1294
+ findings.push(finding(
1295
+ "hierarchy-label-outside-section",
1296
+ "Visible section labels must remain inside the filled section surface and outside holes",
1297
+ hierarchy.filter((label) => label.kind === "section" && label.fitsContainer === false).map((label) => hierarchySample(label))
1298
+ ));
1299
+ const hierarchyCollisions = [];
1300
+ for (let index = 0; index < hierarchy.length; index += 1) {
1301
+ for (let other = 0; other < index; other += 1) {
1302
+ if (intersects(hierarchy[index].screenBox, hierarchy[other].screenBox)) {
1303
+ hierarchyCollisions.push(collisionSample(
1304
+ hierarchy[other].id,
1305
+ hierarchy[other].label,
1306
+ hierarchy[index].id,
1307
+ hierarchy[index].label
1308
+ ));
1309
+ }
1310
+ }
1311
+ }
1312
+ findings.push(finding(
1313
+ "hierarchy-label-collision",
1314
+ "Visible hierarchy labels must not overlap each other",
1315
+ hierarchyCollisions
1316
+ ));
1317
+ const hierarchyBookableCollisions = [];
1318
+ for (const upper of hierarchy) {
1319
+ for (const unit of bookable) {
1320
+ if (intersects(upper.screenBox, unit.screenBox)) {
1321
+ hierarchyBookableCollisions.push(collisionSample(upper.id, upper.label, unit.seatId, unit.label));
1322
+ }
1323
+ }
1324
+ }
1325
+ findings.push(finding(
1326
+ "hierarchy-bookable-collision",
1327
+ "Visible hierarchy labels must not overlap bookable labels",
1328
+ hierarchyBookableCollisions
1329
+ ));
1330
+ findings.push(finding(
1331
+ "free-text-undersized",
1332
+ "Visible chart text must meet the rendered small-text size floor",
1333
+ freeText.filter((label) => label.renderedFontPx < evidence.minimumVisibleLabelPx).map((label) => freeTextSample(label, label.renderedFontPx, evidence.minimumVisibleLabelPx))
1334
+ ));
1335
+ findings.push(finding(
1336
+ "free-text-contrast-low",
1337
+ "Visible chart text must meet 4.5:1 contrast against its measured background",
1338
+ freeText.flatMap((label) => {
1339
+ const ratio = renderedTextContrast(label.ink, label.background) ?? 0;
1340
+ return ratio < MIN_TEXT_CONTRAST ? [freeTextSample(label, ratio, MIN_TEXT_CONTRAST)] : [];
1341
+ })
1342
+ ));
1343
+ const freeTextCollisions = [];
1344
+ for (let index = 0; index < freeText.length; index += 1) {
1345
+ for (let other = 0; other < index; other += 1) {
1346
+ if (intersects(freeText[index].screenBox, freeText[other].screenBox)) {
1347
+ freeTextCollisions.push(collisionSample(
1348
+ freeText[other].objectId,
1349
+ freeText[other].text,
1350
+ freeText[index].objectId,
1351
+ freeText[index].text
1352
+ ));
1353
+ }
1354
+ }
1355
+ }
1356
+ findings.push(finding(
1357
+ "free-text-collision",
1358
+ "Visible chart text labels must not overlap each other",
1359
+ freeTextCollisions
1360
+ ));
1361
+ const freeTextBookableCollisions = [];
1362
+ for (const text of freeText) {
1363
+ for (const unit of bookable) {
1364
+ if (intersects(text.screenBox, unit.screenBox)) {
1365
+ freeTextBookableCollisions.push(collisionSample(text.objectId, text.text, unit.seatId, unit.label));
1366
+ }
1367
+ }
1368
+ }
1369
+ findings.push(finding(
1370
+ "free-text-bookable-collision",
1371
+ "Visible chart text must not overlap bookable labels",
1372
+ freeTextBookableCollisions
1373
+ ));
1374
+ const freeTextHierarchyCollisions = [];
1375
+ for (const text of freeText) {
1376
+ for (const upper of hierarchy) {
1377
+ if (intersects(text.screenBox, upper.screenBox)) {
1378
+ freeTextHierarchyCollisions.push(collisionSample(text.objectId, text.text, upper.id, upper.label));
1379
+ }
1380
+ }
1381
+ }
1382
+ findings.push(finding(
1383
+ "free-text-hierarchy-collision",
1384
+ "Visible chart text must not overlap hierarchy labels",
1385
+ freeTextHierarchyCollisions
1386
+ ));
1387
+ findings.push(finding(
1388
+ "ga-contrast-low",
1389
+ "Visible GA surfaces must maintain 3:1 graphical contrast with the canvas",
1390
+ visibleGA.flatMap((area) => {
1391
+ const ratio = renderedTextContrast(area.effectiveBackground, evidence.canvasBackground) ?? 0;
1392
+ return ratio < MIN_GRAPHICAL_CONTRAST ? [{
1393
+ primaryId: area.areaId,
1394
+ primaryLabel: area.label,
1395
+ measured: Math.round(ratio * 100) / 100,
1396
+ minimum: MIN_GRAPHICAL_CONTRAST
1397
+ }] : [];
1398
+ })
1399
+ ));
1400
+ const materialFindings = findings.filter((item) => Boolean(item));
1401
+ return {
1402
+ version: RENDERED_QUALITY_REPORT_VERSION,
1403
+ passed: materialFindings.length === 0,
1404
+ state,
1405
+ targetCategoryKey,
1406
+ resolvedRules: [
1407
+ "rendered-overview-label-size",
1408
+ "rendered-overview-label-contrast",
1409
+ "rendered-overview-label-collision",
1410
+ "rendered-overview-hierarchy-containment",
1411
+ "rendered-overview-section-first-style",
1412
+ "rendered-overview-ga-contrast",
1413
+ ...state === "interaction" ? [
1414
+ "rendered-detail-inventory",
1415
+ "rendered-pointer-target",
1416
+ "rendered-selected-held-taken-states",
1417
+ "rendered-section-focus",
1418
+ "rendered-category-filter"
1419
+ ] : []
1420
+ ],
1421
+ viewport: evidence.viewport,
1422
+ canvasBackground: evidence.canvasBackground,
1423
+ effectiveScale: evidence.effectiveScale,
1424
+ rung: evidence.rung,
1425
+ inventory: {
1426
+ totalBookableUnits: evidence.totalBookableUnits,
1427
+ totalLabelledBookableUnits: evidence.totalLabelledBookableUnits,
1428
+ visibleBookableLabels: bookable.length,
1429
+ hiddenBookableLabels: evidence.hiddenLabels,
1430
+ visibleHierarchyLabels: hierarchy.length,
1431
+ visibleFreeTextLabels: freeText.length,
1432
+ visibleGAAreas: visibleGA.length
1433
+ },
1434
+ overviewStyle: evidence.overviewStyle,
1435
+ composition: {
1436
+ hierarchy: evidence.hierarchyLabels.filter((label) => label.role === "name").map((label) => ({
1437
+ id: label.id,
1438
+ kind: label.kind,
1439
+ label: label.label,
1440
+ visible: label.visible
1441
+ })).sort((left, right) => left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id)),
1442
+ labelledObjects: evidence.freeTextLabels.map((label) => ({
1443
+ objectId: label.objectId,
1444
+ kind: label.kind,
1445
+ text: label.text,
1446
+ visible: label.visible
1447
+ })).sort((left, right) => left.objectId.localeCompare(right.objectId) || left.kind.localeCompare(right.kind)),
1448
+ gaAreas: evidence.gaAreas.map((area) => ({
1449
+ areaId: area.areaId,
1450
+ label: area.label,
1451
+ categoryKey: area.categoryKey,
1452
+ ...area.sectionId ? { sectionId: area.sectionId } : {},
1453
+ visible: area.visible
1454
+ })).sort((left, right) => left.areaId.localeCompare(right.areaId)),
1455
+ bookableSectionIds: [...new Set(evidence.labels.flatMap((label) => label.sectionId ? [label.sectionId] : []))].sort(),
1456
+ categoryKeys: [.../* @__PURE__ */ new Set([
1457
+ ...evidence.labels.map((label) => label.categoryKey),
1458
+ ...evidence.gaAreas.map((area) => area.categoryKey)
1459
+ ])].sort(),
1460
+ activeCategoryKeys: [...activeCategoryKeys].sort()
1461
+ },
1462
+ metrics: {
1463
+ minimumRenderedBookableLabelPx: minimum(bookable.map((label) => label.renderedFontPx)),
1464
+ minimumBookableLabelContrast: minimum(bookableContrast),
1465
+ minimumRenderedHierarchyLabelPx: minimum(hierarchy.map((label) => label.renderedFontPx)),
1466
+ minimumHierarchyLabelContrast: minimum(hierarchyContrast),
1467
+ minimumRenderedFreeTextPx: minimum(freeText.map((label) => label.renderedFontPx)),
1468
+ minimumFreeTextContrast: minimum(freeTextContrast),
1469
+ minimumGAContrast: minimum(gaContrast),
1470
+ minimumEffectivePointerTargetPx: minimum(activePointerTargets.map((label) => label.pointerTarget.effectiveMinimumPx)),
1471
+ selectedRingContrast: selectedRingContrast == null ? null : Math.round(selectedRingContrast * 100) / 100
1472
+ },
1473
+ interaction: {
1474
+ applicable: {
1475
+ detail: targetLabels.length > 0 || targetGAAreas.length > 0,
1476
+ pointer: targetLabels.length > 0 || targetGAAreas.length > 0,
1477
+ held: targetLabels.length >= 2,
1478
+ booked: targetLabels.length >= 3,
1479
+ sectionFocus: targetLabels.some((label) => Boolean(label.sectionId)) || targetGAAreas.some((area) => Boolean(area.sectionId)),
1480
+ categoryFilter: (/* @__PURE__ */ new Set([
1481
+ ...evidence.labels.map((label) => label.categoryKey),
1482
+ ...evidence.gaAreas.map((area) => area.categoryKey)
1483
+ ])).size >= 2
1484
+ },
1485
+ selectedUnits: selected.length,
1486
+ heldUnits: held.length,
1487
+ bookedUnits: booked.length,
1488
+ activePointerTargets: activePointerTargets.length,
1489
+ focusedSectionId: evidence.focusedSectionId,
1490
+ focusBackdropVisible: evidence.focusBackdropVisible,
1491
+ categoryFilterKeys: evidence.categoryFilterKeys
1492
+ },
1493
+ findings: materialFindings
1494
+ };
1495
+ }
1496
+
1497
+ // src/engine/SeatmapRenderer.ts
1498
+ var import_Core = require("konva/lib/Core");
1499
+ var import_Stage = require("konva/lib/Stage");
1500
+ var import_Layer = require("konva/lib/Layer");
1501
+ var import_Group = require("konva/lib/Group");
1502
+ var import_Circle = require("konva/lib/shapes/Circle");
1503
+ var import_Rect = require("konva/lib/shapes/Rect");
1504
+ var import_Ellipse = require("konva/lib/shapes/Ellipse");
1505
+ var import_Line = require("konva/lib/shapes/Line");
1506
+ var import_Text = require("konva/lib/shapes/Text");
1507
+ var import_Path = require("konva/lib/shapes/Path");
1508
+ var import_Image = require("konva/lib/shapes/Image");
1509
+ var import_Shape = require("konva/lib/Shape");
1510
+
1056
1511
  // src/lib/money.ts
1057
1512
  var DEFAULT_CURRENCY = "USD";
1058
1513
  var displayLocale;
@@ -1115,6 +1570,7 @@ var en = {
1115
1570
  "picker.seatsLeftInSection.one": "{count} seat left",
1116
1571
  "picker.seatsLeftInSection.other": "{count} seats left",
1117
1572
  "picker.overview": "Overview",
1573
+ "picker.entrance": "Entrance",
1118
1574
  "picker.tapSeatHint": "Tap any seat to check its view",
1119
1575
  "picker.ticketTierFor": "Ticket tier for {label}",
1120
1576
  "picker.viewFromSeat": "View from seat {label}",
@@ -5086,13 +5542,15 @@ var PickerController = class {
5086
5542
  const sourceLabel = source && "label" in source && typeof source.label === "string" ? source.label : void 0;
5087
5543
  const sourceDisplayLabel = source && "displayLabel" in source && typeof source.displayLabel === "string" && source.displayLabel ? source.displayLabel : sourceLabel;
5088
5544
  const rowLabel = s.kind === "booth" ? void 0 : sourceDisplayLabel;
5545
+ const rowType = source && "displayType" in source && typeof source.displayType === "string" && source.displayType.trim() ? source.displayType.trim() : void 0;
5089
5546
  const visibleSeatLabel = s.displayLabel ?? s.label;
5090
5547
  const labelParts = visibleSeatLabel.split("-");
5091
5548
  const seatNumber = sourceDisplayLabel && visibleSeatLabel.startsWith(`${sourceDisplayLabel}-`) ? visibleSeatLabel.slice(sourceDisplayLabel.length + 1) : s.kind === "booth" ? visibleSeatLabel : labelParts[labelParts.length - 1] ?? visibleSeatLabel;
5092
5549
  this.seatContext.set(s.id, {
5093
5550
  sectionLabel: sectionLabels.get(membership.objectToSection.get(s.rowId) ?? ""),
5094
5551
  rowLabel,
5095
- seatNumber
5552
+ seatNumber,
5553
+ rowType
5096
5554
  });
5097
5555
  }
5098
5556
  const currency = res.event.currency ?? this.opts.currency;
@@ -5727,6 +6185,7 @@ var PickerController = class {
5727
6185
  id,
5728
6186
  label: sec.label,
5729
6187
  zoneLabel: zone?.label ?? "",
6188
+ ...sec.entrance && sec.entrance.trim() ? { entrance: sec.entrance.trim() } : {},
5730
6189
  color,
5731
6190
  seatsLeft,
5732
6191
  priceMin: prices.length ? prices[0] : 0,
@@ -6292,6 +6751,7 @@ async function loadLocale(code) {
6292
6751
  MAX_EVENT_INVENTORY,
6293
6752
  MAX_GA_CAPACITY,
6294
6753
  PickerController,
6754
+ RENDERED_QUALITY_REPORT_VERSION,
6295
6755
  SUPPORTED_LOCALES,
6296
6756
  SeatmapRenderer,
6297
6757
  UNGROUPED_ID,
@@ -6319,6 +6779,7 @@ async function loadLocale(code) {
6319
6779
  generateSeatThumb,
6320
6780
  getLocale,
6321
6781
  hiddenObjectIds,
6782
+ inspectRenderedQualityEvidence,
6322
6783
  isGaUnitLabel,
6323
6784
  isSectionHidden,
6324
6785
  layerOf,