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; }