@industry-theme/file-city-panel 0.5.62 → 0.5.64

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.
@@ -255103,7 +255103,7 @@ const toUiNote = (n) => {
255103
255103
  };
255104
255104
  const PANEL_WIDTH_PCT$3 = 38;
255105
255105
  const FLOAT_INSET$3 = 16;
255106
- const TOP_INSET$3 = 72;
255106
+ const TOP_INSET$2 = 72;
255107
255107
  const MIN_WIDTH_PX$3 = 360;
255108
255108
  const MIN_LEFT_GAP_PX$1 = 80;
255109
255109
  const RESIZE_HANDLE_WIDTH$3 = 6;
@@ -255259,7 +255259,7 @@ const SequenceEventDetailOverlay = React.forwardRef(function SequenceEventDetail
255259
255259
  const NOTES_PANEL_GAP = 12;
255260
255260
  const notesPanelStyle = {
255261
255261
  position: "absolute",
255262
- top: TOP_INSET$3,
255262
+ top: TOP_INSET$2,
255263
255263
  bottom: `calc(${bottomOffsetCss} + ${FLOAT_INSET$3}px)`,
255264
255264
  width: NOTES_PANEL_WIDTH,
255265
255265
  right: `calc(${detailWidthCss} + ${FLOAT_INSET$3}px + ${NOTES_PANEL_GAP}px)`,
@@ -255310,7 +255310,7 @@ const SequenceEventDetailOverlay = React.forwardRef(function SequenceEventDetail
255310
255310
  onAnimationEnd: () => setHasEntered(true),
255311
255311
  style: {
255312
255312
  position: "absolute",
255313
- top: TOP_INSET$3,
255313
+ top: TOP_INSET$2,
255314
255314
  right: FLOAT_INSET$3,
255315
255315
  // Anchor the bottom edge (mirroring the markdown overlay) so the
255316
255316
  // panel has a *definite* height — otherwise transient placeholder
@@ -255574,7 +255574,7 @@ const iconButtonStyle = (color2, disabled = false) => ({
255574
255574
  });
255575
255575
  const PANEL_WIDTH_PCT$2 = 38;
255576
255576
  const FLOAT_INSET$2 = 16;
255577
- const TOP_INSET$2 = 72;
255577
+ const TOP_INSET$1 = 72;
255578
255578
  const MIN_WIDTH_PX$2 = 360;
255579
255579
  const MIN_LEFT_GAP_PX = 80;
255580
255580
  const RESIZE_HANDLE_WIDTH$2 = 6;
@@ -255631,7 +255631,7 @@ const SequenceFilesOverlay = ({
255631
255631
  onAnimationEnd: () => setHasEntered(true),
255632
255632
  style: {
255633
255633
  position: "absolute",
255634
- top: TOP_INSET$2,
255634
+ top: TOP_INSET$1,
255635
255635
  right: FLOAT_INSET$2,
255636
255636
  bottom: `calc(${bottomOffsetCss} + ${FLOAT_INSET$2}px)`,
255637
255637
  width: widthPx != null ? `${widthPx}px` : `calc(${PANEL_WIDTH_PCT$2}% - ${FLOAT_INSET$2}px)`,
@@ -256360,7 +256360,7 @@ const primaryButton = (theme2) => ({
256360
256360
  });
256361
256361
  const PANEL_WIDTH_PCT$1 = 38;
256362
256362
  const FLOAT_INSET$1 = 16;
256363
- const TOP_INSET$1 = 72;
256363
+ const TOP_INSET = 72;
256364
256364
  const MIN_WIDTH_PX$1 = 360;
256365
256365
  const RESIZE_HANDLE_WIDTH$1 = 6;
256366
256366
  const SequenceMarkdownOverlay = ({
@@ -256475,7 +256475,7 @@ const SequenceMarkdownOverlay = ({
256475
256475
  onAnimationEnd: () => setHasEntered(true),
256476
256476
  style: {
256477
256477
  position: "absolute",
256478
- top: TOP_INSET$1,
256478
+ top: TOP_INSET,
256479
256479
  left: FLOAT_INSET$1,
256480
256480
  // Anchor the bottom edge too so the column has a *definite* height —
256481
256481
  // `IndustryMarkdownSlide` renders with `height: 100%` and only
@@ -257701,14 +257701,30 @@ const TrailLeaderLine = React.forwardRef(function TrailLeaderLine2({ containerRe
257701
257701
  const sx = canvasLeft + (v.x * 0.5 + 0.5) * size.width;
257702
257702
  const sy = canvasTop + (v.y * -0.5 + 0.5) * size.height;
257703
257703
  const HEADER_BAND_OFFSET = 22;
257704
- const nodeCenterX = nodeRect.left + nodeRect.width / 2 - containerRect.left;
257705
- const aimRight = nodeCenterX < sx;
257706
- const bx = aimRight ? nodeRect.right - containerRect.left : nodeRect.left - containerRect.left;
257704
+ const bx = nodeRect.left - containerRect.left;
257707
257705
  const by = nodeRect.top - containerRect.top + HEADER_BAND_OFFSET;
257708
- const dxRaw = (bx - sx) * 0.5;
257709
- const dxAbs = Math.max(80, Math.abs(dxRaw));
257710
- const dx = dxRaw < 0 ? -dxAbs : dxAbs;
257711
- const d = `M ${sx} ${sy} C ${sx + dx} ${sy}, ${bx - dx} ${by}, ${bx} ${by}`;
257706
+ const ELBOW_GAP = 60;
257707
+ const CORNER_RADIUS = 14;
257708
+ const elbowX = Math.max(
257709
+ canvasLeft + 8,
257710
+ Math.min(sx, bx) - ELBOW_GAP
257711
+ );
257712
+ const goingUp = by < sy;
257713
+ const r2 = Math.min(
257714
+ CORNER_RADIUS,
257715
+ Math.abs(sy - by) / 2,
257716
+ Math.abs(sx - elbowX) / 2,
257717
+ Math.abs(bx - elbowX) / 2
257718
+ );
257719
+ const cornerSign = goingUp ? -1 : 1;
257720
+ const d = [
257721
+ `M ${sx} ${sy}`,
257722
+ `L ${elbowX + r2} ${sy}`,
257723
+ `Q ${elbowX} ${sy} ${elbowX} ${sy + cornerSign * r2}`,
257724
+ `L ${elbowX} ${by - cornerSign * r2}`,
257725
+ `Q ${elbowX} ${by} ${elbowX + r2} ${by}`,
257726
+ `L ${bx} ${by}`
257727
+ ].join(" ");
257712
257728
  const pathEl = pathRef.current;
257713
257729
  const buildingEl = buildingMarkerRef.current;
257714
257730
  const nodeMarkerEl = nodeMarkerRef.current;
@@ -257790,9 +257806,141 @@ const TrailLeaderLine = React.forwardRef(function TrailLeaderLine2({ containerRe
257790
257806
  )
257791
257807
  ] });
257792
257808
  });
257809
+ const TrailFilePath = React.forwardRef(function TrailFilePath2({ containerRef, buildings, cityCenter }, ref) {
257810
+ const { theme: theme2 } = useTheme();
257811
+ const color2 = theme2.colors.accent ?? "#22d3ee";
257812
+ const groupRef = React.useRef(null);
257813
+ const projectScratch = React.useRef(new THREE.Vector3());
257814
+ const buildingsRef = React.useRef(buildings);
257815
+ const cityCenterRef = React.useRef(cityCenter);
257816
+ React.useEffect(() => {
257817
+ buildingsRef.current = buildings;
257818
+ }, [buildings]);
257819
+ React.useEffect(() => {
257820
+ cityCenterRef.current = cityCenter;
257821
+ }, [cityCenter]);
257822
+ const hideAll = React.useCallback(() => {
257823
+ const g2 = groupRef.current;
257824
+ if (!g2) return;
257825
+ g2.setAttribute("opacity", "0");
257826
+ }, []);
257827
+ React.useEffect(() => {
257828
+ if (buildings.length < 2) hideAll();
257829
+ }, [buildings, hideAll]);
257830
+ const onCameraFrame = React.useCallback(
257831
+ (camera, size) => {
257832
+ const targets = buildingsRef.current;
257833
+ const center = cityCenterRef.current;
257834
+ const container = containerRef.current;
257835
+ const g2 = groupRef.current;
257836
+ if (!targets || targets.length < 2 || !center || !container || !g2 || size.width === 0) {
257837
+ hideAll();
257838
+ return;
257839
+ }
257840
+ const containerRect = container.getBoundingClientRect();
257841
+ const canvasEl = container.querySelector("canvas");
257842
+ if (!canvasEl) {
257843
+ hideAll();
257844
+ return;
257845
+ }
257846
+ const canvasRect = canvasEl.getBoundingClientRect();
257847
+ const canvasLeft = canvasRect.left - containerRect.left;
257848
+ const canvasTop = canvasRect.top - containerRect.top;
257849
+ const points = [];
257850
+ const v = projectScratch.current;
257851
+ for (const b of targets) {
257852
+ v.set(b.position.x - center.x, 0, b.position.z - center.z).project(
257853
+ camera
257854
+ );
257855
+ const visible = v.z <= 1;
257856
+ const x2 = canvasLeft + (v.x * 0.5 + 0.5) * size.width;
257857
+ const y2 = canvasTop + (v.y * -0.5 + 0.5) * size.height;
257858
+ points.push({ x: x2, y: y2, visible });
257859
+ }
257860
+ let d = "";
257861
+ for (let i = 0; i < points.length; i++) {
257862
+ const p2 = points[i];
257863
+ if (!p2.visible) {
257864
+ d += "";
257865
+ continue;
257866
+ }
257867
+ if (i === 0 || !points[i - 1].visible) {
257868
+ d += `M ${p2.x} ${p2.y} `;
257869
+ } else {
257870
+ d += `L ${p2.x} ${p2.y} `;
257871
+ }
257872
+ }
257873
+ while (g2.children.length < 1 + targets.length * 2) {
257874
+ g2.appendChild(
257875
+ document.createElementNS("http://www.w3.org/2000/svg", "path")
257876
+ );
257877
+ g2.appendChild(
257878
+ document.createElementNS("http://www.w3.org/2000/svg", "circle")
257879
+ );
257880
+ g2.appendChild(
257881
+ document.createElementNS("http://www.w3.org/2000/svg", "text")
257882
+ );
257883
+ }
257884
+ while (g2.children.length > 1 + targets.length * 2) {
257885
+ g2.removeChild(g2.children[g2.children.length - 1]);
257886
+ }
257887
+ const pathEl = g2.children[0];
257888
+ pathEl.setAttribute("d", d.trim() || "M 0 0");
257889
+ pathEl.setAttribute("fill", "none");
257890
+ pathEl.setAttribute("stroke", color2);
257891
+ pathEl.setAttribute("stroke-width", "2");
257892
+ pathEl.setAttribute("stroke-dasharray", "6 4");
257893
+ pathEl.setAttribute("opacity", "0.75");
257894
+ for (let i = 0; i < targets.length; i++) {
257895
+ const dot = g2.children[1 + i * 2];
257896
+ const label = g2.children[1 + i * 2 + 1];
257897
+ const p2 = points[i];
257898
+ if (!p2.visible) {
257899
+ dot.setAttribute("opacity", "0");
257900
+ label.setAttribute("opacity", "0");
257901
+ continue;
257902
+ }
257903
+ dot.setAttribute("cx", String(p2.x));
257904
+ dot.setAttribute("cy", String(p2.y));
257905
+ dot.setAttribute("r", "6");
257906
+ dot.setAttribute("fill", color2);
257907
+ dot.setAttribute("stroke", "#0f1419");
257908
+ dot.setAttribute("stroke-width", "1.5");
257909
+ dot.setAttribute("opacity", "1");
257910
+ label.setAttribute("x", String(p2.x));
257911
+ label.setAttribute("y", String(p2.y - 10));
257912
+ label.setAttribute("text-anchor", "middle");
257913
+ label.setAttribute("fill", color2);
257914
+ label.setAttribute("font-size", "11");
257915
+ label.setAttribute("font-weight", "600");
257916
+ label.setAttribute("opacity", "1");
257917
+ label.textContent = String(i + 1);
257918
+ }
257919
+ g2.setAttribute("opacity", "1");
257920
+ },
257921
+ [containerRef, hideAll, color2]
257922
+ );
257923
+ React.useImperativeHandle(ref, () => ({ onCameraFrame }), [onCameraFrame]);
257924
+ return /* @__PURE__ */ jsx(
257925
+ "svg",
257926
+ {
257927
+ width: "100%",
257928
+ height: "100%",
257929
+ style: {
257930
+ position: "absolute",
257931
+ inset: 0,
257932
+ pointerEvents: "none",
257933
+ overflow: "visible",
257934
+ // Above the city canvas (auto z-index) but below the floating
257935
+ // overlays (z 1900) so the path only paints over the city.
257936
+ zIndex: 26
257937
+ },
257938
+ children: /* @__PURE__ */ jsx("g", { ref: groupRef, opacity: 0 })
257939
+ }
257940
+ );
257941
+ });
257793
257942
  const PANEL_WIDTH_PCT = 38;
257794
257943
  const FLOAT_INSET = 16;
257795
- const TOP_INSET = 16;
257796
257944
  const MIN_WIDTH_PX = 360;
257797
257945
  const RESIZE_HANDLE_WIDTH = 6;
257798
257946
  const TrailMarkdownOverlay = ({
@@ -257802,7 +257950,9 @@ const TrailMarkdownOverlay = ({
257802
257950
  slideIdPrefix,
257803
257951
  bottomOffset,
257804
257952
  containerRef: externalContainerRef,
257805
- nav
257953
+ disableBottomTransition,
257954
+ nav,
257955
+ files
257806
257956
  }) => {
257807
257957
  const { theme: theme2 } = useTheme();
257808
257958
  const body = markdown2.trim();
@@ -257845,7 +257995,7 @@ const TrailMarkdownOverlay = ({
257845
257995
  if (!drag2) return;
257846
257996
  const parent = (_a = containerRef.current) == null ? void 0 : _a.parentElement;
257847
257997
  const parentWidth = (parent == null ? void 0 : parent.clientWidth) ?? window.innerWidth;
257848
- const maxWidth = Math.max(MIN_WIDTH_PX, parentWidth - FLOAT_INSET * 2);
257998
+ const maxWidth = Math.max(MIN_WIDTH_PX, parentWidth - FLOAT_INSET);
257849
257999
  const dx = e.clientX - drag2.startX;
257850
258000
  const next2 = Math.min(maxWidth, Math.max(MIN_WIDTH_PX, drag2.startWidth + dx));
257851
258001
  setWidthPx(next2);
@@ -257871,16 +258021,25 @@ const TrailMarkdownOverlay = ({
257871
258021
  onAnimationEnd: () => setHasEntered(true),
257872
258022
  style: {
257873
258023
  position: "absolute",
257874
- top: TOP_INSET,
257875
- left: FLOAT_INSET,
257876
- bottom: `calc(${bottomOffsetCss} + ${FLOAT_INSET}px)`,
257877
- width: widthPx != null ? widthPx : `calc(${PANEL_WIDTH_PCT}% - ${FLOAT_INSET}px)`,
258024
+ top: 0,
258025
+ left: 0,
258026
+ // 1px gap above the drawer's top edge so the drawer reads
258027
+ // cleanly through any subpixel rounding / transition flicker.
258028
+ bottom: `calc(${bottomOffsetCss} + 1px)`,
258029
+ // Match the sequence drawer's slide animation so the
258030
+ // overlay's bottom edge tracks the drawer's height instead
258031
+ // of snapping when the host toggles `bottomOffset`. Skipped
258032
+ // while the drawer is being live-resized so the bottom edge
258033
+ // tracks the cursor without lag.
258034
+ transition: disableBottomTransition ? void 0 : "bottom 280ms ease",
258035
+ width: widthPx != null ? widthPx : `${PANEL_WIDTH_PCT}%`,
257878
258036
  minWidth: MIN_WIDTH_PX,
257879
258037
  backgroundColor: theme2.colors.background,
257880
- border: `1px solid ${theme2.colors.border}`,
257881
- borderRadius: 12,
258038
+ // Flush against the city container's left/top/bottom edges;
258039
+ // only the right edge separates from the canvas, so we draw
258040
+ // the border there only.
258041
+ borderRight: `1px solid ${theme2.colors.border}`,
257882
258042
  overflow: "hidden",
257883
- boxShadow: "0 12px 32px rgba(0, 0, 0, 0.28)",
257884
258043
  display: "flex",
257885
258044
  flexDirection: "column",
257886
258045
  zIndex: 1900,
@@ -257894,7 +258053,7 @@ const TrailMarkdownOverlay = ({
257894
258053
  to { transform: translateX(0); }
257895
258054
  }
257896
258055
  ` }),
257897
- /* @__PURE__ */ jsxs(
258056
+ eyebrow || title ? /* @__PURE__ */ jsxs(
257898
258057
  "div",
257899
258058
  {
257900
258059
  style: {
@@ -257908,7 +258067,7 @@ const TrailMarkdownOverlay = ({
257908
258067
  minWidth: 0
257909
258068
  },
257910
258069
  children: [
257911
- /* @__PURE__ */ jsx(
258070
+ eyebrow ? /* @__PURE__ */ jsx(
257912
258071
  "span",
257913
258072
  {
257914
258073
  style: {
@@ -257919,8 +258078,8 @@ const TrailMarkdownOverlay = ({
257919
258078
  },
257920
258079
  children: eyebrow
257921
258080
  }
257922
- ),
257923
- /* @__PURE__ */ jsx(
258081
+ ) : null,
258082
+ title ? /* @__PURE__ */ jsx(
257924
258083
  "span",
257925
258084
  {
257926
258085
  style: {
@@ -257933,23 +258092,35 @@ const TrailMarkdownOverlay = ({
257933
258092
  title,
257934
258093
  children: title
257935
258094
  }
257936
- )
258095
+ ) : null
257937
258096
  ]
257938
258097
  }
257939
- ),
257940
- /* @__PURE__ */ jsx("div", { style: { flex: 1, minHeight: 0, position: "relative" }, children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0 }, children: /* @__PURE__ */ jsx(
257941
- IndustryMarkdownSlide,
258098
+ ) : null,
258099
+ /* @__PURE__ */ jsx(
258100
+ "div",
257942
258101
  {
257943
- content: body,
257944
- slideIdPrefix,
257945
- slideIndex: 0,
257946
- isVisible: true,
257947
- theme: theme2,
257948
- transparentBackground: true,
257949
- enableKeyboardScrolling: false
258102
+ style: {
258103
+ flex: 1,
258104
+ minHeight: 0,
258105
+ maxHeight: files && files.length > 0 ? "30%" : void 0,
258106
+ position: "relative"
258107
+ },
258108
+ children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0 }, children: /* @__PURE__ */ jsx(
258109
+ IndustryMarkdownSlide,
258110
+ {
258111
+ content: body,
258112
+ slideIdPrefix,
258113
+ slideIndex: 0,
258114
+ isVisible: true,
258115
+ theme: theme2,
258116
+ transparentBackground: true,
258117
+ enableKeyboardScrolling: false
258118
+ }
258119
+ ) })
257950
258120
  }
257951
- ) }) }),
258121
+ ),
257952
258122
  nav ? /* @__PURE__ */ jsx(TrailMarkdownOverlayFooter, { nav }) : null,
258123
+ files && files.length > 0 ? /* @__PURE__ */ jsx(TrailFilesList, { files }) : null,
257953
258124
  /* @__PURE__ */ jsx(
257954
258125
  "div",
257955
258126
  {
@@ -257985,13 +258156,156 @@ const TrailMarkdownOverlay = ({
257985
258156
  }
257986
258157
  );
257987
258158
  };
258159
+ const TrailFilesList = ({ files }) => {
258160
+ const { theme: theme2 } = useTheme();
258161
+ return /* @__PURE__ */ jsxs(
258162
+ "div",
258163
+ {
258164
+ style: {
258165
+ borderTop: `1px solid ${theme2.colors.border}`,
258166
+ // Markdown body is capped at 50% above; files take the rest
258167
+ // of the remaining column. Grows past 50% if the summary is
258168
+ // shorter than its cap (the markdown body shrinks below 50%
258169
+ // to accommodate, since `max-height` doesn't force it).
258170
+ flex: 1,
258171
+ display: "flex",
258172
+ flexDirection: "column",
258173
+ minHeight: 0
258174
+ },
258175
+ children: [
258176
+ /* @__PURE__ */ jsx(
258177
+ "div",
258178
+ {
258179
+ style: {
258180
+ padding: "8px 14px 4px",
258181
+ fontFamily: theme2.fonts.body,
258182
+ fontSize: theme2.fontSizes[0],
258183
+ color: theme2.colors.textSecondary,
258184
+ letterSpacing: 0.4,
258185
+ textTransform: "uppercase",
258186
+ flexShrink: 0
258187
+ },
258188
+ children: "Files in this trail"
258189
+ }
258190
+ ),
258191
+ /* @__PURE__ */ jsx("div", { style: { overflowY: "auto", minHeight: 0, padding: "0 6px 8px" }, children: files.map((file) => {
258192
+ const fileName = file.sourcePath.split("/").pop() ?? file.sourcePath;
258193
+ const dir = file.sourcePath.slice(
258194
+ 0,
258195
+ Math.max(0, file.sourcePath.length - fileName.length - 1)
258196
+ );
258197
+ return /* @__PURE__ */ jsxs(
258198
+ "button",
258199
+ {
258200
+ type: "button",
258201
+ onClick: file.onClick,
258202
+ title: file.sourcePath,
258203
+ style: {
258204
+ display: "flex",
258205
+ alignItems: "center",
258206
+ justifyContent: "space-between",
258207
+ gap: 8,
258208
+ width: "100%",
258209
+ background: file.isActive ? `color-mix(in oklab, ${theme2.colors.accent} 20%, transparent)` : "transparent",
258210
+ border: "none",
258211
+ borderRadius: theme2.radii[2],
258212
+ padding: "6px 8px",
258213
+ cursor: "pointer",
258214
+ color: theme2.colors.text,
258215
+ fontFamily: theme2.fonts.monospace,
258216
+ fontSize: theme2.fontSizes[1],
258217
+ textAlign: "left"
258218
+ },
258219
+ onMouseEnter: (e) => {
258220
+ var _a;
258221
+ (_a = file.onHoverChange) == null ? void 0 : _a.call(file, file.sourcePath);
258222
+ if (file.isActive) return;
258223
+ e.currentTarget.style.background = `color-mix(in oklab, ${theme2.colors.text} 6%, transparent)`;
258224
+ },
258225
+ onMouseLeave: (e) => {
258226
+ var _a;
258227
+ (_a = file.onHoverChange) == null ? void 0 : _a.call(file, null);
258228
+ if (file.isActive) return;
258229
+ e.currentTarget.style.background = "transparent";
258230
+ },
258231
+ children: [
258232
+ /* @__PURE__ */ jsxs(
258233
+ "span",
258234
+ {
258235
+ style: {
258236
+ display: "flex",
258237
+ flexDirection: "column",
258238
+ alignItems: "flex-start",
258239
+ gap: 1,
258240
+ minWidth: 0,
258241
+ flex: 1,
258242
+ overflow: "hidden"
258243
+ },
258244
+ children: [
258245
+ /* @__PURE__ */ jsx(
258246
+ "span",
258247
+ {
258248
+ style: {
258249
+ fontWeight: file.isActive ? theme2.fontWeights.semibold : theme2.fontWeights.body,
258250
+ overflow: "hidden",
258251
+ textOverflow: "ellipsis",
258252
+ whiteSpace: "nowrap",
258253
+ maxWidth: "100%"
258254
+ },
258255
+ children: fileName
258256
+ }
258257
+ ),
258258
+ dir ? /* @__PURE__ */ jsx(
258259
+ "span",
258260
+ {
258261
+ style: {
258262
+ fontSize: theme2.fontSizes[0],
258263
+ color: theme2.colors.textTertiary,
258264
+ overflow: "hidden",
258265
+ textOverflow: "ellipsis",
258266
+ whiteSpace: "nowrap",
258267
+ direction: "rtl",
258268
+ textAlign: "left",
258269
+ maxWidth: "100%"
258270
+ },
258271
+ children: dir
258272
+ }
258273
+ ) : null
258274
+ ]
258275
+ }
258276
+ ),
258277
+ /* @__PURE__ */ jsx(
258278
+ "span",
258279
+ {
258280
+ style: {
258281
+ flexShrink: 0,
258282
+ color: theme2.colors.textSecondary,
258283
+ fontFamily: theme2.fonts.body,
258284
+ fontVariantNumeric: "tabular-nums"
258285
+ },
258286
+ children: file.markerCount
258287
+ }
258288
+ )
258289
+ ]
258290
+ },
258291
+ file.sourcePath
258292
+ );
258293
+ }) })
258294
+ ]
258295
+ }
258296
+ );
258297
+ };
257988
258298
  const TrailMarkdownOverlayFooter = ({
257989
258299
  nav
257990
258300
  }) => {
257991
258301
  const { theme: theme2 } = useTheme();
257992
258302
  const idle = nav.position < 0;
257993
- const canPrev = !idle && nav.position > 0;
258303
+ const canPrev = !idle;
257994
258304
  const canNext = !idle && nav.position < nav.total - 1;
258305
+ const startEnabled = nav.total > 0;
258306
+ const [startHover, setStartHover] = React.useState(false);
258307
+ const [startActive, setStartActive] = React.useState(false);
258308
+ const startBackground = !startEnabled ? theme2.colors.accent : startActive ? `color-mix(in oklab, ${theme2.colors.accent} 82%, black)` : startHover ? `color-mix(in oklab, ${theme2.colors.accent} 88%, black)` : theme2.colors.accent;
257995
258309
  const stepButtonStyle = (enabled) => ({
257996
258310
  background: "transparent",
257997
258311
  border: `1px solid ${theme2.colors.muted}`,
@@ -258008,11 +258322,14 @@ const TrailMarkdownOverlayFooter = ({
258008
258322
  "div",
258009
258323
  {
258010
258324
  style: {
258011
- padding: "8px 14px",
258325
+ // In idle state the Start button fills the entire footer
258326
+ // (no padding around it); in active state we keep the inset
258327
+ // chrome so the prev/counter/next row reads as a row.
258328
+ padding: idle ? 0 : "8px 14px",
258012
258329
  borderTop: `1px solid ${theme2.colors.border}`,
258013
258330
  display: "flex",
258014
- alignItems: "center",
258015
- justifyContent: idle ? "center" : "space-between",
258331
+ alignItems: "stretch",
258332
+ justifyContent: idle ? "stretch" : "space-between",
258016
258333
  gap: 8,
258017
258334
  flexShrink: 0
258018
258335
  },
@@ -258021,20 +258338,30 @@ const TrailMarkdownOverlayFooter = ({
258021
258338
  {
258022
258339
  type: "button",
258023
258340
  onClick: nav.onStart,
258024
- disabled: nav.total === 0,
258025
- title: nav.total === 0 ? "No markers in this repo" : "Jump to the first marker",
258341
+ disabled: !startEnabled,
258342
+ title: startEnabled ? "Jump to the first marker" : "No markers in this repo",
258343
+ onMouseEnter: () => setStartHover(true),
258344
+ onMouseLeave: () => {
258345
+ setStartHover(false);
258346
+ setStartActive(false);
258347
+ },
258348
+ onPointerDown: () => setStartActive(true),
258349
+ onPointerUp: () => setStartActive(false),
258350
+ onPointerCancel: () => setStartActive(false),
258026
258351
  style: {
258027
- background: theme2.colors.accent,
258352
+ flex: 1,
258353
+ background: startBackground,
258028
258354
  color: theme2.colors.background,
258029
258355
  border: "none",
258030
- borderRadius: theme2.radii[3],
258031
- padding: "6px 14px",
258356
+ borderRadius: 0,
258357
+ padding: "12px 14px",
258032
258358
  fontFamily: theme2.fonts.body,
258033
- fontSize: theme2.fontSizes[0],
258359
+ fontSize: theme2.fontSizes[1],
258034
258360
  fontWeight: theme2.fontWeights.semibold,
258035
- cursor: nav.total === 0 ? "not-allowed" : "pointer",
258036
- opacity: nav.total === 0 ? 0.5 : 1,
258037
- lineHeight: 1.2
258361
+ cursor: startEnabled ? "pointer" : "not-allowed",
258362
+ opacity: startEnabled ? 1 : 0.5,
258363
+ lineHeight: 1.2,
258364
+ transition: "background 120ms ease"
258038
258365
  },
258039
258366
  children: "Start trail"
258040
258367
  }
@@ -258355,9 +258682,10 @@ const pierreOptionsBase = {
258355
258682
  const pierreStyle = {
258356
258683
  display: "block"
258357
258684
  };
258358
- const SEQUENCE_DRAWER_HEIGHT_PCT = 38;
258685
+ const SEQUENCE_DRAWER_HEIGHT_PCT = 45;
258359
258686
  const SNIPPET_PANE_WIDTH_PX = 460;
258360
258687
  const HEADER_HEIGHT_PX = 48;
258688
+ const PANEL_TRANSITION_MS = 280;
258361
258689
  const THREE_D_TOGGLE_DISABLED = true;
258362
258690
  const FileCityTrailExplorerPanel = ({ context, actions }) => {
258363
258691
  var _a;
@@ -258438,6 +258766,10 @@ const FileCityTrailSequenceLayout = ({
258438
258766
  return;
258439
258767
  }, [hasLineCounts]);
258440
258768
  const effectiveShow3D = false;
258769
+ const [showSequenceDrawer, setShowSequenceDrawer] = React.useState(false);
258770
+ const [drawerHeightOverridePct, setDrawerHeightOverridePct] = React.useState(null);
258771
+ const [isResizingDrawer, setIsResizingDrawer] = React.useState(false);
258772
+ const drawerHeightPct = !showSequenceDrawer ? 0 : drawerHeightOverridePct ?? SEQUENCE_DRAWER_HEIGHT_PCT;
258441
258773
  const selectedMarker = React.useMemo(() => {
258442
258774
  if (!selectedMarkerId) return null;
258443
258775
  return trail2.markers.find((m) => m.id === selectedMarkerId) ?? null;
@@ -258465,8 +258797,16 @@ const FileCityTrailSequenceLayout = ({
258465
258797
  const first = markersForThisRepo[0];
258466
258798
  if (first) onSelectMarker(first.id);
258467
258799
  }, [markersForThisRepo, onSelectMarker]);
258800
+ const [snippetExpanded, setSnippetExpanded] = React.useState(false);
258801
+ React.useEffect(() => {
258802
+ setSnippetExpanded(false);
258803
+ }, [selectedMarkerId]);
258468
258804
  const handleStepPrev = React.useCallback(() => {
258469
- if (stepperIndex <= 0) return;
258805
+ if (stepperIndex < 0) return;
258806
+ if (stepperIndex === 0) {
258807
+ onSelectMarker(null);
258808
+ return;
258809
+ }
258470
258810
  const target = markersForThisRepo[stepperIndex - 1];
258471
258811
  if (target) onSelectMarker(target.id);
258472
258812
  }, [markersForThisRepo, stepperIndex, onSelectMarker]);
@@ -258475,6 +258815,33 @@ const FileCityTrailSequenceLayout = ({
258475
258815
  const target = markersForThisRepo[stepperIndex + 1];
258476
258816
  if (target) onSelectMarker(target.id);
258477
258817
  }, [markersForThisRepo, stepperIndex, onSelectMarker]);
258818
+ const [hoveredFilePath, setHoveredFilePath] = React.useState(
258819
+ null
258820
+ );
258821
+ const trailFiles = React.useMemo(() => {
258822
+ const counts = /* @__PURE__ */ new Map();
258823
+ const firstMarkerByPath = /* @__PURE__ */ new Map();
258824
+ const order2 = [];
258825
+ for (const m of markersForThisRepo) {
258826
+ if (!m.sourcePath) continue;
258827
+ const existing = counts.get(m.sourcePath) ?? 0;
258828
+ counts.set(m.sourcePath, existing + 1);
258829
+ if (!firstMarkerByPath.has(m.sourcePath)) {
258830
+ firstMarkerByPath.set(m.sourcePath, m.id);
258831
+ order2.push(m.sourcePath);
258832
+ }
258833
+ }
258834
+ return order2.map((sourcePath) => ({
258835
+ sourcePath,
258836
+ markerCount: counts.get(sourcePath) ?? 0,
258837
+ isActive: (selectedMarker == null ? void 0 : selectedMarker.sourcePath) === sourcePath,
258838
+ onClick: () => {
258839
+ const markerId = firstMarkerByPath.get(sourcePath);
258840
+ if (markerId) onSelectMarker(markerId);
258841
+ },
258842
+ onHoverChange: setHoveredFilePath
258843
+ }));
258844
+ }, [markersForThisRepo, selectedMarker, onSelectMarker]);
258478
258845
  const trailFilesHighlightLayers = React.useMemo(() => {
258479
258846
  if (!cityData) return null;
258480
258847
  const sourcePaths = markersForThisRepo.map((m) => m.sourcePath).filter((p2) => typeof p2 === "string" && p2.length > 0);
@@ -258510,9 +258877,39 @@ const FileCityTrailSequenceLayout = ({
258510
258877
  }));
258511
258878
  }, [markersForThisRepo, cityData]);
258512
258879
  const trailFilesActive = trailFilesHighlightLayers !== null && trailFilesHighlightLayers.length > 0;
258880
+ const hoveredFileHighlightLayer = React.useMemo(() => {
258881
+ if (!hoveredFilePath || !cityData) return null;
258882
+ const building = cityData.buildings.find((b) => b.path === hoveredFilePath) ?? cityData.buildings.find(
258883
+ (b) => b.path.endsWith(`/${hoveredFilePath}`)
258884
+ );
258885
+ if (!building) return null;
258886
+ return {
258887
+ id: "trail-files-hover",
258888
+ name: "Trail file hover",
258889
+ enabled: true,
258890
+ color: theme2.colors.accent,
258891
+ opacity: 0.9,
258892
+ borderWidth: 4,
258893
+ priority: 100,
258894
+ items: [
258895
+ {
258896
+ path: building.path,
258897
+ type: "file",
258898
+ renderStrategy: "fill"
258899
+ }
258900
+ ]
258901
+ };
258902
+ }, [hoveredFilePath, cityData, theme2.colors.accent]);
258903
+ const cityHighlightLayers = React.useMemo(() => {
258904
+ const layers = [];
258905
+ if (trailFilesHighlightLayers) layers.push(...trailFilesHighlightLayers);
258906
+ if (hoveredFileHighlightLayer) layers.push(hoveredFileHighlightLayer);
258907
+ return layers.length > 0 ? layers : void 0;
258908
+ }, [trailFilesHighlightLayers, hoveredFileHighlightLayer]);
258513
258909
  const containerRef = React.useRef(null);
258514
258910
  const snippetPaneRef = React.useRef(null);
258515
258911
  const leaderLineRef = React.useRef(null);
258912
+ const filePathRef = React.useRef(null);
258516
258913
  const [markdownOverlayWidth, setMarkdownOverlayWidth] = React.useState(null);
258517
258914
  const [snippetPaneEl, setSnippetPaneEl] = React.useState(null);
258518
258915
  const snippetPaneCallbackRef = React.useCallback(
@@ -258536,10 +258933,22 @@ const FileCityTrailSequenceLayout = ({
258536
258933
  z: (cityData.bounds.minZ + cityData.bounds.maxZ) / 2
258537
258934
  };
258538
258935
  }, [cityData]);
258936
+ const trailPathBuildings = React.useMemo(() => {
258937
+ if (!cityData || markersForThisRepo.length === 0) return [];
258938
+ const buildingByPath = new Map(cityData.buildings.map((b) => [b.path, b]));
258939
+ const out = [];
258940
+ for (const m of markersForThisRepo) {
258941
+ if (!m.sourcePath) continue;
258942
+ const b = buildingByPath.get(m.sourcePath) ?? cityData.buildings.find((cb) => cb.path.endsWith(`/${m.sourcePath}`));
258943
+ if (b) out.push(b);
258944
+ }
258945
+ return out;
258946
+ }, [cityData, markersForThisRepo]);
258539
258947
  const onCameraFrame = React.useCallback(
258540
258948
  (camera, size) => {
258541
- var _a2;
258949
+ var _a2, _b2;
258542
258950
  (_a2 = leaderLineRef.current) == null ? void 0 : _a2.onCameraFrame(camera, size);
258951
+ (_b2 = filePathRef.current) == null ? void 0 : _b2.onCameraFrame(camera, size);
258543
258952
  },
258544
258953
  []
258545
258954
  );
@@ -258584,8 +258993,8 @@ const FileCityTrailSequenceLayout = ({
258584
258993
  markdown: selectedMarker.description,
258585
258994
  slideIdPrefix: `trail-${trail2.id}-marker-${selectedMarker.id}`
258586
258995
  } : ((_b = trail2.summary) == null ? void 0 : _b.trim()) ? {
258587
- eyebrow: "Trail",
258588
- title: trail2.title,
258996
+ eyebrow: null,
258997
+ title: null,
258589
258998
  markdown: trail2.summary,
258590
258999
  slideIdPrefix: `trail-${trail2.id}-summary`
258591
259000
  } : null;
@@ -258600,11 +259009,10 @@ const FileCityTrailSequenceLayout = ({
258600
259009
  if (hasOverlayMarkdown && markdownOverlayWidth == null) return;
258601
259010
  if (showSnippetPane && snippetPaneWidth == null) return;
258602
259011
  const FLOAT_INSET2 = 16;
258603
- const leftInset = hasOverlayMarkdown ? FLOAT_INSET2 + (markdownOverlayWidth ?? 0) : 0;
258604
- const snippetW = snippetPaneWidth ?? SNIPPET_PANE_WIDTH_PX;
258605
- const rightInset = showSnippetPane ? snippetW + FLOAT_INSET2 : 0;
258606
- const topInset = FLOAT_INSET2;
258607
- const bottomInset = SEQUENCE_DRAWER_HEIGHT_PCT / 100 * containerSize.height;
259012
+ const leftInset = hasOverlayMarkdown ? markdownOverlayWidth ?? 0 : 0;
259013
+ const rightInset = 0;
259014
+ const topInset = showSnippetPane ? containerSize.height * 0.5 + FLOAT_INSET2 : FLOAT_INSET2;
259015
+ const bottomInset = showSnippetPane ? 0 : drawerHeightPct / 100 * containerSize.height;
258608
259016
  const result = computeCameraFraming({
258609
259017
  canvasW: containerSize.width,
258610
259018
  canvasH: containerSize.height,
@@ -258649,7 +259057,8 @@ const FileCityTrailSequenceLayout = ({
258649
259057
  showSnippetPane,
258650
259058
  snippetPaneWidth,
258651
259059
  hasOverlayMarkdown,
258652
- markdownOverlayWidth
259060
+ markdownOverlayWidth,
259061
+ drawerHeightPct
258653
259062
  ]);
258654
259063
  return /* @__PURE__ */ jsxs(
258655
259064
  "div",
@@ -258671,7 +259080,19 @@ const FileCityTrailSequenceLayout = ({
258671
259080
  markerCount: markersForThisRepo.length,
258672
259081
  show3D: effectiveShow3D,
258673
259082
  onToggle3D: () => setShow3D((v) => !v),
258674
- hideToggle: THREE_D_TOGGLE_DISABLED
259083
+ hideToggle: THREE_D_TOGGLE_DISABLED,
259084
+ showSequenceDrawer,
259085
+ onToggleSequenceDrawer: () => {
259086
+ setDrawerHeightOverridePct(null);
259087
+ setShowSequenceDrawer((v) => !v);
259088
+ },
259089
+ canExit: selectedMarkerId != null || showSequenceDrawer || snippetExpanded,
259090
+ onExitTrail: () => {
259091
+ onSelectMarker(null);
259092
+ setShowSequenceDrawer(false);
259093
+ setDrawerHeightOverridePct(null);
259094
+ setSnippetExpanded(false);
259095
+ }
258675
259096
  }
258676
259097
  ),
258677
259098
  /* @__PURE__ */ jsxs(
@@ -258698,10 +259119,19 @@ const FileCityTrailSequenceLayout = ({
258698
259119
  backgroundColor: theme2.colors.background,
258699
259120
  textColor: theme2.colors.textMuted,
258700
259121
  isGrown: effectiveShow3D,
258701
- highlightLayers: trailFilesHighlightLayers ?? void 0,
259122
+ highlightLayers: cityHighlightLayers,
258702
259123
  defaultBuildingColor: trailFilesActive ? theme2.colors.textTertiary : void 0
258703
259124
  }
258704
259125
  ) : /* @__PURE__ */ jsx(CityLoadingPlaceholder, {}),
259126
+ /* @__PURE__ */ jsx(
259127
+ TrailFilePath,
259128
+ {
259129
+ ref: filePathRef,
259130
+ containerRef,
259131
+ buildings: trailPathBuildings,
259132
+ cityCenter
259133
+ }
259134
+ ),
258705
259135
  /* @__PURE__ */ jsx(
258706
259136
  TrailLeaderLine,
258707
259137
  {
@@ -258719,7 +259149,8 @@ const FileCityTrailSequenceLayout = ({
258719
259149
  title: overlayMarkdown.title,
258720
259150
  markdown: overlayMarkdown.markdown,
258721
259151
  slideIdPrefix: overlayMarkdown.slideIdPrefix,
258722
- bottomOffset: `${SEQUENCE_DRAWER_HEIGHT_PCT}%`,
259152
+ bottomOffset: `${drawerHeightPct}%`,
259153
+ disableBottomTransition: isResizingDrawer,
258723
259154
  containerRef: setMarkdownOverlayEl,
258724
259155
  nav: markersForThisRepo.length > 0 ? {
258725
259156
  position: stepperIndex,
@@ -258727,7 +259158,8 @@ const FileCityTrailSequenceLayout = ({
258727
259158
  onStart: handleStartTrail,
258728
259159
  onPrev: handleStepPrev,
258729
259160
  onNext: handleStepNext
258730
- } : void 0
259161
+ } : void 0,
259162
+ files: showSequenceDrawer ? void 0 : trailFiles
258731
259163
  }
258732
259164
  ) : null,
258733
259165
  /* @__PURE__ */ jsx(
@@ -258736,7 +259168,13 @@ const FileCityTrailSequenceLayout = ({
258736
259168
  trail: trail2,
258737
259169
  view,
258738
259170
  selectedMarkerId,
258739
- onSelectMarker
259171
+ onSelectMarker,
259172
+ visible: showSequenceDrawer,
259173
+ heightPct: drawerHeightPct,
259174
+ containerHeightPx: (containerSize == null ? void 0 : containerSize.height) ?? null,
259175
+ onHeightChange: setDrawerHeightOverridePct,
259176
+ onResizeStart: () => setIsResizingDrawer(true),
259177
+ onResizeEnd: () => setIsResizingDrawer(false)
258740
259178
  }
258741
259179
  ),
258742
259180
  (selectedMarker == null ? void 0 : selectedMarker.snippet) ? /* @__PURE__ */ jsx(
@@ -258746,7 +259184,11 @@ const FileCityTrailSequenceLayout = ({
258746
259184
  marker: selectedMarker,
258747
259185
  snippet: selectedMarker.snippet,
258748
259186
  readFile,
258749
- onClose: () => onSelectMarker(null)
259187
+ onClose: () => onSelectMarker(null),
259188
+ drawerHeightPct,
259189
+ disableBottomTransition: isResizingDrawer,
259190
+ expanded: snippetExpanded,
259191
+ onToggleExpanded: () => setSnippetExpanded((v) => !v)
258750
259192
  }
258751
259193
  ) : null
258752
259194
  ]
@@ -258756,11 +259198,20 @@ const FileCityTrailSequenceLayout = ({
258756
259198
  }
258757
259199
  );
258758
259200
  };
259201
+ const DRAWER_RESIZE_HANDLE_HEIGHT = 6;
259202
+ const DRAWER_MIN_HEIGHT_PCT = 12;
259203
+ const DRAWER_MAX_HEIGHT_PCT = 80;
258759
259204
  const SequenceDrawer = ({
258760
259205
  trail: trail2,
258761
259206
  view,
258762
259207
  selectedMarkerId,
258763
- onSelectMarker
259208
+ onSelectMarker,
259209
+ visible,
259210
+ heightPct,
259211
+ containerHeightPx,
259212
+ onHeightChange,
259213
+ onResizeStart,
259214
+ onResizeEnd
258764
259215
  }) => {
258765
259216
  const { theme: theme2 } = useTheme();
258766
259217
  const inputs = React.useMemo(
@@ -258774,86 +259225,215 @@ const SequenceDrawer = ({
258774
259225
  },
258775
259226
  [onSelectMarker, selectedMarkerId]
258776
259227
  );
258777
- return /* @__PURE__ */ jsx(
259228
+ const [isResizing, setIsResizing] = React.useState(false);
259229
+ const dragStateRef = React.useRef(null);
259230
+ const handleResizePointerDown = React.useCallback(
259231
+ (e) => {
259232
+ if (e.button !== 0) return;
259233
+ if (containerHeightPx == null || containerHeightPx <= 0) return;
259234
+ e.preventDefault();
259235
+ dragStateRef.current = {
259236
+ startY: e.clientY,
259237
+ startHeightPct: heightPct,
259238
+ containerHeightPx
259239
+ };
259240
+ setIsResizing(true);
259241
+ onResizeStart();
259242
+ e.currentTarget.setPointerCapture(e.pointerId);
259243
+ },
259244
+ [heightPct, containerHeightPx, onResizeStart]
259245
+ );
259246
+ const handleResizePointerMove = React.useCallback(
259247
+ (e) => {
259248
+ const drag2 = dragStateRef.current;
259249
+ if (!drag2) return;
259250
+ const dy = e.clientY - drag2.startY;
259251
+ const deltaPct = dy / drag2.containerHeightPx * 100;
259252
+ const next2 = Math.min(
259253
+ DRAWER_MAX_HEIGHT_PCT,
259254
+ Math.max(DRAWER_MIN_HEIGHT_PCT, drag2.startHeightPct - deltaPct)
259255
+ );
259256
+ onHeightChange(next2);
259257
+ },
259258
+ [onHeightChange]
259259
+ );
259260
+ const handleResizePointerUp = React.useCallback(
259261
+ (e) => {
259262
+ if (!dragStateRef.current) return;
259263
+ dragStateRef.current = null;
259264
+ setIsResizing(false);
259265
+ onResizeEnd();
259266
+ if (e.currentTarget.hasPointerCapture(e.pointerId)) {
259267
+ e.currentTarget.releasePointerCapture(e.pointerId);
259268
+ }
259269
+ },
259270
+ [onResizeEnd]
259271
+ );
259272
+ const handleResizeDoubleClick = React.useCallback(() => {
259273
+ onHeightChange(null);
259274
+ }, [onHeightChange]);
259275
+ return /* @__PURE__ */ jsxs(
258778
259276
  "div",
258779
259277
  {
259278
+ "aria-hidden": !visible,
258780
259279
  style: {
258781
259280
  position: "absolute",
258782
259281
  left: 0,
258783
259282
  right: 0,
258784
259283
  bottom: 0,
258785
- height: `${SEQUENCE_DRAWER_HEIGHT_PCT}%`,
259284
+ height: visible ? `${heightPct}%` : 0,
259285
+ opacity: visible ? 1 : 0,
259286
+ pointerEvents: visible ? "auto" : "none",
258786
259287
  backgroundColor: withAlpha(theme2.colors.background, 92),
258787
259288
  borderTop: `1px solid ${theme2.colors.border}`,
258788
- zIndex: 10
259289
+ zIndex: 10,
259290
+ overflow: "hidden",
259291
+ transition: isResizing ? `opacity ${PANEL_TRANSITION_MS}ms ease` : `height ${PANEL_TRANSITION_MS}ms ease, opacity ${PANEL_TRANSITION_MS}ms ease`
258789
259292
  },
258790
- children: /* @__PURE__ */ jsx(
258791
- SequenceDiagramRenderer,
258792
- {
258793
- events: inputs.events,
258794
- edges: inputs.edges,
258795
- layoutOptions: inputs.layoutOptions,
258796
- width: "100%",
258797
- height: "100%",
258798
- showControls: false,
258799
- showBackground: false,
258800
- stickyHeaders: true,
258801
- selectedNodeId: selectedMarkerId ?? void 0,
258802
- onNodeClick: handleNodeClick2
258803
- }
258804
- )
259293
+ children: [
259294
+ visible && containerHeightPx != null ? /* @__PURE__ */ jsx(
259295
+ "div",
259296
+ {
259297
+ role: "separator",
259298
+ "aria-orientation": "horizontal",
259299
+ "aria-label": "Resize sequence drawer",
259300
+ onPointerDown: handleResizePointerDown,
259301
+ onPointerMove: handleResizePointerMove,
259302
+ onPointerUp: handleResizePointerUp,
259303
+ onPointerCancel: handleResizePointerUp,
259304
+ onDoubleClick: handleResizeDoubleClick,
259305
+ title: "Drag to resize · double-click to reset",
259306
+ style: {
259307
+ position: "absolute",
259308
+ top: 0,
259309
+ left: 0,
259310
+ right: 0,
259311
+ height: DRAWER_RESIZE_HANDLE_HEIGHT,
259312
+ cursor: "ns-resize",
259313
+ backgroundColor: isResizing ? theme2.colors.accent : "transparent",
259314
+ transition: isResizing ? void 0 : "background-color 120ms ease",
259315
+ touchAction: "none",
259316
+ zIndex: 1
259317
+ },
259318
+ onMouseEnter: (e) => {
259319
+ if (isResizing) return;
259320
+ e.currentTarget.style.backgroundColor = theme2.colors.border;
259321
+ },
259322
+ onMouseLeave: (e) => {
259323
+ if (isResizing) return;
259324
+ e.currentTarget.style.backgroundColor = "transparent";
259325
+ }
259326
+ }
259327
+ ) : null,
259328
+ /* @__PURE__ */ jsx(
259329
+ SequenceDiagramRenderer,
259330
+ {
259331
+ events: inputs.events,
259332
+ edges: inputs.edges,
259333
+ layoutOptions: inputs.layoutOptions,
259334
+ width: "100%",
259335
+ height: "100%",
259336
+ showControls: false,
259337
+ showBackground: false,
259338
+ stickyHeaders: true,
259339
+ selectedNodeId: selectedMarkerId ?? void 0,
259340
+ onNodeClick: handleNodeClick2
259341
+ }
259342
+ )
259343
+ ]
258805
259344
  }
258806
259345
  );
258807
259346
  };
258808
- const SnippetSidePane = React.forwardRef(function SnippetSidePane2({ marker, snippet: snippet2, readFile, onClose }, ref) {
259347
+ const SnippetSidePane = React.forwardRef(function SnippetSidePane2({
259348
+ marker,
259349
+ snippet: snippet2,
259350
+ readFile,
259351
+ onClose,
259352
+ drawerHeightPct,
259353
+ disableBottomTransition,
259354
+ expanded,
259355
+ onToggleExpanded
259356
+ }, ref) {
258809
259357
  const { theme: theme2 } = useTheme();
258810
259358
  if (!marker.sourcePath) {
258811
- return /* @__PURE__ */ jsx(SidePaneFrame, { ref, marker, onClose, children: /* @__PURE__ */ jsx(
258812
- "div",
259359
+ return /* @__PURE__ */ jsx(
259360
+ SidePaneFrame,
258813
259361
  {
258814
- style: {
258815
- fontSize: theme2.fontSizes[0],
258816
- color: theme2.colors.textSecondary,
258817
- padding: 8
258818
- },
258819
- children: "Marker has no sourcePath; snippet cannot be rendered."
259362
+ ref,
259363
+ marker,
259364
+ onClose,
259365
+ drawerHeightPct,
259366
+ disableBottomTransition,
259367
+ expanded,
259368
+ onToggleExpanded,
259369
+ children: /* @__PURE__ */ jsx(
259370
+ "div",
259371
+ {
259372
+ style: {
259373
+ fontSize: theme2.fontSizes[0],
259374
+ color: theme2.colors.textSecondary,
259375
+ padding: 8
259376
+ },
259377
+ children: "Marker has no sourcePath; snippet cannot be rendered."
259378
+ }
259379
+ )
258820
259380
  }
258821
- ) });
259381
+ );
258822
259382
  }
258823
259383
  const fileName = marker.sourcePath.split("/").pop() ?? marker.sourcePath;
258824
- return /* @__PURE__ */ jsx(SidePaneFrame, { ref, marker, onClose, children: snippet2.kind === "slice" ? /* @__PURE__ */ jsx(
258825
- TrailSnippetView,
258826
- {
258827
- filePath: marker.sourcePath,
258828
- fileName,
258829
- startLine: snippet2.startLine,
258830
- endLine: snippet2.endLine,
258831
- focusLine: snippet2.focusLine,
258832
- contextLines: snippet2.contextLines,
258833
- readFile,
258834
- background: theme2.colors.background
258835
- }
258836
- ) : /* @__PURE__ */ jsx(
258837
- TrailDiffSnippetView,
259384
+ return /* @__PURE__ */ jsx(
259385
+ SidePaneFrame,
258838
259386
  {
258839
- filePath: marker.sourcePath,
258840
- fileName,
258841
- oldContents: snippet2.oldContents,
258842
- newContents: snippet2.newContents,
258843
- readFile,
258844
- startLine: snippet2.startLine,
258845
- endLine: snippet2.endLine,
258846
- focusLine: snippet2.focusLine,
258847
- contextLines: snippet2.contextLines,
258848
- diffStyle: snippet2.diffStyle,
258849
- background: theme2.colors.background
259387
+ ref,
259388
+ marker,
259389
+ onClose,
259390
+ drawerHeightPct,
259391
+ disableBottomTransition,
259392
+ expanded,
259393
+ onToggleExpanded,
259394
+ children: snippet2.kind === "slice" ? /* @__PURE__ */ jsx(
259395
+ TrailSnippetView,
259396
+ {
259397
+ filePath: marker.sourcePath,
259398
+ fileName,
259399
+ startLine: snippet2.startLine,
259400
+ endLine: snippet2.endLine,
259401
+ focusLine: snippet2.focusLine,
259402
+ contextLines: snippet2.contextLines,
259403
+ readFile,
259404
+ background: theme2.colors.background
259405
+ }
259406
+ ) : /* @__PURE__ */ jsx(
259407
+ TrailDiffSnippetView,
259408
+ {
259409
+ filePath: marker.sourcePath,
259410
+ fileName,
259411
+ oldContents: snippet2.oldContents,
259412
+ newContents: snippet2.newContents,
259413
+ readFile,
259414
+ startLine: snippet2.startLine,
259415
+ endLine: snippet2.endLine,
259416
+ focusLine: snippet2.focusLine,
259417
+ contextLines: snippet2.contextLines,
259418
+ diffStyle: snippet2.diffStyle,
259419
+ background: theme2.colors.background
259420
+ }
259421
+ )
258850
259422
  }
258851
- ) });
259423
+ );
258852
259424
  });
258853
259425
  const SNIPPET_PANE_MIN_WIDTH_PX = 320;
258854
259426
  const SNIPPET_PANE_RESIZE_HANDLE_WIDTH = 6;
258855
259427
  const SidePaneFrame = React.forwardRef(
258856
- function SidePaneFrame2({ marker, onClose, children: children2 }, ref) {
259428
+ function SidePaneFrame2({
259429
+ marker,
259430
+ onClose,
259431
+ children: children2,
259432
+ drawerHeightPct,
259433
+ disableBottomTransition,
259434
+ expanded,
259435
+ onToggleExpanded
259436
+ }, ref) {
258857
259437
  const { theme: theme2 } = useTheme();
258858
259438
  const [widthPx, setWidthPx] = React.useState(null);
258859
259439
  const [isResizing, setIsResizing] = React.useState(false);
@@ -258924,7 +259504,7 @@ const SidePaneFrame = React.forwardRef(
258924
259504
  position: "absolute",
258925
259505
  top: 16,
258926
259506
  right: 16,
258927
- bottom: `calc(${SEQUENCE_DRAWER_HEIGHT_PCT}% + 16px)`,
259507
+ bottom: expanded ? `calc(${drawerHeightPct}% + 16px)` : "50%",
258928
259508
  width: widthPx ?? SNIPPET_PANE_WIDTH_PX,
258929
259509
  minWidth: SNIPPET_PANE_MIN_WIDTH_PX,
258930
259510
  backgroundColor: theme2.colors.background,
@@ -258935,6 +259515,11 @@ const SidePaneFrame = React.forwardRef(
258935
259515
  flexDirection: "column",
258936
259516
  overflow: "hidden",
258937
259517
  zIndex: 1900,
259518
+ // Match the sequence drawer's slide animation so the pane
259519
+ // grows/shrinks with the drawer instead of snapping. Skipped
259520
+ // while the drawer is being live-resized so the bottom edge
259521
+ // tracks the cursor without lag.
259522
+ transition: disableBottomTransition ? void 0 : `bottom ${PANEL_TRANSITION_MS}ms ease`,
258938
259523
  userSelect: isResizing ? "none" : void 0
258939
259524
  },
258940
259525
  children: [
@@ -259010,24 +259595,45 @@ const SidePaneFrame = React.forwardRef(
259010
259595
  }
259011
259596
  ) : null
259012
259597
  ] }),
259013
- /* @__PURE__ */ jsx(
259014
- "button",
259015
- {
259016
- onClick: onClose,
259017
- style: {
259018
- background: "transparent",
259019
- color: theme2.colors.text,
259020
- border: `1px solid ${theme2.colors.muted}`,
259021
- borderRadius: theme2.radii[2],
259022
- padding: "2px 8px",
259023
- cursor: "pointer",
259024
- fontSize: theme2.fontSizes[0],
259025
- fontFamily: theme2.fonts.body,
259026
- flexShrink: 0
259027
- },
259028
- children: "Close"
259029
- }
259030
- )
259598
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 6, flexShrink: 0 }, children: [
259599
+ /* @__PURE__ */ jsx(
259600
+ "button",
259601
+ {
259602
+ onClick: onToggleExpanded,
259603
+ "aria-pressed": expanded,
259604
+ title: expanded ? "Collapse to top half (city visible below)" : "Expand to fill the right column",
259605
+ style: {
259606
+ background: "transparent",
259607
+ color: theme2.colors.text,
259608
+ border: `1px solid ${theme2.colors.muted}`,
259609
+ borderRadius: theme2.radii[2],
259610
+ padding: "2px 8px",
259611
+ cursor: "pointer",
259612
+ fontSize: theme2.fontSizes[0],
259613
+ fontFamily: theme2.fonts.body,
259614
+ lineHeight: 1.2
259615
+ },
259616
+ children: expanded ? "▾" : "▴"
259617
+ }
259618
+ ),
259619
+ /* @__PURE__ */ jsx(
259620
+ "button",
259621
+ {
259622
+ onClick: onClose,
259623
+ style: {
259624
+ background: "transparent",
259625
+ color: theme2.colors.text,
259626
+ border: `1px solid ${theme2.colors.muted}`,
259627
+ borderRadius: theme2.radii[2],
259628
+ padding: "2px 8px",
259629
+ cursor: "pointer",
259630
+ fontSize: theme2.fontSizes[0],
259631
+ fontFamily: theme2.fonts.body
259632
+ },
259633
+ children: "Close"
259634
+ }
259635
+ )
259636
+ ] })
259031
259637
  ]
259032
259638
  }
259033
259639
  ),
@@ -259042,9 +259648,26 @@ const TrailHeader = ({
259042
259648
  markerCount,
259043
259649
  show3D,
259044
259650
  onToggle3D,
259045
- hideToggle = false
259651
+ hideToggle = false,
259652
+ showSequenceDrawer,
259653
+ onToggleSequenceDrawer,
259654
+ canExit,
259655
+ onExitTrail
259046
259656
  }) => {
259047
259657
  const { theme: theme2 } = useTheme();
259658
+ const toggleButtonStyle = (active) => ({
259659
+ flexShrink: 0,
259660
+ fontFamily: theme2.fonts.body,
259661
+ fontSize: theme2.fontSizes[0],
259662
+ fontWeight: theme2.fontWeights.medium,
259663
+ color: active ? theme2.colors.background : theme2.colors.text,
259664
+ background: active ? theme2.colors.accent : "transparent",
259665
+ border: `1px solid ${active ? theme2.colors.accent : theme2.colors.muted}`,
259666
+ borderRadius: theme2.radii[3],
259667
+ padding: "4px 10px",
259668
+ cursor: "pointer",
259669
+ lineHeight: 1.2
259670
+ });
259048
259671
  return /* @__PURE__ */ jsxs(
259049
259672
  "div",
259050
259673
  {
@@ -259102,29 +259725,54 @@ const TrailHeader = ({
259102
259725
  ]
259103
259726
  }
259104
259727
  ),
259105
- hideToggle ? null : /* @__PURE__ */ jsx(
259106
- "button",
259107
- {
259108
- type: "button",
259109
- onClick: onToggle3D,
259110
- "aria-pressed": show3D,
259111
- title: show3D ? "Switch to flat (2D) view" : "Switch to 3D view",
259112
- style: {
259113
- flexShrink: 0,
259114
- fontFamily: theme2.fonts.body,
259115
- fontSize: theme2.fontSizes[0],
259116
- fontWeight: theme2.fontWeights.medium,
259117
- color: show3D ? theme2.colors.background : theme2.colors.text,
259118
- background: show3D ? theme2.colors.accent : "transparent",
259119
- border: `1px solid ${show3D ? theme2.colors.accent : theme2.colors.muted}`,
259120
- borderRadius: theme2.radii[3],
259121
- padding: "4px 10px",
259122
- cursor: "pointer",
259123
- lineHeight: 1.2
259124
- },
259125
- children: "3D"
259126
- }
259127
- )
259728
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
259729
+ /* @__PURE__ */ jsx(
259730
+ "button",
259731
+ {
259732
+ type: "button",
259733
+ onClick: onToggleSequenceDrawer,
259734
+ "aria-pressed": showSequenceDrawer,
259735
+ title: showSequenceDrawer ? "Hide sequence diagram" : "Show sequence diagram",
259736
+ style: toggleButtonStyle(showSequenceDrawer),
259737
+ children: "Diagram"
259738
+ }
259739
+ ),
259740
+ /* @__PURE__ */ jsx(
259741
+ "button",
259742
+ {
259743
+ type: "button",
259744
+ onClick: onExitTrail,
259745
+ disabled: !canExit,
259746
+ title: canExit ? "Return to the trail summary" : "Already at the trail summary",
259747
+ style: {
259748
+ flexShrink: 0,
259749
+ fontFamily: theme2.fonts.body,
259750
+ fontSize: theme2.fontSizes[0],
259751
+ fontWeight: theme2.fontWeights.medium,
259752
+ color: canExit ? theme2.colors.text : theme2.colors.textTertiary,
259753
+ background: "transparent",
259754
+ border: `1px solid ${theme2.colors.muted}`,
259755
+ borderRadius: theme2.radii[3],
259756
+ padding: "4px 10px",
259757
+ cursor: canExit ? "pointer" : "not-allowed",
259758
+ opacity: canExit ? 1 : 0.5,
259759
+ lineHeight: 1.2
259760
+ },
259761
+ children: "Exit"
259762
+ }
259763
+ ),
259764
+ hideToggle ? null : /* @__PURE__ */ jsx(
259765
+ "button",
259766
+ {
259767
+ type: "button",
259768
+ onClick: onToggle3D,
259769
+ "aria-pressed": show3D,
259770
+ title: show3D ? "Switch to flat (2D) view" : "Switch to 3D view",
259771
+ style: toggleButtonStyle(show3D),
259772
+ children: "3D"
259773
+ }
259774
+ )
259775
+ ] })
259128
259776
  ]
259129
259777
  }
259130
259778
  );