@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.js CHANGED
@@ -723,20 +723,6 @@ function applyHidden(doc, hidden) {
723
723
  return { ...doc, objects };
724
724
  }
725
725
 
726
- // src/engine/SeatmapRenderer.ts
727
- import { Konva } from "konva/lib/Core";
728
- import { Stage } from "konva/lib/Stage";
729
- import { Layer } from "konva/lib/Layer";
730
- import { Group } from "konva/lib/Group";
731
- import { Circle } from "konva/lib/shapes/Circle";
732
- import { Rect } from "konva/lib/shapes/Rect";
733
- import { Ellipse } from "konva/lib/shapes/Ellipse";
734
- import { Line } from "konva/lib/shapes/Line";
735
- import { Text } from "konva/lib/shapes/Text";
736
- import { Path } from "konva/lib/shapes/Path";
737
- import { Image as KImage } from "konva/lib/shapes/Image";
738
- import { Shape } from "konva/lib/Shape";
739
-
740
726
  // src/core/chartRenderRules.ts
741
727
  var SEAT_LABEL_FONT_SIZE = 7;
742
728
  var BOOTH_LABEL_FONT_SIZE = 10;
@@ -793,6 +779,470 @@ function stateAwareBookableLabelInk(fill, preferred) {
793
779
  return darkContrast >= lightContrast ? DARK_BOOKABLE_LABEL_INK : LIGHT_BOOKABLE_LABEL_INK;
794
780
  }
795
781
 
782
+ // src/core/renderedQuality.ts
783
+ var RENDERED_QUALITY_REPORT_VERSION = 3;
784
+ var MIN_TEXT_CONTRAST = 4.5;
785
+ var MIN_GRAPHICAL_CONTRAST = 3;
786
+ var MIN_POINTER_TARGET_PX = 24;
787
+ var MAX_SAMPLES_PER_FINDING = 20;
788
+ function visibleWithBox(items) {
789
+ return items.filter((item) => item.visible && Boolean(item.screenBox));
790
+ }
791
+ function intersects(left, right) {
792
+ 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;
793
+ }
794
+ function minimum(values) {
795
+ return values.length ? Math.round(Math.min(...values) * 100) / 100 : null;
796
+ }
797
+ function finding(code, message, samples) {
798
+ if (!samples.length) return null;
799
+ return {
800
+ code,
801
+ message,
802
+ count: samples.length,
803
+ samples: samples.slice(0, MAX_SAMPLES_PER_FINDING)
804
+ };
805
+ }
806
+ function labelSample(label, measured, minimumValue) {
807
+ return {
808
+ primaryId: label.seatId,
809
+ primaryLabel: label.label,
810
+ ...measured == null ? {} : { measured: Math.round(measured * 100) / 100 },
811
+ ...minimumValue == null ? {} : { minimum: minimumValue }
812
+ };
813
+ }
814
+ function hierarchySample(label, measured, minimumValue) {
815
+ return {
816
+ primaryId: label.id,
817
+ primaryLabel: label.label,
818
+ ...measured == null ? {} : { measured: Math.round(measured * 100) / 100 },
819
+ ...minimumValue == null ? {} : { minimum: minimumValue }
820
+ };
821
+ }
822
+ function freeTextSample(label, measured, minimumValue) {
823
+ return {
824
+ primaryId: label.objectId,
825
+ primaryLabel: label.text,
826
+ ...measured == null ? {} : { measured: Math.round(measured * 100) / 100 },
827
+ ...minimumValue == null ? {} : { minimum: minimumValue }
828
+ };
829
+ }
830
+ function collisionSample(primaryId, primaryLabel, secondaryId, secondaryLabel) {
831
+ return { primaryId, primaryLabel, secondaryId, secondaryLabel };
832
+ }
833
+ function inspectRenderedQualityEvidence(evidence, state = "overview", targetCategoryKey = null) {
834
+ const bookable = visibleWithBox(evidence.labels);
835
+ const hierarchy = visibleWithBox(evidence.hierarchyLabels);
836
+ const freeText = visibleWithBox(evidence.freeTextLabels);
837
+ const visibleGA = evidence.gaAreas.filter((area) => area.visible);
838
+ const bookableContrast = bookable.map((label) => renderedTextContrast(label.ink, label.fill) ?? 0);
839
+ const hierarchyContrast = hierarchy.map((label) => renderedTextContrast(label.ink, label.fill) ?? 0);
840
+ const freeTextContrast = freeText.map((label) => renderedTextContrast(label.ink, label.background) ?? 0);
841
+ const gaContrast = visibleGA.map((area) => renderedTextContrast(area.effectiveBackground, evidence.canvasBackground) ?? 0);
842
+ const targetLabels = targetCategoryKey ? evidence.labels.filter((label) => label.categoryKey === targetCategoryKey) : evidence.labels;
843
+ const targetGAAreas = targetCategoryKey ? evidence.gaAreas.filter((area) => area.categoryKey === targetCategoryKey) : evidence.gaAreas;
844
+ const selected = targetLabels.filter((label) => label.selected);
845
+ const held = targetLabels.filter((label) => label.status === "held");
846
+ const booked = targetLabels.filter((label) => label.status === "booked");
847
+ const activePointerTargets = targetLabels.filter((label) => label.pointerTarget.active);
848
+ const activeCategoryKeys = /* @__PURE__ */ new Set([
849
+ ...evidence.labels.filter((label) => label.visible && (label.opacity > 0.25 || label.selected || label.status !== "free")).map((label) => label.categoryKey),
850
+ ...evidence.gaAreas.filter((area) => area.visible && area.opacity > 0.1).map((area) => area.categoryKey)
851
+ ]);
852
+ const selectedRingContrast = selected.length ? minimum(selected.flatMap((label) => [
853
+ renderedTextContrast(evidence.selectionRingColor, evidence.canvasBackground) ?? 0,
854
+ renderedTextContrast(evidence.selectionRingColor, label.fill) ?? 0
855
+ ])) : null;
856
+ const findings = [];
857
+ const overviewCountSamples = (count, label) => Array.from({ length: count }, (_, index) => ({
858
+ primaryId: `overview:${index + 1}`,
859
+ primaryLabel: label
860
+ }));
861
+ if (state === "overview" && evidence.rung === "sections") {
862
+ findings.push(finding(
863
+ "overview-section-category-paint",
864
+ "Section overview shells must use the neutral hierarchy palette, not category paint",
865
+ overviewCountSamples(evidence.overviewStyle.categoryPaintedSectionShells, "category-painted section shell")
866
+ ));
867
+ findings.push(finding(
868
+ "overview-category-detail-visible",
869
+ "Category-tinted section detail must wait until section focus or seat zoom",
870
+ overviewCountSamples(evidence.overviewStyle.visibleCategoryDetailOutlines, "category detail outline")
871
+ ));
872
+ findings.push(finding(
873
+ "overview-row-hints-visible",
874
+ "Row and seat patterns must not clutter the section overview",
875
+ overviewCountSamples(evidence.overviewStyle.visibleSectionRowHints, "section row hint")
876
+ ));
877
+ findings.push(finding(
878
+ "overview-availability-clutter",
879
+ "Live availability counts belong in focused detail, not on section overview shells",
880
+ overviewCountSamples(evidence.overviewStyle.visibleSectionAvailabilityLabels, "section availability label")
881
+ ));
882
+ findings.push(finding(
883
+ "overview-ga-detail-visible",
884
+ "Section-contained standing paint belongs in focused detail, not on section overview shells",
885
+ overviewCountSamples(evidence.overviewStyle.visibleSectionGADetails, "section-contained GA detail")
886
+ ));
887
+ }
888
+ findings.push(finding(
889
+ "bookable-label-undersized",
890
+ "Visible bookable labels must meet the rendered small-text size floor",
891
+ bookable.filter((label) => label.renderedFontPx < evidence.minimumVisibleLabelPx).map((label) => labelSample(label, label.renderedFontPx, evidence.minimumVisibleLabelPx))
892
+ ));
893
+ if (state === "interaction") {
894
+ const labelledInventory = targetLabels.length > 0;
895
+ const gaInventory = targetGAAreas.length > 0;
896
+ const detailInventory = labelledInventory || gaInventory;
897
+ const visibleTargetGA = targetGAAreas.filter((area) => area.visible);
898
+ const sections = /* @__PURE__ */ new Set([
899
+ ...targetLabels.flatMap((label) => label.sectionId ? [label.sectionId] : []),
900
+ ...targetGAAreas.flatMap((area) => area.sectionId ? [area.sectionId] : [])
901
+ ]);
902
+ const categories = /* @__PURE__ */ new Set([
903
+ ...evidence.labels.map((label) => label.categoryKey),
904
+ ...evidence.gaAreas.map((area) => area.categoryKey)
905
+ ]);
906
+ const inViewport = (label) => label.screenCenter.x >= 0 && label.screenCenter.x <= evidence.viewport.width && label.screenCenter.y >= 0 && label.screenCenter.y <= evidence.viewport.height;
907
+ findings.push(finding(
908
+ "detail-rung-missing",
909
+ "Interaction evidence with bookable inventory must render the seat-detail rung",
910
+ detailInventory && evidence.rung !== "seats" ? [{ primaryId: "renderer", primaryLabel: evidence.rung }] : []
911
+ ));
912
+ findings.push(finding(
913
+ "detail-inventory-not-visible",
914
+ "Interaction evidence must frame at least one real bookable unit",
915
+ detailInventory && !targetLabels.some(inViewport) && !visibleTargetGA.length ? [{ primaryId: "renderer", primaryLabel: "no target inventory in viewport" }] : []
916
+ ));
917
+ findings.push(finding(
918
+ "pointer-target-inactive",
919
+ "Interaction evidence must expose a live production pointer target",
920
+ labelledInventory && !activePointerTargets.some(inViewport) || !labelledInventory && gaInventory && !visibleTargetGA.some((area) => area.interactive) ? [{ primaryId: "renderer", primaryLabel: "no active pointer target in viewport" }] : []
921
+ ));
922
+ findings.push(finding(
923
+ "pointer-target-undersized",
924
+ "Every active production pointer target must reach at least 24 CSS pixels",
925
+ activePointerTargets.filter((label) => inViewport(label) && label.pointerTarget.effectiveMinimumPx < MIN_POINTER_TARGET_PX).map((label) => labelSample(label, label.pointerTarget.effectiveMinimumPx, MIN_POINTER_TARGET_PX))
926
+ ));
927
+ findings.push(finding(
928
+ "selected-state-missing",
929
+ "Interaction evidence must paint a selected unit and its renderer-owned ring",
930
+ labelledInventory && (!selected.length || !selected.some((label) => evidence.selectionRingSeatIds.includes(label.seatId))) ? [{ primaryId: "renderer", primaryLabel: "selected state" }] : []
931
+ ));
932
+ findings.push(finding(
933
+ "selected-state-contrast-low",
934
+ "The selected-state ring must maintain 3:1 graphical contrast with the canvas",
935
+ selected.length && (selectedRingContrast ?? 0) < MIN_GRAPHICAL_CONTRAST ? [{
936
+ primaryId: selected[0].seatId,
937
+ primaryLabel: selected[0].label,
938
+ measured: Math.round((selectedRingContrast ?? 0) * 100) / 100,
939
+ minimum: MIN_GRAPHICAL_CONTRAST
940
+ }] : []
941
+ ));
942
+ findings.push(finding(
943
+ "held-state-missing",
944
+ "Interaction evidence must paint a held state when the floor has at least two status-managed units",
945
+ targetLabels.length >= 2 && !held.length ? [{ primaryId: "renderer", primaryLabel: "held state" }] : []
946
+ ));
947
+ findings.push(finding(
948
+ "booked-state-missing",
949
+ "Interaction evidence must paint a taken state when the floor has at least three status-managed units",
950
+ targetLabels.length >= 3 && !booked.length ? [{ primaryId: "renderer", primaryLabel: "booked state" }] : []
951
+ ));
952
+ const heldSignatures = new Set(held.map((label) => `${label.fill.toLowerCase()}:${label.opacity}`));
953
+ const bookedSignatures = new Set(booked.map((label) => `${label.fill.toLowerCase()}:${label.opacity}`));
954
+ findings.push(finding(
955
+ "status-state-indistinct",
956
+ "Held and taken evidence must resolve to distinct renderer paint",
957
+ 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 }] : []
958
+ ));
959
+ findings.push(finding(
960
+ "section-focus-missing",
961
+ "Interaction evidence must exercise section focus and its backdrop when section membership exists",
962
+ sections.size && (!evidence.focusedSectionId || !evidence.focusBackdropVisible) ? [{ primaryId: "renderer", primaryLabel: "section focus" }] : []
963
+ ));
964
+ findings.push(finding(
965
+ "category-filter-missing",
966
+ "Interaction evidence must exercise a category filter when multiple categories exist",
967
+ categories.size >= 2 && (!evidence.categoryFilterKeys || !evidence.categoryFilterKeys.length) ? [{ primaryId: "renderer", primaryLabel: "category filter" }] : []
968
+ ));
969
+ const excludedFree = evidence.labels.filter((label) => label.status === "free" && !label.selected && evidence.categoryFilterKeys != null && !evidence.categoryFilterKeys.includes(label.categoryKey));
970
+ const excludedGA = evidence.gaAreas.filter((area) => evidence.categoryFilterKeys != null && !evidence.categoryFilterKeys.includes(area.categoryKey));
971
+ findings.push(finding(
972
+ "category-filter-ineffective",
973
+ "The active category filter must visibly dim excluded free inventory",
974
+ (excludedFree.length || excludedGA.length) && !excludedFree.some((label) => label.opacity <= 0.25) && !excludedGA.some((area) => area.opacity <= 0.1) ? [
975
+ ...excludedFree.map((label) => labelSample(label, label.opacity)),
976
+ ...excludedGA.map((area) => ({ primaryId: area.areaId, primaryLabel: area.label, measured: area.opacity }))
977
+ ] : []
978
+ ));
979
+ findings.push(finding(
980
+ "target-category-not-visible",
981
+ "A category-specific interaction scene must visibly paint its exact target category",
982
+ targetCategoryKey && !activeCategoryKeys.has(targetCategoryKey) ? [{ primaryId: targetCategoryKey, primaryLabel: targetCategoryKey }] : []
983
+ ));
984
+ findings.push(finding(
985
+ "target-category-filter-mismatch",
986
+ "A category-specific interaction scene must bind its filter to only the exact target category",
987
+ targetCategoryKey && (evidence.categoryFilterKeys?.length !== 1 || evidence.categoryFilterKeys[0] !== targetCategoryKey) ? [{ primaryId: targetCategoryKey, primaryLabel: targetCategoryKey }] : []
988
+ ));
989
+ }
990
+ findings.push(finding(
991
+ "bookable-label-contrast-low",
992
+ "Visible bookable labels must meet 4.5:1 contrast against their actual paint",
993
+ bookable.flatMap((label) => {
994
+ const ratio = renderedTextContrast(label.ink, label.fill) ?? 0;
995
+ return ratio < MIN_TEXT_CONTRAST ? [labelSample(label, ratio, MIN_TEXT_CONTRAST)] : [];
996
+ })
997
+ ));
998
+ const bookableCollisions = [];
999
+ for (let index = 0; index < bookable.length; index += 1) {
1000
+ for (let other = 0; other < index; other += 1) {
1001
+ if (intersects(bookable[index].screenBox, bookable[other].screenBox)) {
1002
+ bookableCollisions.push(collisionSample(
1003
+ bookable[other].seatId,
1004
+ bookable[other].label,
1005
+ bookable[index].seatId,
1006
+ bookable[index].label
1007
+ ));
1008
+ }
1009
+ }
1010
+ }
1011
+ findings.push(finding(
1012
+ "bookable-label-collision",
1013
+ "Visible bookable labels must not overlap",
1014
+ bookableCollisions
1015
+ ));
1016
+ findings.push(finding(
1017
+ "hierarchy-label-undersized",
1018
+ "Visible section and zone labels must meet the rendered small-text size floor",
1019
+ hierarchy.filter((label) => label.renderedFontPx < MIN_VISIBLE_BOOKABLE_LABEL_PX).map((label) => hierarchySample(label, label.renderedFontPx, MIN_VISIBLE_BOOKABLE_LABEL_PX))
1020
+ ));
1021
+ findings.push(finding(
1022
+ "hierarchy-label-contrast-low",
1023
+ "Visible section and zone labels must meet 4.5:1 contrast against their backing paint",
1024
+ hierarchy.flatMap((label) => {
1025
+ const ratio = renderedTextContrast(label.ink, label.fill) ?? 0;
1026
+ return ratio < MIN_TEXT_CONTRAST ? [hierarchySample(label, ratio, MIN_TEXT_CONTRAST)] : [];
1027
+ })
1028
+ ));
1029
+ findings.push(finding(
1030
+ "hierarchy-label-outside-section",
1031
+ "Visible section labels must remain inside the filled section surface and outside holes",
1032
+ hierarchy.filter((label) => label.kind === "section" && label.fitsContainer === false).map((label) => hierarchySample(label))
1033
+ ));
1034
+ const hierarchyCollisions = [];
1035
+ for (let index = 0; index < hierarchy.length; index += 1) {
1036
+ for (let other = 0; other < index; other += 1) {
1037
+ if (intersects(hierarchy[index].screenBox, hierarchy[other].screenBox)) {
1038
+ hierarchyCollisions.push(collisionSample(
1039
+ hierarchy[other].id,
1040
+ hierarchy[other].label,
1041
+ hierarchy[index].id,
1042
+ hierarchy[index].label
1043
+ ));
1044
+ }
1045
+ }
1046
+ }
1047
+ findings.push(finding(
1048
+ "hierarchy-label-collision",
1049
+ "Visible hierarchy labels must not overlap each other",
1050
+ hierarchyCollisions
1051
+ ));
1052
+ const hierarchyBookableCollisions = [];
1053
+ for (const upper of hierarchy) {
1054
+ for (const unit of bookable) {
1055
+ if (intersects(upper.screenBox, unit.screenBox)) {
1056
+ hierarchyBookableCollisions.push(collisionSample(upper.id, upper.label, unit.seatId, unit.label));
1057
+ }
1058
+ }
1059
+ }
1060
+ findings.push(finding(
1061
+ "hierarchy-bookable-collision",
1062
+ "Visible hierarchy labels must not overlap bookable labels",
1063
+ hierarchyBookableCollisions
1064
+ ));
1065
+ findings.push(finding(
1066
+ "free-text-undersized",
1067
+ "Visible chart text must meet the rendered small-text size floor",
1068
+ freeText.filter((label) => label.renderedFontPx < evidence.minimumVisibleLabelPx).map((label) => freeTextSample(label, label.renderedFontPx, evidence.minimumVisibleLabelPx))
1069
+ ));
1070
+ findings.push(finding(
1071
+ "free-text-contrast-low",
1072
+ "Visible chart text must meet 4.5:1 contrast against its measured background",
1073
+ freeText.flatMap((label) => {
1074
+ const ratio = renderedTextContrast(label.ink, label.background) ?? 0;
1075
+ return ratio < MIN_TEXT_CONTRAST ? [freeTextSample(label, ratio, MIN_TEXT_CONTRAST)] : [];
1076
+ })
1077
+ ));
1078
+ const freeTextCollisions = [];
1079
+ for (let index = 0; index < freeText.length; index += 1) {
1080
+ for (let other = 0; other < index; other += 1) {
1081
+ if (intersects(freeText[index].screenBox, freeText[other].screenBox)) {
1082
+ freeTextCollisions.push(collisionSample(
1083
+ freeText[other].objectId,
1084
+ freeText[other].text,
1085
+ freeText[index].objectId,
1086
+ freeText[index].text
1087
+ ));
1088
+ }
1089
+ }
1090
+ }
1091
+ findings.push(finding(
1092
+ "free-text-collision",
1093
+ "Visible chart text labels must not overlap each other",
1094
+ freeTextCollisions
1095
+ ));
1096
+ const freeTextBookableCollisions = [];
1097
+ for (const text of freeText) {
1098
+ for (const unit of bookable) {
1099
+ if (intersects(text.screenBox, unit.screenBox)) {
1100
+ freeTextBookableCollisions.push(collisionSample(text.objectId, text.text, unit.seatId, unit.label));
1101
+ }
1102
+ }
1103
+ }
1104
+ findings.push(finding(
1105
+ "free-text-bookable-collision",
1106
+ "Visible chart text must not overlap bookable labels",
1107
+ freeTextBookableCollisions
1108
+ ));
1109
+ const freeTextHierarchyCollisions = [];
1110
+ for (const text of freeText) {
1111
+ for (const upper of hierarchy) {
1112
+ if (intersects(text.screenBox, upper.screenBox)) {
1113
+ freeTextHierarchyCollisions.push(collisionSample(text.objectId, text.text, upper.id, upper.label));
1114
+ }
1115
+ }
1116
+ }
1117
+ findings.push(finding(
1118
+ "free-text-hierarchy-collision",
1119
+ "Visible chart text must not overlap hierarchy labels",
1120
+ freeTextHierarchyCollisions
1121
+ ));
1122
+ findings.push(finding(
1123
+ "ga-contrast-low",
1124
+ "Visible GA surfaces must maintain 3:1 graphical contrast with the canvas",
1125
+ visibleGA.flatMap((area) => {
1126
+ const ratio = renderedTextContrast(area.effectiveBackground, evidence.canvasBackground) ?? 0;
1127
+ return ratio < MIN_GRAPHICAL_CONTRAST ? [{
1128
+ primaryId: area.areaId,
1129
+ primaryLabel: area.label,
1130
+ measured: Math.round(ratio * 100) / 100,
1131
+ minimum: MIN_GRAPHICAL_CONTRAST
1132
+ }] : [];
1133
+ })
1134
+ ));
1135
+ const materialFindings = findings.filter((item) => Boolean(item));
1136
+ return {
1137
+ version: RENDERED_QUALITY_REPORT_VERSION,
1138
+ passed: materialFindings.length === 0,
1139
+ state,
1140
+ targetCategoryKey,
1141
+ resolvedRules: [
1142
+ "rendered-overview-label-size",
1143
+ "rendered-overview-label-contrast",
1144
+ "rendered-overview-label-collision",
1145
+ "rendered-overview-hierarchy-containment",
1146
+ "rendered-overview-section-first-style",
1147
+ "rendered-overview-ga-contrast",
1148
+ ...state === "interaction" ? [
1149
+ "rendered-detail-inventory",
1150
+ "rendered-pointer-target",
1151
+ "rendered-selected-held-taken-states",
1152
+ "rendered-section-focus",
1153
+ "rendered-category-filter"
1154
+ ] : []
1155
+ ],
1156
+ viewport: evidence.viewport,
1157
+ canvasBackground: evidence.canvasBackground,
1158
+ effectiveScale: evidence.effectiveScale,
1159
+ rung: evidence.rung,
1160
+ inventory: {
1161
+ totalBookableUnits: evidence.totalBookableUnits,
1162
+ totalLabelledBookableUnits: evidence.totalLabelledBookableUnits,
1163
+ visibleBookableLabels: bookable.length,
1164
+ hiddenBookableLabels: evidence.hiddenLabels,
1165
+ visibleHierarchyLabels: hierarchy.length,
1166
+ visibleFreeTextLabels: freeText.length,
1167
+ visibleGAAreas: visibleGA.length
1168
+ },
1169
+ overviewStyle: evidence.overviewStyle,
1170
+ composition: {
1171
+ hierarchy: evidence.hierarchyLabels.filter((label) => label.role === "name").map((label) => ({
1172
+ id: label.id,
1173
+ kind: label.kind,
1174
+ label: label.label,
1175
+ visible: label.visible
1176
+ })).sort((left, right) => left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id)),
1177
+ labelledObjects: evidence.freeTextLabels.map((label) => ({
1178
+ objectId: label.objectId,
1179
+ kind: label.kind,
1180
+ text: label.text,
1181
+ visible: label.visible
1182
+ })).sort((left, right) => left.objectId.localeCompare(right.objectId) || left.kind.localeCompare(right.kind)),
1183
+ gaAreas: evidence.gaAreas.map((area) => ({
1184
+ areaId: area.areaId,
1185
+ label: area.label,
1186
+ categoryKey: area.categoryKey,
1187
+ ...area.sectionId ? { sectionId: area.sectionId } : {},
1188
+ visible: area.visible
1189
+ })).sort((left, right) => left.areaId.localeCompare(right.areaId)),
1190
+ bookableSectionIds: [...new Set(evidence.labels.flatMap((label) => label.sectionId ? [label.sectionId] : []))].sort(),
1191
+ categoryKeys: [.../* @__PURE__ */ new Set([
1192
+ ...evidence.labels.map((label) => label.categoryKey),
1193
+ ...evidence.gaAreas.map((area) => area.categoryKey)
1194
+ ])].sort(),
1195
+ activeCategoryKeys: [...activeCategoryKeys].sort()
1196
+ },
1197
+ metrics: {
1198
+ minimumRenderedBookableLabelPx: minimum(bookable.map((label) => label.renderedFontPx)),
1199
+ minimumBookableLabelContrast: minimum(bookableContrast),
1200
+ minimumRenderedHierarchyLabelPx: minimum(hierarchy.map((label) => label.renderedFontPx)),
1201
+ minimumHierarchyLabelContrast: minimum(hierarchyContrast),
1202
+ minimumRenderedFreeTextPx: minimum(freeText.map((label) => label.renderedFontPx)),
1203
+ minimumFreeTextContrast: minimum(freeTextContrast),
1204
+ minimumGAContrast: minimum(gaContrast),
1205
+ minimumEffectivePointerTargetPx: minimum(activePointerTargets.map((label) => label.pointerTarget.effectiveMinimumPx)),
1206
+ selectedRingContrast: selectedRingContrast == null ? null : Math.round(selectedRingContrast * 100) / 100
1207
+ },
1208
+ interaction: {
1209
+ applicable: {
1210
+ detail: targetLabels.length > 0 || targetGAAreas.length > 0,
1211
+ pointer: targetLabels.length > 0 || targetGAAreas.length > 0,
1212
+ held: targetLabels.length >= 2,
1213
+ booked: targetLabels.length >= 3,
1214
+ sectionFocus: targetLabels.some((label) => Boolean(label.sectionId)) || targetGAAreas.some((area) => Boolean(area.sectionId)),
1215
+ categoryFilter: (/* @__PURE__ */ new Set([
1216
+ ...evidence.labels.map((label) => label.categoryKey),
1217
+ ...evidence.gaAreas.map((area) => area.categoryKey)
1218
+ ])).size >= 2
1219
+ },
1220
+ selectedUnits: selected.length,
1221
+ heldUnits: held.length,
1222
+ bookedUnits: booked.length,
1223
+ activePointerTargets: activePointerTargets.length,
1224
+ focusedSectionId: evidence.focusedSectionId,
1225
+ focusBackdropVisible: evidence.focusBackdropVisible,
1226
+ categoryFilterKeys: evidence.categoryFilterKeys
1227
+ },
1228
+ findings: materialFindings
1229
+ };
1230
+ }
1231
+
1232
+ // src/engine/SeatmapRenderer.ts
1233
+ import { Konva } from "konva/lib/Core";
1234
+ import { Stage } from "konva/lib/Stage";
1235
+ import { Layer } from "konva/lib/Layer";
1236
+ import { Group } from "konva/lib/Group";
1237
+ import { Circle } from "konva/lib/shapes/Circle";
1238
+ import { Rect } from "konva/lib/shapes/Rect";
1239
+ import { Ellipse } from "konva/lib/shapes/Ellipse";
1240
+ import { Line } from "konva/lib/shapes/Line";
1241
+ import { Text } from "konva/lib/shapes/Text";
1242
+ import { Path } from "konva/lib/shapes/Path";
1243
+ import { Image as KImage } from "konva/lib/shapes/Image";
1244
+ import { Shape } from "konva/lib/Shape";
1245
+
796
1246
  // src/lib/money.ts
797
1247
  var DEFAULT_CURRENCY = "USD";
798
1248
  var displayLocale;
@@ -855,6 +1305,7 @@ var en = {
855
1305
  "picker.seatsLeftInSection.one": "{count} seat left",
856
1306
  "picker.seatsLeftInSection.other": "{count} seats left",
857
1307
  "picker.overview": "Overview",
1308
+ "picker.entrance": "Entrance",
858
1309
  "picker.tapSeatHint": "Tap any seat to check its view",
859
1310
  "picker.ticketTierFor": "Ticket tier for {label}",
860
1311
  "picker.viewFromSeat": "View from seat {label}",
@@ -4826,13 +5277,15 @@ var PickerController = class {
4826
5277
  const sourceLabel = source && "label" in source && typeof source.label === "string" ? source.label : void 0;
4827
5278
  const sourceDisplayLabel = source && "displayLabel" in source && typeof source.displayLabel === "string" && source.displayLabel ? source.displayLabel : sourceLabel;
4828
5279
  const rowLabel = s.kind === "booth" ? void 0 : sourceDisplayLabel;
5280
+ const rowType = source && "displayType" in source && typeof source.displayType === "string" && source.displayType.trim() ? source.displayType.trim() : void 0;
4829
5281
  const visibleSeatLabel = s.displayLabel ?? s.label;
4830
5282
  const labelParts = visibleSeatLabel.split("-");
4831
5283
  const seatNumber = sourceDisplayLabel && visibleSeatLabel.startsWith(`${sourceDisplayLabel}-`) ? visibleSeatLabel.slice(sourceDisplayLabel.length + 1) : s.kind === "booth" ? visibleSeatLabel : labelParts[labelParts.length - 1] ?? visibleSeatLabel;
4832
5284
  this.seatContext.set(s.id, {
4833
5285
  sectionLabel: sectionLabels.get(membership.objectToSection.get(s.rowId) ?? ""),
4834
5286
  rowLabel,
4835
- seatNumber
5287
+ seatNumber,
5288
+ rowType
4836
5289
  });
4837
5290
  }
4838
5291
  const currency = res.event.currency ?? this.opts.currency;
@@ -5467,6 +5920,7 @@ var PickerController = class {
5467
5920
  id,
5468
5921
  label: sec.label,
5469
5922
  zoneLabel: zone?.label ?? "",
5923
+ ...sec.entrance && sec.entrance.trim() ? { entrance: sec.entrance.trim() } : {},
5470
5924
  color,
5471
5925
  seatsLeft,
5472
5926
  priceMin: prices.length ? prices[0] : 0,
@@ -6000,9 +6454,9 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
6000
6454
 
6001
6455
  // src/i18n/bundles.ts
6002
6456
  var LOADERS = {
6003
- es: () => import("./es-47HFAWS6.js").then((m) => ({ default: m.es })),
6004
- de: () => import("./de-EIG65UFU.js").then((m) => ({ default: m.de })),
6005
- fr: () => import("./fr-7IXNNFBS.js").then((m) => ({ default: m.fr }))
6457
+ es: () => import("./es-SKAODLTR.js").then((m) => ({ default: m.es })),
6458
+ de: () => import("./de-73TIXYJH.js").then((m) => ({ default: m.de })),
6459
+ fr: () => import("./fr-J4637T6V.js").then((m) => ({ default: m.fr }))
6006
6460
  };
6007
6461
  var loaded = /* @__PURE__ */ new Set(["en"]);
6008
6462
  async function loadLocale(code) {
@@ -6031,6 +6485,7 @@ export {
6031
6485
  MAX_EVENT_INVENTORY,
6032
6486
  MAX_GA_CAPACITY,
6033
6487
  PickerController,
6488
+ RENDERED_QUALITY_REPORT_VERSION,
6034
6489
  SUPPORTED_LOCALES,
6035
6490
  SeatmapRenderer,
6036
6491
  UNGROUPED_ID,
@@ -6058,6 +6513,7 @@ export {
6058
6513
  generateSeatThumb,
6059
6514
  getLocale,
6060
6515
  hiddenObjectIds,
6516
+ inspectRenderedQualityEvidence,
6061
6517
  isGaUnitLabel,
6062
6518
  isSectionHidden,
6063
6519
  layerOf,