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

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