You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
453 lines
12 KiB
453 lines
12 KiB
@import "tailwindcss";
|
|
|
|
@theme {
|
|
--color-canvas-bg: var(--canvas-bg);
|
|
}
|
|
|
|
:root {
|
|
color-scheme: dark;
|
|
--canvas-bg: #101112;
|
|
--canvas-background: var(--canvas-bg);
|
|
--canvas-grid: #454545;
|
|
--surface-1: #171819;
|
|
--surface-2: #202122;
|
|
--surface-3: #2a2b2d;
|
|
--surface-hover: #2f3033;
|
|
--surface-active: #3a3b3e;
|
|
--surface-overlay: rgba(0, 0, 0, 0.6);
|
|
--media-preview-bg: #000000;
|
|
--text-on-overlay: #ffffff;
|
|
--border-subtle: rgba(255, 255, 255, 0.08);
|
|
--border-strong: rgba(255, 255, 255, 0.14);
|
|
--text-primary: #f4f4f5;
|
|
--text-secondary: #d4d4d8;
|
|
--text-muted: #8f9298;
|
|
--text-disabled: #65686f;
|
|
--text-inverse: #171717;
|
|
--accent: #3b82f6;
|
|
--accent-hover: #60a5fa;
|
|
--accent-soft: rgba(59, 130, 246, 0.16);
|
|
--accent-ring: rgba(59, 130, 246, 0.4);
|
|
--danger: #ef4444;
|
|
--danger-soft: rgba(239, 68, 68, 0.16);
|
|
--success: #10b981;
|
|
--success-soft: rgba(16, 185, 129, 0.16);
|
|
--warning: #f59e0b;
|
|
--warning-soft: rgba(245, 158, 11, 0.16);
|
|
--shadow-panel: 0 12px 28px rgba(0, 0, 0, 0.32);
|
|
--shadow-node: 0 10px 22px rgba(0, 0, 0, 0.26);
|
|
--edge-default: rgba(148, 163, 184, 0.58);
|
|
--edge-muted: rgba(148, 163, 184, 0.26);
|
|
--edge-flow: #93c5fd;
|
|
--edge-flow-soft: #cee5ff;
|
|
--edge-loop: #d946ef;
|
|
--edge-label-bg: rgba(38, 38, 38, 0.92);
|
|
--group-bg-neutral: rgba(42, 43, 45, 0.52);
|
|
--group-border: rgba(255, 255, 255, 0.08);
|
|
--group-border-selected: rgba(255, 255, 255, 0.35);
|
|
--group-resize-border: rgba(255, 255, 255, 0.22);
|
|
--scrollbar-size: 8px;
|
|
--scrollbar-track: transparent;
|
|
--scrollbar-thumb: rgba(255, 255, 255, 0.22);
|
|
--scrollbar-thumb-hover: rgba(255, 255, 255, 0.34);
|
|
--node-selected-border: #3b82f6;
|
|
--node-selected-ring: rgba(59, 130, 246, 0.4);
|
|
--node-selected-shadow: 0 10px 22px rgba(59, 130, 246, 0.25);
|
|
--background: var(--canvas-bg);
|
|
--foreground: #fafafa;
|
|
--handle-color: #ffffff;
|
|
}
|
|
|
|
:root[data-canvas-theme="light"] {
|
|
color-scheme: light;
|
|
--canvas-bg: #f0f0f0;
|
|
--canvas-background: linear-gradient(132deg, #E9DAEE 3.04%, #E9E9E9 50%, #DAD6E5 96.96%);
|
|
--canvas-grid: #CBC4DD;
|
|
--surface-1: #ffffff;
|
|
--surface-2: #ffffff;
|
|
--surface-3: #f3f3f2;
|
|
--surface-hover: #f5f5f4;
|
|
--surface-active: #ececea;
|
|
--surface-overlay: rgba(15, 23, 42, 0.38);
|
|
--media-preview-bg: #000000;
|
|
--text-on-overlay: #ffffff;
|
|
--border-subtle: rgba(234, 235, 236, 0.24);
|
|
--border-strong: rgba(15, 23, 42, 0.12);
|
|
--text-primary: #1f2328;
|
|
--text-secondary: #4b5563;
|
|
--text-muted: #737780;
|
|
--text-disabled: #a1a4aa;
|
|
--text-inverse: #ffffff;
|
|
--accent: #2563eb;
|
|
--accent-hover: #1d4ed8;
|
|
--accent-soft: rgba(37, 99, 235, 0.12);
|
|
--accent-ring: rgba(37, 99, 235, 0.28);
|
|
--danger: #dc2626;
|
|
--danger-soft: rgba(220, 38, 38, 0.12);
|
|
--success: #059669;
|
|
--success-soft: rgba(5, 150, 105, 0.12);
|
|
--warning: #d97706;
|
|
--warning-soft: rgba(217, 119, 6, 0.14);
|
|
--shadow-panel: 0 8px 22px rgba(15, 23, 42, 0.08);
|
|
--shadow-node: 0 4px 12px rgba(15, 23, 42, 0.06);
|
|
--edge-default: rgba(100, 116, 139, 0.38);
|
|
--edge-muted: rgba(100, 116, 139, 0.18);
|
|
--edge-flow: #2563eb;
|
|
--edge-flow-soft: #60a5fa;
|
|
--edge-loop: #c026d3;
|
|
--edge-label-bg: rgba(255, 255, 255, 0.94);
|
|
--group-bg-neutral: rgba(31, 35, 40, 0.038);
|
|
--group-border: rgba(126, 122, 148, 0.18);
|
|
--group-border-selected: rgba(126, 122, 148, 0.28);
|
|
--group-resize-border: rgba(126, 122, 148, 0.28);
|
|
--scrollbar-size: 8px;
|
|
--scrollbar-track: transparent;
|
|
--scrollbar-thumb: rgba(15, 23, 42, 0.18);
|
|
--scrollbar-thumb-hover: rgba(15, 23, 42, 0.28);
|
|
--node-selected-border: rgba(39, 47, 66, 0.14);
|
|
--node-selected-ring: rgba(15, 23, 42, 0.12);
|
|
--node-selected-shadow: 0 4px 12px rgba(15, 23, 42, 0.08);
|
|
--foreground: var(--text-primary);
|
|
--handle-color: #4b5563;
|
|
}
|
|
|
|
html {
|
|
/* Required for overscroll-behavior to work in Chrome on macOS */
|
|
overflow-x: hidden;
|
|
overscroll-behavior-x: none;
|
|
}
|
|
|
|
body {
|
|
color: var(--foreground);
|
|
background: var(--canvas-background);
|
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
/* Prevent browser back/forward swipe navigation on macOS */
|
|
overscroll-behavior-x: none;
|
|
/* Prevent touch gestures from triggering navigation */
|
|
touch-action: pan-y pinch-zoom;
|
|
overflow-y: initial !important;
|
|
width: 100% !important;
|
|
}
|
|
|
|
::view-transition-old(root),
|
|
::view-transition-new(root) {
|
|
animation-duration: 260ms;
|
|
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
|
|
}
|
|
|
|
::view-transition-old(root) {
|
|
animation-name: canvas-theme-fade-out;
|
|
}
|
|
|
|
::view-transition-new(root) {
|
|
animation-name: canvas-theme-fade-in;
|
|
}
|
|
|
|
@keyframes canvas-theme-fade-out {
|
|
from {
|
|
opacity: 1;
|
|
}
|
|
to {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
@keyframes canvas-theme-fade-in {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
::view-transition-old(root),
|
|
::view-transition-new(root) {
|
|
animation-duration: 1ms;
|
|
}
|
|
}
|
|
|
|
/* React Flow customizations */
|
|
.react-flow {
|
|
/* Prevent browser back/forward swipe navigation when panning */
|
|
overscroll-behavior-x: none;
|
|
touch-action: none;
|
|
}
|
|
|
|
.canvas-background {
|
|
background: var(--canvas-background);
|
|
}
|
|
|
|
.react-flow__node {
|
|
font-family: inherit;
|
|
}
|
|
|
|
/* Skip hit-testing on images inside nodes — parent containers handle all
|
|
click/hover events. Prevents expensive bitmap decoding during mouse movement. */
|
|
.react-flow__node img {
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* During active panning or node dragging, disable pointer events on all node
|
|
content to eliminate expensive browser hit-testing against the DOM tree.
|
|
The class is toggled on <html> via direct DOM access (no React re-render). */
|
|
.canvas-interacting .react-flow__node > * {
|
|
pointer-events: none;
|
|
}
|
|
|
|
.canvas-default-cursor .react-flow,
|
|
.canvas-default-cursor .react-flow__pane {
|
|
cursor: default !important;
|
|
}
|
|
|
|
.canvas-pan-ready .react-flow,
|
|
.canvas-pan-ready .react-flow__pane,
|
|
.canvas-pan-ready .react-flow__node,
|
|
.canvas-pan-ready .react-flow__node * {
|
|
cursor: grab !important;
|
|
}
|
|
|
|
.canvas-pan-active .react-flow,
|
|
.canvas-pan-active .react-flow__pane,
|
|
.canvas-pan-active .react-flow__node,
|
|
.canvas-pan-active .react-flow__node * {
|
|
cursor: grabbing !important;
|
|
}
|
|
|
|
/* Remove default React Flow styling for output nodes to match custom nodes */
|
|
.react-flow__node-output {
|
|
border: none !important;
|
|
border-radius: 0 !important;
|
|
padding: 0 !important;
|
|
background: transparent !important;
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
/* Switch dimming - nodes downstream of disabled Switch outputs */
|
|
.react-flow__node.switch-dimmed {
|
|
filter: brightness(0.62) saturate(0.55);
|
|
transition: filter 250ms ease-in-out;
|
|
}
|
|
|
|
/* Skip dimming - nodes skipped due to optional empty inputs */
|
|
.react-flow__node.node-skipped {
|
|
filter: brightness(0.55) saturate(0.45);
|
|
transition: filter 250ms ease-in-out;
|
|
}
|
|
|
|
.react-flow__node:not(.switch-dimmed):not(.node-skipped) {
|
|
filter: none;
|
|
transition: filter 250ms ease-in-out;
|
|
}
|
|
|
|
.react-flow__node-group {
|
|
padding: 0 !important;
|
|
border-radius: 12px !important;
|
|
border: none !important;
|
|
background: transparent !important;
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
.react-flow__node-group.selectable:hover,
|
|
.react-flow__node-group.selectable.selected,
|
|
.react-flow__node-group.selectable:focus,
|
|
.react-flow__node-group.selectable:focus-visible {
|
|
border: none !important;
|
|
background: transparent !important;
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
.react-flow__edge-path {
|
|
stroke: var(--edge-default);
|
|
stroke-width: 2;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
}
|
|
|
|
.react-flow__edge.selected .react-flow__edge-path,
|
|
.react-flow__edge:hover .react-flow__edge-path {
|
|
stroke: var(--edge-default);
|
|
}
|
|
|
|
/* Hide resize handles but show resize cursor on edges */
|
|
.react-flow__resize-control {
|
|
opacity: 0 !important;
|
|
pointer-events: auto !important;
|
|
}
|
|
|
|
.react-flow__resize-control.left,
|
|
.react-flow__resize-control.right {
|
|
cursor: ew-resize !important;
|
|
width: 16px !important;
|
|
}
|
|
|
|
.react-flow__resize-control.top,
|
|
.react-flow__resize-control.bottom {
|
|
cursor: ns-resize !important;
|
|
height: 16px !important;
|
|
}
|
|
|
|
.react-flow__resize-control.top.left,
|
|
.react-flow__resize-control.bottom.right {
|
|
cursor: nwse-resize !important;
|
|
}
|
|
|
|
.react-flow__resize-control.top.right,
|
|
.react-flow__resize-control.bottom.left {
|
|
cursor: nesw-resize !important;
|
|
}
|
|
|
|
.react-flow__resize-control.group-resize-corner-control {
|
|
bottom: 0 !important;
|
|
right: 0 !important;
|
|
width: 16px !important;
|
|
height: 16px !important;
|
|
border: 0 !important;
|
|
background: transparent !important;
|
|
opacity: 1 !important;
|
|
z-index: 20 !important;
|
|
}
|
|
|
|
.react-flow__resize-control.bottom.right.group-resize-corner-control {
|
|
transform: translate(-8px, -8px) !important;
|
|
}
|
|
|
|
/* React Flow Controls dark theme */
|
|
.react-flow__controls {
|
|
background: var(--surface-2) !important;
|
|
border: 1px solid var(--border-subtle) !important;
|
|
border-radius: 8px !important;
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3) !important;
|
|
}
|
|
|
|
.react-flow__controls-button {
|
|
background: var(--surface-2) !important;
|
|
border-bottom: 1px solid var(--border-subtle) !important;
|
|
}
|
|
|
|
.react-flow__controls-button svg {
|
|
fill: var(--text-secondary) !important;
|
|
}
|
|
|
|
.react-flow__controls-button svg path {
|
|
fill: var(--text-secondary) !important;
|
|
}
|
|
|
|
.react-flow__controls-button:hover {
|
|
background: var(--surface-hover) !important;
|
|
}
|
|
|
|
.react-flow__controls-button:hover svg,
|
|
.react-flow__controls-button:hover svg path {
|
|
fill: #fafafa !important;
|
|
}
|
|
|
|
.react-flow__controls-button:last-child {
|
|
border-bottom: none !important;
|
|
}
|
|
|
|
body.canvas-slider-scope .ant-slider .ant-slider-rail {
|
|
background: var(--surface-active) !important;
|
|
}
|
|
|
|
body.canvas-slider-scope .ant-slider .ant-slider-track {
|
|
background: var(--text-muted) !important;
|
|
}
|
|
|
|
body.canvas-slider-scope .ant-slider .ant-slider-handle::after {
|
|
background: var(--text-secondary) !important;
|
|
box-shadow: 0 0 0 2px var(--border-subtle) !important;
|
|
}
|
|
|
|
body.canvas-slider-scope .ant-slider:hover .ant-slider-track {
|
|
background: var(--text-secondary) !important;
|
|
}
|
|
|
|
body.canvas-slider-scope .ant-slider:hover .ant-slider-handle::after,
|
|
body.canvas-slider-scope .ant-slider .ant-slider-handle:focus::after {
|
|
box-shadow: 0 0 0 2px var(--border-strong) !important;
|
|
}
|
|
|
|
/* Custom scrollbar */
|
|
* {
|
|
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
|
scrollbar-width: thin;
|
|
}
|
|
|
|
::-webkit-scrollbar {
|
|
width: var(--scrollbar-size);
|
|
height: var(--scrollbar-size);
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: var(--scrollbar-track);
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
border: 2px solid transparent;
|
|
border-radius: 999px;
|
|
background: var(--scrollbar-thumb);
|
|
background-clip: content-box;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: var(--scrollbar-thumb-hover);
|
|
background-clip: content-box;
|
|
}
|
|
|
|
::-webkit-scrollbar-corner {
|
|
background: transparent;
|
|
}
|
|
|
|
/* Make the multi-selection box pass through pointer events so we can still
|
|
interact with handles, resize controls, and other node elements */
|
|
.react-flow__nodesselection-rect {
|
|
pointer-events: none !important;
|
|
}
|
|
|
|
/* Box selection should select nodes without opening each node's own action bar. */
|
|
.box-selection-active .react-flow__node.selected .nodrag.nopan.absolute[class*="-top-"] {
|
|
display: none !important;
|
|
}
|
|
|
|
/* Edge glow dash animation for loading connectors */
|
|
@keyframes flowPulse {
|
|
0% {
|
|
stroke-dashoffset: 356;
|
|
}
|
|
100% {
|
|
stroke-dashoffset: 0;
|
|
}
|
|
}
|
|
|
|
/* Fan animation for image history */
|
|
@keyframes fanEnter {
|
|
0% {
|
|
opacity: 0;
|
|
transform: scale(0.3) translate(0, 0);
|
|
}
|
|
100% {
|
|
opacity: 1;
|
|
transform: scale(1) translate(var(--fan-x), var(--fan-y));
|
|
}
|
|
}
|
|
|
|
@keyframes fanExit {
|
|
0% {
|
|
opacity: 1;
|
|
transform: scale(1) translate(var(--fan-x), var(--fan-y));
|
|
}
|
|
100% {
|
|
opacity: 0;
|
|
transform: scale(0.3) translate(0, 0);
|
|
}
|
|
}
|
|
|
|
.animate-fan-enter {
|
|
animation: fanEnter 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
|
}
|
|
|
|
.animate-fan-exit {
|
|
animation: fanExit 0.15s ease-in forwards;
|
|
}
|
|
|