@industry-theme/file-city-panel 0.3.1 → 0.3.3

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.
@@ -38567,16 +38567,244 @@ const files = {
38567
38567
  defaultConfig,
38568
38568
  includeUnmatched
38569
38569
  };
38570
+ class TourValidationError extends Error {
38571
+ constructor(message, field, value) {
38572
+ super(message);
38573
+ this.field = field;
38574
+ this.value = value;
38575
+ this.name = "TourValidationError";
38576
+ }
38577
+ }
38578
+ function isValidHexColor(color2) {
38579
+ return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(color2);
38580
+ }
38581
+ function isValidVersion(version2) {
38582
+ return /^\d+\.\d+\.\d+$/.test(version2);
38583
+ }
38584
+ function isValidRelativePath(path) {
38585
+ return !path.startsWith("/");
38586
+ }
38587
+ function isValidKebabCase(id2) {
38588
+ return /^[a-z0-9-]+$/.test(id2);
38589
+ }
38590
+ function validateStep(step, index2) {
38591
+ const errors = [];
38592
+ if (!step.id || typeof step.id !== "string") {
38593
+ errors.push(new TourValidationError(`Step ${index2}: Missing or invalid 'id'`, "id", step.id));
38594
+ } else if (!isValidKebabCase(step.id)) {
38595
+ errors.push(new TourValidationError(`Step ${index2}: 'id' must be kebab-case (lowercase, numbers, hyphens only)`, "id", step.id));
38596
+ }
38597
+ if (!step.title || typeof step.title !== "string") {
38598
+ errors.push(new TourValidationError(`Step ${index2}: Missing or invalid 'title'`, "title", step.title));
38599
+ }
38600
+ if (!step.description || typeof step.description !== "string") {
38601
+ errors.push(new TourValidationError(`Step ${index2}: Missing or invalid 'description'`, "description", step.description));
38602
+ }
38603
+ if (step.estimatedTime !== void 0 && (typeof step.estimatedTime !== "number" || step.estimatedTime < 1)) {
38604
+ errors.push(new TourValidationError(`Step ${index2}: 'estimatedTime' must be a positive number`, "estimatedTime", step.estimatedTime));
38605
+ }
38606
+ if (step.focusDirectory !== void 0) {
38607
+ if (typeof step.focusDirectory !== "string") {
38608
+ errors.push(new TourValidationError(`Step ${index2}: 'focusDirectory' must be a string`, "focusDirectory", step.focusDirectory));
38609
+ } else if (!isValidRelativePath(step.focusDirectory)) {
38610
+ errors.push(new TourValidationError(`Step ${index2}: 'focusDirectory' must be a relative path (no leading slash)`, "focusDirectory", step.focusDirectory));
38611
+ }
38612
+ }
38613
+ if (step.autoAdvance && step.autoAdvanceDelay !== void 0) {
38614
+ if (typeof step.autoAdvanceDelay !== "number" || step.autoAdvanceDelay < 1e3) {
38615
+ errors.push(new TourValidationError(`Step ${index2}: 'autoAdvanceDelay' must be at least 1000ms when auto-advance is enabled`, "autoAdvanceDelay", step.autoAdvanceDelay));
38616
+ }
38617
+ }
38618
+ if (step.highlightLayers) {
38619
+ step.highlightLayers.forEach((layer, layerIndex) => {
38620
+ if (!layer.id || !isValidKebabCase(layer.id)) {
38621
+ errors.push(new TourValidationError(`Step ${index2}, Layer ${layerIndex}: Invalid layer 'id' (must be kebab-case)`, `highlightLayers[${layerIndex}].id`, layer.id));
38622
+ }
38623
+ if (!layer.color || !isValidHexColor(layer.color)) {
38624
+ errors.push(new TourValidationError(`Step ${index2}, Layer ${layerIndex}: Invalid hex color`, `highlightLayers[${layerIndex}].color`, layer.color));
38625
+ }
38626
+ if (layer.opacity !== void 0 && (layer.opacity < 0 || layer.opacity > 1)) {
38627
+ errors.push(new TourValidationError(`Step ${index2}, Layer ${layerIndex}: 'opacity' must be between 0 and 1`, `highlightLayers[${layerIndex}].opacity`, layer.opacity));
38628
+ }
38629
+ if (layer.borderWidth !== void 0 && layer.borderWidth < 0) {
38630
+ errors.push(new TourValidationError(`Step ${index2}, Layer ${layerIndex}: 'borderWidth' must be positive`, `highlightLayers[${layerIndex}].borderWidth`, layer.borderWidth));
38631
+ }
38632
+ if (!layer.items || layer.items.length === 0) {
38633
+ errors.push(new TourValidationError(`Step ${index2}, Layer ${layerIndex}: 'items' array must not be empty`, `highlightLayers[${layerIndex}].items`, layer.items));
38634
+ } else {
38635
+ layer.items.forEach((item, itemIndex) => {
38636
+ if (!item.path || !isValidRelativePath(item.path)) {
38637
+ errors.push(new TourValidationError(`Step ${index2}, Layer ${layerIndex}, Item ${itemIndex}: Invalid relative path`, `highlightLayers[${layerIndex}].items[${itemIndex}].path`, item.path));
38638
+ }
38639
+ if (!item.type || item.type !== "file" && item.type !== "directory") {
38640
+ errors.push(new TourValidationError(`Step ${index2}, Layer ${layerIndex}, Item ${itemIndex}: 'type' must be 'file' or 'directory'`, `highlightLayers[${layerIndex}].items[${itemIndex}].type`, item.type));
38641
+ }
38642
+ });
38643
+ }
38644
+ });
38645
+ }
38646
+ if (step.highlightFiles) {
38647
+ step.highlightFiles.forEach((filePath, fileIndex) => {
38648
+ if (!isValidRelativePath(filePath)) {
38649
+ errors.push(new TourValidationError(`Step ${index2}, highlightFiles[${fileIndex}]: Must be a relative path (no leading slash)`, `highlightFiles[${fileIndex}]`, filePath));
38650
+ }
38651
+ });
38652
+ }
38653
+ if (step.interactiveActions) {
38654
+ step.interactiveActions.forEach((action, actionIndex) => {
38655
+ const needsTarget = ["click-file", "hover-directory", "toggle-layer"].includes(action.type);
38656
+ if (needsTarget && !action.target) {
38657
+ errors.push(new TourValidationError(`Step ${index2}, Action ${actionIndex}: '${action.type}' requires a 'target'`, `interactiveActions[${actionIndex}].target`, action.target));
38658
+ }
38659
+ });
38660
+ }
38661
+ return errors;
38662
+ }
38663
+ function validateTour(tour) {
38664
+ const errors = [];
38665
+ if (!tour.id || typeof tour.id !== "string") {
38666
+ errors.push(new TourValidationError("Missing or invalid 'id'", "id", tour.id));
38667
+ } else if (!isValidKebabCase(tour.id)) {
38668
+ errors.push(new TourValidationError("Tour 'id' must be kebab-case (lowercase, numbers, hyphens only)", "id", tour.id));
38669
+ }
38670
+ if (!tour.title || typeof tour.title !== "string") {
38671
+ errors.push(new TourValidationError("Missing or invalid 'title'", "title", tour.title));
38672
+ }
38673
+ if (!tour.description || typeof tour.description !== "string") {
38674
+ errors.push(new TourValidationError("Missing or invalid 'description'", "description", tour.description));
38675
+ }
38676
+ if (!tour.version || !isValidVersion(tour.version)) {
38677
+ errors.push(new TourValidationError("Invalid 'version' - must be semantic version (e.g., '1.0.0')", "version", tour.version));
38678
+ }
38679
+ if (!tour.steps || !Array.isArray(tour.steps) || tour.steps.length === 0) {
38680
+ errors.push(new TourValidationError("'steps' array must contain at least one step", "steps", tour.steps));
38681
+ } else {
38682
+ tour.steps.forEach((step, index2) => {
38683
+ const stepErrors = validateStep(step, index2);
38684
+ errors.push(...stepErrors);
38685
+ });
38686
+ const stepIds = /* @__PURE__ */ new Set();
38687
+ tour.steps.forEach((step, index2) => {
38688
+ if (stepIds.has(step.id)) {
38689
+ errors.push(new TourValidationError(`Duplicate step ID '${step.id}' at index ${index2}`, "steps", step.id));
38690
+ }
38691
+ stepIds.add(step.id);
38692
+ });
38693
+ }
38694
+ if (tour.audience !== void 0 && typeof tour.audience !== "string") {
38695
+ errors.push(new TourValidationError(`Invalid 'audience' - must be a string`, "audience", tour.audience));
38696
+ }
38697
+ return errors;
38698
+ }
38699
+ function parseTour(jsonString) {
38700
+ try {
38701
+ const parsed = JSON.parse(jsonString);
38702
+ if (typeof parsed !== "object" || parsed === null) {
38703
+ return {
38704
+ success: false,
38705
+ errors: [new TourValidationError("Tour JSON must be an object")]
38706
+ };
38707
+ }
38708
+ const errors = validateTour(parsed);
38709
+ if (errors.length > 0) {
38710
+ return {
38711
+ success: false,
38712
+ errors
38713
+ };
38714
+ }
38715
+ return {
38716
+ success: true,
38717
+ tour: parsed
38718
+ };
38719
+ } catch (error) {
38720
+ return {
38721
+ success: false,
38722
+ errors: [
38723
+ new TourValidationError(`Failed to parse JSON: ${error instanceof Error ? error.message : "Unknown error"}`)
38724
+ ]
38725
+ };
38726
+ }
38727
+ }
38728
+ function parseTourOrThrow(jsonString) {
38729
+ var _a;
38730
+ const result = parseTour(jsonString);
38731
+ if (!result.success || !result.tour) {
38732
+ const errorMessages2 = ((_a = result.errors) == null ? void 0 : _a.map((e) => e.message).join("\n")) || "Unknown error";
38733
+ throw new TourValidationError(`Tour validation failed:
38734
+ ${errorMessages2}`);
38735
+ }
38736
+ return result.tour;
38737
+ }
38738
+ function findTourFilePathInFileTree(fileTree) {
38739
+ const tourFiles = fileTree.allFiles.filter((file) => {
38740
+ const path = file.path;
38741
+ return path.endsWith(".tour.json") && !path.includes("/");
38742
+ });
38743
+ tourFiles.sort((a, b) => a.path.localeCompare(b.path));
38744
+ const tourFile = tourFiles[0];
38745
+ return tourFile ? tourFile.path : null;
38746
+ }
38747
+ function findAllTourFilesInFileTree(fileTree) {
38748
+ return fileTree.allFiles.filter((file) => file.path.endsWith(".tour.json")).map((file) => file.path).sort((a, b) => a.localeCompare(b));
38749
+ }
38750
+ async function loadTourFromFileTree(fileTree, readFile) {
38751
+ const tourFilePath = findTourFilePathInFileTree(fileTree);
38752
+ if (!tourFilePath) {
38753
+ return {
38754
+ success: false,
38755
+ errors: [new TourValidationError("No *.tour.json file found in repository root")]
38756
+ };
38757
+ }
38758
+ try {
38759
+ const tourContent = await readFile(tourFilePath);
38760
+ return parseTour(tourContent);
38761
+ } catch (error) {
38762
+ return {
38763
+ success: false,
38764
+ errors: [
38765
+ new TourValidationError(`Failed to read tour file: ${error instanceof Error ? error.message : "Unknown error"}`)
38766
+ ]
38767
+ };
38768
+ }
38769
+ }
38770
+ async function loadAllToursFromFileTree(fileTree, readFile) {
38771
+ const tourPaths = findAllTourFilesInFileTree(fileTree);
38772
+ const results = await Promise.all(tourPaths.map(async (path) => {
38773
+ try {
38774
+ const content = await readFile(path);
38775
+ const result = parseTour(content);
38776
+ return { path, result };
38777
+ } catch (error) {
38778
+ return {
38779
+ path,
38780
+ result: {
38781
+ success: false,
38782
+ errors: [
38783
+ new TourValidationError(`Failed to read tour file: ${error instanceof Error ? error.message : "Unknown error"}`)
38784
+ ]
38785
+ }
38786
+ };
38787
+ }
38788
+ }));
38789
+ return results;
38790
+ }
38570
38791
  const dist = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
38571
38792
  __proto__: null,
38572
38793
  CodeCityBuilderWithGrid,
38573
38794
  CommonSorts,
38574
38795
  GridLayoutManager,
38575
38796
  MultiVersionCityBuilder,
38797
+ TourValidationError,
38576
38798
  buildFileSystemTreeFromFileInfoList,
38577
38799
  buildMultiVersionCity,
38578
38800
  defaultFileColorConfig: files,
38579
- getFilesFromGitHubTree
38801
+ findAllTourFilesInFileTree,
38802
+ findTourFilePathInFileTree,
38803
+ getFilesFromGitHubTree,
38804
+ loadAllToursFromFileTree,
38805
+ loadTourFromFileTree,
38806
+ parseTour,
38807
+ parseTourOrThrow
38580
38808
  }, Symbol.toStringTag, { value: "Module" }));
38581
38809
  const require$$6 = /* @__PURE__ */ getAugmentedNamespace(dist);
38582
38810
  var fileColorOverrides = {};
@@ -48798,7 +49026,9 @@ const ContextContainer = ({
48798
49026
  className
48799
49027
  }) => {
48800
49028
  const { theme: theme2 } = useTheme();
49029
+ const isTop = position === "top";
48801
49030
  const isRight = position === "right";
49031
+ const isLeft = position === "left";
48802
49032
  return /* @__PURE__ */ jsx(
48803
49033
  "div",
48804
49034
  {
@@ -48809,8 +49039,10 @@ const ContextContainer = ({
48809
49039
  gap: "12px",
48810
49040
  padding: "12px 0",
48811
49041
  backgroundColor: theme2.colors.background,
48812
- borderTop: isRight ? "none" : `1px solid ${theme2.colors.border}`,
49042
+ borderTop: isRight || isLeft || isTop ? "none" : `1px solid ${theme2.colors.border}`,
49043
+ borderBottom: isTop ? `1px solid ${theme2.colors.border}` : "none",
48813
49044
  borderLeft: isRight ? `1px solid ${theme2.colors.border}` : "none",
49045
+ borderRight: isLeft ? `1px solid ${theme2.colors.border}` : "none",
48814
49046
  boxSizing: "border-box",
48815
49047
  flex: 1,
48816
49048
  minWidth: 0,
@@ -50826,7 +51058,7 @@ const TourPlayer = ({
50826
51058
  const showTTSControls = !!ttsAdapter && audioReady;
50827
51059
  const canAutoPlay = autoPlayAudio && autoAdvanceOnAudioEnd;
50828
51060
  if (!visible) return null;
50829
- const useAbsolutePositioning = position === "top" || position === "overlay";
51061
+ const useAbsolutePositioning = position === "overlay";
50830
51062
  const absoluteContainerStyle = {
50831
51063
  // Positioned mode (absolute/fixed positioning)
50832
51064
  position: "absolute",
@@ -50836,10 +51068,6 @@ const TourPlayer = ({
50836
51068
  borderColor: theme2.colors.border,
50837
51069
  boxShadow: "0 -4px 16px rgba(0,0,0,0.1)",
50838
51070
  zIndex: 100,
50839
- ...position === "top" && {
50840
- top: 0,
50841
- borderBottom: `2px solid ${theme2.colors.border}`
50842
- },
50843
51071
  ...position === "overlay" && {
50844
51072
  top: "50%",
50845
51073
  left: "50%",
@@ -50903,7 +51131,7 @@ const TourPlayer = ({
50903
51131
  "h2",
50904
51132
  {
50905
51133
  style: {
50906
- fontSize: theme2.fontSizes[3],
51134
+ fontSize: theme2.fontSizes[4],
50907
51135
  fontFamily: theme2.fonts.heading,
50908
51136
  color: theme2.colors.text,
50909
51137
  margin: 0
@@ -50920,7 +51148,7 @@ const TourPlayer = ({
50920
51148
  "p",
50921
51149
  {
50922
51150
  style: {
50923
- fontSize: theme2.fontSizes[1],
51151
+ fontSize: theme2.fontSizes[2],
50924
51152
  fontFamily: theme2.fonts.body,
50925
51153
  color: theme2.colors.textSecondary,
50926
51154
  margin: 0,
@@ -50946,7 +51174,7 @@ const TourPlayer = ({
50946
51174
  backgroundColor: theme2.colors.primary + "20",
50947
51175
  color: theme2.colors.primary,
50948
51176
  borderRadius: "4px",
50949
- fontSize: theme2.fontSizes[0],
51177
+ fontSize: theme2.fontSizes[1],
50950
51178
  fontFamily: theme2.fonts.body,
50951
51179
  fontWeight: 500
50952
51180
  },
@@ -50961,7 +51189,7 @@ const TourPlayer = ({
50961
51189
  backgroundColor: theme2.colors.accent + "20",
50962
51190
  color: theme2.colors.accent,
50963
51191
  borderRadius: "4px",
50964
- fontSize: theme2.fontSizes[0],
51192
+ fontSize: theme2.fontSizes[1],
50965
51193
  fontFamily: theme2.fonts.body,
50966
51194
  fontWeight: 500
50967
51195
  },
@@ -50990,7 +51218,7 @@ const TourPlayer = ({
50990
51218
  "div",
50991
51219
  {
50992
51220
  style: {
50993
- fontSize: theme2.fontSizes[1],
51221
+ fontSize: theme2.fontSizes[2],
50994
51222
  fontFamily: theme2.fonts.heading,
50995
51223
  color: theme2.colors.text,
50996
51224
  fontWeight: 600,
@@ -51005,7 +51233,7 @@ const TourPlayer = ({
51005
51233
  style: {
51006
51234
  margin: 0,
51007
51235
  paddingLeft: "20px",
51008
- fontSize: theme2.fontSizes[0],
51236
+ fontSize: theme2.fontSizes[1],
51009
51237
  fontFamily: theme2.fonts.body,
51010
51238
  color: theme2.colors.textSecondary
51011
51239
  },
@@ -51037,7 +51265,7 @@ const TourPlayer = ({
51037
51265
  color: theme2.colors.textSecondary,
51038
51266
  border: `1px solid ${theme2.colors.border}`,
51039
51267
  borderRadius: "6px",
51040
- fontSize: theme2.fontSizes[1],
51268
+ fontSize: theme2.fontSizes[2],
51041
51269
  fontFamily: theme2.fonts.body,
51042
51270
  cursor: "pointer",
51043
51271
  transition: "all 0.2s"
@@ -51066,7 +51294,7 @@ const TourPlayer = ({
51066
51294
  color: "#ffffff",
51067
51295
  border: "none",
51068
51296
  borderRadius: "6px",
51069
- fontSize: theme2.fontSizes[1],
51297
+ fontSize: theme2.fontSizes[2],
51070
51298
  fontFamily: theme2.fonts.body,
51071
51299
  fontWeight: 600,
51072
51300
  cursor: "pointer",
@@ -51100,7 +51328,7 @@ const TourPlayer = ({
51100
51328
  color: "#ffffff",
51101
51329
  border: "none",
51102
51330
  borderRadius: "6px",
51103
- fontSize: theme2.fontSizes[1],
51331
+ fontSize: theme2.fontSizes[2],
51104
51332
  fontFamily: theme2.fonts.body,
51105
51333
  fontWeight: 600,
51106
51334
  cursor: "pointer",
@@ -51177,7 +51405,7 @@ const TourPlayer = ({
51177
51405
  backgroundColor: theme2.colors.primary + "20",
51178
51406
  color: theme2.colors.primary,
51179
51407
  borderRadius: "6px",
51180
- fontSize: theme2.fontSizes[0],
51408
+ fontSize: theme2.fontSizes[1],
51181
51409
  fontFamily: theme2.fonts.monospace,
51182
51410
  fontWeight: 600
51183
51411
  },
@@ -51196,7 +51424,7 @@ const TourPlayer = ({
51196
51424
  alignItems: "center",
51197
51425
  gap: "4px",
51198
51426
  color: theme2.colors.textSecondary,
51199
- fontSize: theme2.fontSizes[0],
51427
+ fontSize: theme2.fontSizes[1],
51200
51428
  fontFamily: theme2.fonts.body
51201
51429
  },
51202
51430
  children: [
@@ -51216,7 +51444,7 @@ const TourPlayer = ({
51216
51444
  alignItems: "center",
51217
51445
  gap: "4px",
51218
51446
  color: theme2.colors.primary,
51219
- fontSize: theme2.fontSizes[0],
51447
+ fontSize: theme2.fontSizes[1],
51220
51448
  fontFamily: theme2.fonts.body,
51221
51449
  fontWeight: 600
51222
51450
  },
@@ -51246,7 +51474,7 @@ const TourPlayer = ({
51246
51474
  color: theme2.colors.textSecondary,
51247
51475
  border: `1px solid ${theme2.colors.border}`,
51248
51476
  borderRadius: "6px",
51249
- fontSize: theme2.fontSizes[0],
51477
+ fontSize: theme2.fontSizes[1],
51250
51478
  fontFamily: theme2.fonts.body,
51251
51479
  cursor: "pointer",
51252
51480
  transition: "all 0.2s"
@@ -51282,7 +51510,7 @@ const TourPlayer = ({
51282
51510
  "h2",
51283
51511
  {
51284
51512
  style: {
51285
- fontSize: theme2.fontSizes[3],
51513
+ fontSize: theme2.fontSizes[4],
51286
51514
  fontFamily: theme2.fonts.heading,
51287
51515
  color: theme2.colors.text,
51288
51516
  marginBottom: "8px",
@@ -51295,7 +51523,7 @@ const TourPlayer = ({
51295
51523
  "p",
51296
51524
  {
51297
51525
  style: {
51298
- fontSize: theme2.fontSizes[1],
51526
+ fontSize: theme2.fontSizes[2],
51299
51527
  fontFamily: theme2.fonts.body,
51300
51528
  color: theme2.colors.textSecondary,
51301
51529
  margin: 0,
@@ -51310,7 +51538,7 @@ const TourPlayer = ({
51310
51538
  "h4",
51311
51539
  {
51312
51540
  style: {
51313
- fontSize: theme2.fontSizes[1],
51541
+ fontSize: theme2.fontSizes[2],
51314
51542
  fontFamily: theme2.fonts.heading,
51315
51543
  color: theme2.colors.text,
51316
51544
  marginBottom: "8px"
@@ -51349,7 +51577,7 @@ const TourPlayer = ({
51349
51577
  "span",
51350
51578
  {
51351
51579
  style: {
51352
- fontSize: theme2.fontSizes[0],
51580
+ fontSize: theme2.fontSizes[1],
51353
51581
  fontFamily: theme2.fonts.body,
51354
51582
  color: theme2.colors.text
51355
51583
  },
@@ -51366,7 +51594,7 @@ const TourPlayer = ({
51366
51594
  "h4",
51367
51595
  {
51368
51596
  style: {
51369
- fontSize: theme2.fontSizes[1],
51597
+ fontSize: theme2.fontSizes[2],
51370
51598
  fontFamily: theme2.fonts.heading,
51371
51599
  color: theme2.colors.text,
51372
51600
  marginBottom: "8px"
@@ -51389,7 +51617,7 @@ const TourPlayer = ({
51389
51617
  color: theme2.colors.primary,
51390
51618
  borderRadius: "6px",
51391
51619
  border: `1px solid ${theme2.colors.border}`,
51392
- fontSize: theme2.fontSizes[0],
51620
+ fontSize: theme2.fontSizes[1],
51393
51621
  fontFamily: theme2.fonts.body,
51394
51622
  textDecoration: "none",
51395
51623
  transition: "all 0.2s"
@@ -51440,7 +51668,7 @@ const TourPlayer = ({
51440
51668
  color: theme2.colors.text,
51441
51669
  border: `1px solid ${theme2.colors.border}`,
51442
51670
  borderRadius: "8px",
51443
- fontSize: theme2.fontSizes[1],
51671
+ fontSize: theme2.fontSizes[2],
51444
51672
  fontFamily: theme2.fonts.body,
51445
51673
  cursor: (ttsState == null ? void 0 : ttsState.isLoading) || !audioReady ? "not-allowed" : "pointer",
51446
51674
  opacity: (ttsState == null ? void 0 : ttsState.isLoading) || !audioReady ? 0.5 : 1,
@@ -51477,7 +51705,7 @@ const TourPlayer = ({
51477
51705
  color: isAutoPlaying ? "#ffffff" : theme2.colors.text,
51478
51706
  border: `1px solid ${isAutoPlaying ? theme2.colors.primary : theme2.colors.border}`,
51479
51707
  borderRadius: "8px",
51480
- fontSize: theme2.fontSizes[1],
51708
+ fontSize: theme2.fontSizes[2],
51481
51709
  fontFamily: theme2.fonts.body,
51482
51710
  cursor: !audioReady ? "not-allowed" : "pointer",
51483
51711
  opacity: !audioReady ? 0.5 : 1,
@@ -51499,7 +51727,7 @@ const TourPlayer = ({
51499
51727
  )
51500
51728
  ] }),
51501
51729
  (ttsState == null ? void 0 : ttsState.isPlaying) && ttsState.duration && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
51502
- /* @__PURE__ */ jsx("span", { style: { fontSize: theme2.fontSizes[0], color: theme2.colors.textSecondary, minWidth: "35px" }, children: formatTime(ttsState.currentTime || 0) }),
51730
+ /* @__PURE__ */ jsx("span", { style: { fontSize: theme2.fontSizes[1], color: theme2.colors.textSecondary, minWidth: "35px" }, children: formatTime(ttsState.currentTime || 0) }),
51503
51731
  /* @__PURE__ */ jsx(
51504
51732
  "div",
51505
51733
  {
@@ -51523,7 +51751,7 @@ const TourPlayer = ({
51523
51751
  )
51524
51752
  }
51525
51753
  ),
51526
- /* @__PURE__ */ jsx("span", { style: { fontSize: theme2.fontSizes[0], color: theme2.colors.textSecondary, minWidth: "35px" }, children: formatTime(ttsState.duration) })
51754
+ /* @__PURE__ */ jsx("span", { style: { fontSize: theme2.fontSizes[1], color: theme2.colors.textSecondary, minWidth: "35px" }, children: formatTime(ttsState.duration) })
51527
51755
  ] })
51528
51756
  ]
51529
51757
  }
@@ -51555,7 +51783,7 @@ const TourPlayer = ({
51555
51783
  color: isFirstStep ? theme2.colors.textSecondary : theme2.colors.text,
51556
51784
  border: `1px solid ${theme2.colors.border}`,
51557
51785
  borderRadius: "8px",
51558
- fontSize: theme2.fontSizes[1],
51786
+ fontSize: theme2.fontSizes[2],
51559
51787
  fontFamily: theme2.fonts.body,
51560
51788
  cursor: isFirstStep ? "not-allowed" : "pointer",
51561
51789
  opacity: isFirstStep ? 0.5 : 1,
@@ -51610,7 +51838,7 @@ const TourPlayer = ({
51610
51838
  color: isLastStep ? theme2.colors.textSecondary : "#ffffff",
51611
51839
  border: `1px solid ${isLastStep ? theme2.colors.border : theme2.colors.primary}`,
51612
51840
  borderRadius: "8px",
51613
- fontSize: theme2.fontSizes[1],
51841
+ fontSize: theme2.fontSizes[2],
51614
51842
  fontFamily: theme2.fonts.body,
51615
51843
  fontWeight: 600,
51616
51844
  cursor: isLastStep ? "not-allowed" : "pointer",
@@ -53267,272 +53495,6 @@ function getLayersForColorMode(mode, buildings, qualityData, fileColorLayers, gi
53267
53495
  }
53268
53496
  }
53269
53497
  }
53270
- class TourValidationError extends Error {
53271
- constructor(message, field, value) {
53272
- super(message);
53273
- this.field = field;
53274
- this.value = value;
53275
- this.name = "TourValidationError";
53276
- }
53277
- }
53278
- function isValidHexColor(color2) {
53279
- return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(color2);
53280
- }
53281
- function isValidVersion(version2) {
53282
- return /^\d+\.\d+\.\d+$/.test(version2);
53283
- }
53284
- function isValidRelativePath(path) {
53285
- return !path.startsWith("/");
53286
- }
53287
- function validateStep(step, index2) {
53288
- const errors = [];
53289
- if (!step.id || typeof step.id !== "string") {
53290
- errors.push(new TourValidationError(`Step ${index2}: Missing or invalid 'id'`, "id", step.id));
53291
- } else if (!/^[a-z0-9-]+$/.test(step.id)) {
53292
- errors.push(
53293
- new TourValidationError(
53294
- `Step ${index2}: 'id' must be kebab-case (lowercase, numbers, hyphens only)`,
53295
- "id",
53296
- step.id
53297
- )
53298
- );
53299
- }
53300
- if (!step.title || typeof step.title !== "string") {
53301
- errors.push(new TourValidationError(`Step ${index2}: Missing or invalid 'title'`, "title", step.title));
53302
- }
53303
- if (!step.description || typeof step.description !== "string") {
53304
- errors.push(
53305
- new TourValidationError(`Step ${index2}: Missing or invalid 'description'`, "description", step.description)
53306
- );
53307
- }
53308
- if (step.estimatedTime !== void 0 && (typeof step.estimatedTime !== "number" || step.estimatedTime < 1)) {
53309
- errors.push(
53310
- new TourValidationError(
53311
- `Step ${index2}: 'estimatedTime' must be a positive number`,
53312
- "estimatedTime",
53313
- step.estimatedTime
53314
- )
53315
- );
53316
- }
53317
- if (step.focusDirectory !== void 0) {
53318
- if (typeof step.focusDirectory !== "string") {
53319
- errors.push(
53320
- new TourValidationError(
53321
- `Step ${index2}: 'focusDirectory' must be a string`,
53322
- "focusDirectory",
53323
- step.focusDirectory
53324
- )
53325
- );
53326
- } else if (!isValidRelativePath(step.focusDirectory)) {
53327
- errors.push(
53328
- new TourValidationError(
53329
- `Step ${index2}: 'focusDirectory' must be a relative path (no leading slash)`,
53330
- "focusDirectory",
53331
- step.focusDirectory
53332
- )
53333
- );
53334
- }
53335
- }
53336
- if (step.autoAdvance && step.autoAdvanceDelay !== void 0) {
53337
- if (typeof step.autoAdvanceDelay !== "number" || step.autoAdvanceDelay < 1e3) {
53338
- errors.push(
53339
- new TourValidationError(
53340
- `Step ${index2}: 'autoAdvanceDelay' must be at least 1000ms when auto-advance is enabled`,
53341
- "autoAdvanceDelay",
53342
- step.autoAdvanceDelay
53343
- )
53344
- );
53345
- }
53346
- }
53347
- if (step.highlightLayers) {
53348
- step.highlightLayers.forEach((layer, layerIndex) => {
53349
- if (!layer.id || !/^[a-z0-9-]+$/.test(layer.id)) {
53350
- errors.push(
53351
- new TourValidationError(
53352
- `Step ${index2}, Layer ${layerIndex}: Invalid layer 'id'`,
53353
- `highlightLayers[${layerIndex}].id`,
53354
- layer.id
53355
- )
53356
- );
53357
- }
53358
- if (!layer.color || !isValidHexColor(layer.color)) {
53359
- errors.push(
53360
- new TourValidationError(
53361
- `Step ${index2}, Layer ${layerIndex}: Invalid hex color`,
53362
- `highlightLayers[${layerIndex}].color`,
53363
- layer.color
53364
- )
53365
- );
53366
- }
53367
- if (layer.opacity !== void 0 && (layer.opacity < 0 || layer.opacity > 1)) {
53368
- errors.push(
53369
- new TourValidationError(
53370
- `Step ${index2}, Layer ${layerIndex}: 'opacity' must be between 0 and 1`,
53371
- `highlightLayers[${layerIndex}].opacity`,
53372
- layer.opacity
53373
- )
53374
- );
53375
- }
53376
- if (layer.borderWidth !== void 0 && layer.borderWidth < 0) {
53377
- errors.push(
53378
- new TourValidationError(
53379
- `Step ${index2}, Layer ${layerIndex}: 'borderWidth' must be positive`,
53380
- `highlightLayers[${layerIndex}].borderWidth`,
53381
- layer.borderWidth
53382
- )
53383
- );
53384
- }
53385
- if (!layer.items || layer.items.length === 0) {
53386
- errors.push(
53387
- new TourValidationError(
53388
- `Step ${index2}, Layer ${layerIndex}: 'items' array must not be empty`,
53389
- `highlightLayers[${layerIndex}].items`,
53390
- layer.items
53391
- )
53392
- );
53393
- } else {
53394
- layer.items.forEach((item, itemIndex) => {
53395
- if (!item.path || !isValidRelativePath(item.path)) {
53396
- errors.push(
53397
- new TourValidationError(
53398
- `Step ${index2}, Layer ${layerIndex}, Item ${itemIndex}: Invalid relative path`,
53399
- `highlightLayers[${layerIndex}].items[${itemIndex}].path`,
53400
- item.path
53401
- )
53402
- );
53403
- }
53404
- if (!item.type || item.type !== "file" && item.type !== "directory") {
53405
- errors.push(
53406
- new TourValidationError(
53407
- `Step ${index2}, Layer ${layerIndex}, Item ${itemIndex}: 'type' must be 'file' or 'directory'`,
53408
- `highlightLayers[${layerIndex}].items[${itemIndex}].type`,
53409
- item.type
53410
- )
53411
- );
53412
- }
53413
- });
53414
- }
53415
- });
53416
- }
53417
- if (step.highlightFiles) {
53418
- step.highlightFiles.forEach((filePath, fileIndex) => {
53419
- if (!isValidRelativePath(filePath)) {
53420
- errors.push(
53421
- new TourValidationError(
53422
- `Step ${index2}, highlightFiles[${fileIndex}]: Must be a relative path (no leading slash)`,
53423
- `highlightFiles[${fileIndex}]`,
53424
- filePath
53425
- )
53426
- );
53427
- }
53428
- });
53429
- }
53430
- if (step.interactiveActions) {
53431
- step.interactiveActions.forEach((action, actionIndex) => {
53432
- const needsTarget = ["click-file", "hover-directory", "toggle-layer"].includes(action.type);
53433
- if (needsTarget && !action.target) {
53434
- errors.push(
53435
- new TourValidationError(
53436
- `Step ${index2}, Action ${actionIndex}: '${action.type}' requires a 'target'`,
53437
- `interactiveActions[${actionIndex}].target`,
53438
- action.target
53439
- )
53440
- );
53441
- }
53442
- });
53443
- }
53444
- return errors;
53445
- }
53446
- function validateTour(tour) {
53447
- const errors = [];
53448
- if (!tour.id || typeof tour.id !== "string") {
53449
- errors.push(new TourValidationError("Missing or invalid 'id'", "id", tour.id));
53450
- } else if (!/^[a-z0-9-]+$/.test(tour.id)) {
53451
- errors.push(
53452
- new TourValidationError("Tour 'id' must be kebab-case (lowercase, numbers, hyphens only)", "id", tour.id)
53453
- );
53454
- }
53455
- if (!tour.title || typeof tour.title !== "string") {
53456
- errors.push(new TourValidationError("Missing or invalid 'title'", "title", tour.title));
53457
- }
53458
- if (!tour.description || typeof tour.description !== "string") {
53459
- errors.push(new TourValidationError("Missing or invalid 'description'", "description", tour.description));
53460
- }
53461
- if (!tour.version || !isValidVersion(tour.version)) {
53462
- errors.push(
53463
- new TourValidationError(
53464
- "Invalid 'version' - must be semantic version (e.g., '1.0.0')",
53465
- "version",
53466
- tour.version
53467
- )
53468
- );
53469
- }
53470
- if (!tour.steps || !Array.isArray(tour.steps) || tour.steps.length === 0) {
53471
- errors.push(new TourValidationError("'steps' array must contain at least one step", "steps", tour.steps));
53472
- } else {
53473
- tour.steps.forEach((step, index2) => {
53474
- const stepErrors = validateStep(step, index2);
53475
- errors.push(...stepErrors);
53476
- });
53477
- const stepIds = /* @__PURE__ */ new Set();
53478
- tour.steps.forEach((step, index2) => {
53479
- if (stepIds.has(step.id)) {
53480
- errors.push(new TourValidationError(`Duplicate step ID '${step.id}' at index ${index2}`, "steps", step.id));
53481
- }
53482
- stepIds.add(step.id);
53483
- });
53484
- }
53485
- if (tour.audience !== void 0 && typeof tour.audience !== "string") {
53486
- errors.push(
53487
- new TourValidationError(
53488
- `Invalid 'audience' - must be a string`,
53489
- "audience",
53490
- tour.audience
53491
- )
53492
- );
53493
- }
53494
- return errors;
53495
- }
53496
- function parseTour(jsonString) {
53497
- try {
53498
- const parsed = JSON.parse(jsonString);
53499
- if (typeof parsed !== "object" || parsed === null) {
53500
- return {
53501
- success: false,
53502
- errors: [new TourValidationError("Tour JSON must be an object")]
53503
- };
53504
- }
53505
- const errors = validateTour(parsed);
53506
- if (errors.length > 0) {
53507
- return {
53508
- success: false,
53509
- errors
53510
- };
53511
- }
53512
- return {
53513
- success: true,
53514
- tour: parsed
53515
- };
53516
- } catch (error) {
53517
- return {
53518
- success: false,
53519
- errors: [
53520
- new TourValidationError(
53521
- `Failed to parse JSON: ${error instanceof Error ? error.message : "Unknown error"}`
53522
- )
53523
- ]
53524
- };
53525
- }
53526
- }
53527
- function findTourFilePathInFileTree(fileTree) {
53528
- const tourFiles = fileTree.allFiles.filter((file) => {
53529
- const path = file.path;
53530
- return path.endsWith(".tour.json") && !path.includes("/");
53531
- });
53532
- tourFiles.sort((a, b) => a.path.localeCompare(b.path));
53533
- const tourFile = tourFiles[0];
53534
- return tourFile ? tourFile.path : null;
53535
- }
53536
53498
  const GIT_STATUS_COLORS = {
53537
53499
  staged: "#22c55e",
53538
53500
  // Green
@@ -53612,7 +53574,7 @@ const CodeCityPanelContent = ({
53612
53574
  return { mapSize, legendPosition: "right" };
53613
53575
  } else {
53614
53576
  const mapSize = width + HOVER_BAR_HEIGHT;
53615
- return { mapSize, legendPosition: "bottom" };
53577
+ return { mapSize, legendPosition: "top" };
53616
53578
  }
53617
53579
  }, [containerSize]);
53618
53580
  const fileTreeSlice = context.getSlice("fileTree");
@@ -54735,7 +54697,7 @@ const CodeCityPanelContent = ({
54735
54697
  }
54736
54698
  )
54737
54699
  ] }) }),
54738
- !showTourPlayer && !headerCompact && tourData && /* @__PURE__ */ jsxs(
54700
+ !showTourPlayer && tourData && /* @__PURE__ */ jsxs(
54739
54701
  "button",
54740
54702
  {
54741
54703
  onClick: () => {
@@ -54786,7 +54748,7 @@ const CodeCityPanelContent = ({
54786
54748
  position: "relative",
54787
54749
  overflow: "hidden",
54788
54750
  display: "flex",
54789
- flexDirection: layout.legendPosition === "right" ? "row" : "column"
54751
+ flexDirection: layout.legendPosition === "right" ? "row" : layout.legendPosition === "top" ? "column-reverse" : "column"
54790
54752
  },
54791
54753
  children: [
54792
54754
  /* @__PURE__ */ jsxs(
@@ -54794,9 +54756,9 @@ const CodeCityPanelContent = ({
54794
54756
  {
54795
54757
  style: {
54796
54758
  // In landscape: fixed width for square map, flex height
54797
- // In portrait: fixed height for square map + hover bar, full width
54759
+ // In portrait: limit to 65% height when tour player is on top
54798
54760
  width: layout.legendPosition === "right" ? layout.mapSize : "100%",
54799
- height: layout.legendPosition === "bottom" ? layout.mapSize : "100%",
54761
+ height: layout.legendPosition === "top" ? "65%" : layout.legendPosition === "bottom" ? layout.mapSize : "100%",
54800
54762
  flexShrink: 0,
54801
54763
  position: "relative",
54802
54764
  display: "flex",