diff --git a/shared/components/rstack-tab-bar.ts b/shared/components/rstack-tab-bar.ts index e4c3480..83addb3 100644 --- a/shared/components/rstack-tab-bar.ts +++ b/shared/components/rstack-tab-bar.ts @@ -1162,13 +1162,14 @@ const STYLES = ` background: rgba(34,211,238,0.1); } -/* ── Stack view ── */ +/* ── Stack view (3D) ── */ .stack-view { padding: 12px; - overflow: auto; - max-height: 50vh; + overflow: hidden; + max-height: 60vh; transition: max-height 0.3s ease; + position: relative; } :host-context([data-theme="dark"]) .stack-view { @@ -1180,21 +1181,249 @@ const STYLES = ` border-bottom: 1px solid rgba(0,0,0,0.06); } -.stack-view svg { - display: block; - margin: 0 auto; +.stack-view-3d { + perspective-origin: 50% 40%; + width: 100%; + height: 340px; + display: flex; + align-items: center; + justify-content: center; + cursor: grab; + user-select: none; } -.stack-layer { cursor: pointer; } -.stack-layer:hover rect { stroke-width: 2.5; } -.stack-layer--active rect { stroke-dasharray: none; } +.stack-scene { + transform-style: preserve-3d; + position: relative; + width: 320px; + height: 80px; +} + +/* ── Layer planes ── */ + +.layer-plane { + position: absolute; + width: 320px; + min-height: 70px; + border-radius: 10px; + border: 1px solid var(--layer-color); + padding: 10px 14px; + cursor: pointer; + transition: border-color 0.2s, box-shadow 0.2s; + transform-style: preserve-3d; + backface-visibility: hidden; +} + +:host-context([data-theme="dark"]) .layer-plane { + background: rgba(15,23,42,0.65); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + color: #e2e8f0; +} +:host-context([data-theme="light"]) .layer-plane { + background: rgba(255,255,255,0.7); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + color: #1e293b; +} + +.layer-plane:hover { + border-width: 2px; + box-shadow: 0 0 16px color-mix(in srgb, var(--layer-color) 30%, transparent); +} + +.layer-plane--active { + border-width: 2px; + box-shadow: 0 0 24px color-mix(in srgb, var(--layer-color) 40%, transparent); +} +:host-context([data-theme="dark"]) .layer-plane--active { + background: rgba(15,23,42,0.85); +} +:host-context([data-theme="light"]) .layer-plane--active { + background: rgba(255,255,255,0.9); +} /* Drag-to-connect visual states */ -.stack-layer.flow-drag-source rect { stroke-dasharray: 4 2; stroke-width: 2.5; } -.stack-layer.flow-drag-target rect { stroke-width: 3; filter: brightness(1.3); } +.layer-plane.flow-drag-source { + border-style: dashed; + border-width: 2px; +} +.layer-plane.flow-drag-target { + border-width: 3px; + box-shadow: 0 0 30px color-mix(in srgb, var(--layer-color) 50%, transparent); +} -.stack-flow { cursor: pointer; } -.stack-flow:hover path { stroke-width: 4 !important; opacity: 1 !important; } +.layer-header { + display: flex; + align-items: center; + gap: 8px; +} + +.layer-badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 5px; + font-size: 0.55rem; + font-weight: 900; + color: #0f172a; + flex-shrink: 0; +} + +.layer-name { + font-size: 0.8rem; + font-weight: 600; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Feed port indicators */ +.layer-ports { + display: flex; + gap: 3px; + flex-shrink: 0; +} + +.feed-port { + width: 6px; + height: 6px; + border-radius: 50%; + opacity: 0.7; +} +.feed-port--in { box-shadow: inset 0 0 0 1.5px rgba(0,0,0,0.3); } +.feed-port--out { box-shadow: 0 0 3px currentColor; } + +/* Containment indicators */ +.layer-contained { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-top: 6px; +} + +.contained-feed { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 0.6rem; + padding: 2px 6px; + border-radius: 4px; + background: color-mix(in srgb, var(--feed-color) 10%, transparent); + border: 1px solid color-mix(in srgb, var(--feed-color) 25%, transparent); + opacity: 0.7; +} + +.contained-lock { + font-size: 0.55rem; +} + +/* ── Flow particles ── */ + +.flow-particle { + position: absolute; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--color); + box-shadow: 0 0 6px var(--color); + left: 50%; + top: 50%; + margin-left: -3px; + margin-top: -3px; + pointer-events: auto; + cursor: pointer; + animation: flow-particle var(--duration) linear var(--delay) infinite; +} + +@keyframes flow-particle { + 0% { transform: translateZ(var(--src-z)); opacity: 0; } + 8% { opacity: 1; } + 85% { opacity: 1; } + 95% { opacity: 0.5; transform: translateZ(var(--tgt-z)); } + 100% { transform: translateZ(var(--tgt-z)); opacity: 0; } +} + +/* ── Legend ── */ + +.stack-legend { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: center; + padding: 6px 0; +} + +.legend-item { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 0.65rem; + opacity: 0.6; +} + +.legend-dot { + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; +} + +/* ── Time scrubber ── */ + +.time-scrubber { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 16px; + justify-content: center; +} + +.scrubber-playpause { + width: 24px; + height: 24px; + border: none; + border-radius: 50%; + background: rgba(34,211,238,0.15); + color: #22d3ee; + font-size: 0.7rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.15s; + flex-shrink: 0; +} +.scrubber-playpause:hover { background: rgba(34,211,238,0.25); } + +.scrubber-range { + flex: 1; + max-width: 200px; + accent-color: #22d3ee; + height: 4px; +} + +.scrubber-label { + font-size: 0.65rem; + font-weight: 700; + opacity: 0.5; + width: 28px; + text-align: center; + flex-shrink: 0; +} + +/* ── Stack hint ── */ + +.stack-hint { + text-align: center; + font-size: 0.6rem; + opacity: 0.35; + padding: 2px 0 4px; +} /* ── Flow creation dialog ── */ @@ -1374,6 +1603,9 @@ const STYLES = ` .tab-label { display: none; } .tab { padding: 4px 8px; } .stack-view { max-height: 40vh; } + .stack-view-3d { height: 260px; } + .stack-scene { width: 240px; } + .layer-plane { width: 240px; min-height: 56px; padding: 8px 10px; } .flow-dialog { width: 240px; } } `; diff --git a/shared/module.ts b/shared/module.ts index d412db4..ba6ed9d 100644 --- a/shared/module.ts +++ b/shared/module.ts @@ -1,5 +1,5 @@ import { Hono } from "hono"; -import type { FlowKind } from "../lib/layer-types"; +import type { FlowKind, FeedDefinition } from "../lib/layer-types"; export type { FeedDefinition } from "../lib/layer-types"; /**