diff --git a/shared/components/rstack-tab-bar.ts b/shared/components/rstack-tab-bar.ts index 5ff05ea..bcda473 100644 --- a/shared/components/rstack-tab-bar.ts +++ b/shared/components/rstack-tab-bar.ts @@ -101,6 +101,7 @@ export class RStackTabBar extends HTMLElement { #flowDialogTargetId = ""; // 3D scene state #sceneRotX = 55; + #sceneRotY = 0; #sceneRotZ = -15; #scenePerspective = 1200; #orbitDragging = false; @@ -555,7 +556,7 @@ export class RStackTabBar extends HTMLElement { const layerCount = this.#layers.length; if (layerCount === 0) return ""; - const layerSpacing = 80; + const layerSpacing = 120; const animDuration = 2 / this.#simSpeed; // Build layer planes @@ -676,7 +677,7 @@ export class RStackTabBar extends HTMLElement {
+ style="transform: rotateX(${this.#sceneRotX}deg) rotateY(${this.#sceneRotY}deg) rotateZ(${this.#sceneRotZ}deg);"> ${layersHtml} ${tubesHtml} ${particlesHtml} @@ -684,7 +685,7 @@ export class RStackTabBar extends HTMLElement {
${legendHtml}
${scrubberHtml} - ${this.#layers.length >= 2 ? `
Click an output port to wire, or drag between layers · Drag empty space to orbit
` : ""} + ${this.#layers.length >= 2 ? `
Drag to orbit · Shift+drag to roll · Scroll to zoom · Click output ports to wire
` : ""} ${this.#flowDialogOpen ? this.#renderFlowDialog() : ""}
`; @@ -777,7 +778,7 @@ export class RStackTabBar extends HTMLElement { scene.querySelectorAll(".flow-tube, .flow-particle").forEach(el => el.remove()); const layerZMap = new Map(); - const layerSpacing = 80; + const layerSpacing = 120; this.#layers.forEach((layer, i) => layerZMap.set(layer.id, i * layerSpacing)); const animDuration = 2 / this.#simSpeed; @@ -1123,12 +1124,18 @@ export class RStackTabBar extends HTMLElement { if (this.#orbitDragging) { const dx = e.clientX - this.#orbitLastX; const dy = e.clientY - this.#orbitLastY; - this.#sceneRotZ += dx * 0.3; - this.#sceneRotX = Math.max(10, Math.min(80, this.#sceneRotX - dy * 0.3)); + if (e.shiftKey) { + // Shift+drag: rotate around Z axis + this.#sceneRotZ += dx * 0.3; + } else { + // Normal drag: rotate around X and Y axes + this.#sceneRotY += dx * 0.3; + this.#sceneRotX = Math.max(-180, Math.min(180, this.#sceneRotX - dy * 0.3)); + } this.#orbitLastX = e.clientX; this.#orbitLastY = e.clientY; const scene = this.#shadow.getElementById("stack-scene"); - if (scene) scene.style.transform = `rotateX(${this.#sceneRotX}deg) rotateZ(${this.#sceneRotZ}deg)`; + if (scene) scene.style.transform = `rotateX(${this.#sceneRotX}deg) rotateY(${this.#sceneRotY}deg) rotateZ(${this.#sceneRotZ}deg)`; } // Drag-to-connect: track target via bounding rects (robust for 3D) @@ -1781,8 +1788,8 @@ const STYLES = ` .stack-view { padding: 12px; - overflow: hidden; - max-height: 60vh; + overflow: visible; + max-height: 80vh; transition: max-height 0.3s ease; position: relative; background: color-mix(in srgb, var(--rs-bg-surface) 50%, transparent); @@ -1790,14 +1797,15 @@ const STYLES = ` } .stack-view-3d { - perspective-origin: 50% 40%; + perspective-origin: 50% 50%; width: 100%; - height: 340px; + height: 420px; display: flex; align-items: center; justify-content: center; cursor: grab; user-select: none; + overflow: visible; } .stack-scene { @@ -1819,7 +1827,6 @@ const STYLES = ` cursor: pointer; transition: border-color 0.2s, box-shadow 0.2s; transform-style: preserve-3d; - backface-visibility: hidden; background: color-mix(in srgb, var(--rs-bg-surface) 65%, transparent); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); @@ -2297,7 +2304,7 @@ const STYLES = ` .tab-label { display: none; } .tab { padding: 4px 8px; } .stack-view { max-height: 40vh; } - .stack-view-3d { height: 260px; } + .stack-view-3d { height: 320px; } .stack-scene { width: 240px; } .layer-plane { width: 240px; min-height: 40px; padding: 8px 10px; } .io-chip { font-size: 0.5rem; padding: 1px 5px; max-width: 100px; }