Files
zui/frontend/src/styles.css
2026-03-15 21:46:08 +01:00

444 lines
11 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@import "shadcn/dist/tailwind.css";
@import "@blocknote/core/fonts/inter.css";
@import "@blocknote/shadcn/style.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body,
#root {
height: 100%;
}
body {
@apply bg-background text-foreground;
}
.reactflow-wrapper {
width: 100%;
height: 100%;
min-height: 0;
}
/* Canvas pane cursors: crosshair for selection box, grab/grabbing for middle-click pan */
.react-flow__pane {
cursor: crosshair;
}
.react-flow.react-flow--panning .react-flow__pane {
cursor: grabbing;
}
.react-flow.react-flow--selecting .react-flow__pane {
cursor: crosshair;
}
/* Node specific overrides */
.react-flow__node {
display: block;
overflow: visible;
}
/* Focused node: clear focus ring so keyboard/screen reader users see which node is active */
.react-flow__node:focus {
outline: none;
}
.react-flow__node:focus-visible>div:first-child {
border-color: hsl(var(--primary) / 0.5);
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.15);
}
.dark .react-flow__node:focus-visible>div:first-child {
border-color: hsl(var(--primary) / 0.35);
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.1);
}
.react-flow__node .react-flow__handle {
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.06);
}
.dark .react-flow__node .react-flow__handle {
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.15);
}
/* Connection validation: valid targets clearly highlighted (from drag start and when hovering), invalid dimmed while connecting */
.react-flow__handle.react-flow__handle-connecting.react-flow__handle-valid,
.react-flow__handle.connection-valid-target {
background: hsl(var(--primary)) !important;
color: hsl(var(--primary-foreground));
box-shadow: 0 0 0 3px hsl(var(--primary)), 0 0 12px 2px hsl(var(--primary) / 0.5) !important;
opacity: 1 !important;
}
.react-flow__handle.react-flow__handle-connecting.react-flow__handle-valid svg,
.react-flow__handle.connection-valid-target svg {
color: inherit;
}
.dark .react-flow__handle.react-flow__handle-connecting.react-flow__handle-valid,
.dark .react-flow__handle.connection-valid-target {
background: hsl(var(--primary)) !important;
box-shadow: 0 0 0 3px hsl(var(--primary)), 0 0 14px 4px hsl(var(--primary) / 0.6) !important;
}
.react-flow__handle.react-flow__handle-connecting:not(.react-flow__handle-valid):not(.connection-valid-target),
.react-flow__handle.connection-invalid-target {
opacity: 0.25 !important;
pointer-events: none;
}
/* Viewport control buttons: same look as React Flow controls; Lucide icons use stroke. */
.rendering-viewport .react-flow__controls-button svg {
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Rendering node: diagram/wireframe SVG must fit inside the content area. */
.rendering-diagram {
display: flex;
align-items: center;
justify-content: center;
}
.rendering-diagram svg {
display: block;
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
}
.dark .rendering-diagram svg {
filter: invert(0.92) hue-rotate(180deg);
}
/* Wireframe: fit HTML preview in viewport (inspired by https://www.wireweave.org/ ). */
.rendering-wireframe__viewport {
container-type: inline-size;
container-name: wireframe;
flex: 1;
min-height: 0;
overflow: auto;
display: flex;
justify-content: center;
align-items: flex-start;
}
.rendering-wireframe__content {
width: 1200px;
/* Scale to fit container width so content stays within the node */
zoom: calc(100cqw / 1200px);
min-height: min-content;
}
/* Animated React Flow edges: ant trail always on; color shows updating path */
.react-flow__edge path.animated-edge-path,
.animated-edge-path {
stroke-width: 4;
stroke: hsl(var(--foreground) / 0.5);
fill: none;
stroke-dasharray: 8 6;
stroke-dashoffset: 0;
animation: edge-flow 0.5s linear infinite;
will-change: stroke-dashoffset;
transform: translateZ(0);
backface-visibility: hidden;
transition: stroke 0.2s ease-out;
}
.react-flow__edge path.animated-edge-path.animated-edge-path--updating,
.animated-edge-path.animated-edge-path--updating {
stroke: hsl(217 91% 60%);
transition: none;
}
.react-flow__edge path.animated-edge-path.animated-edge-path--paused,
.animated-edge-path.animated-edge-path--paused {
stroke: hsl(45 98% 50%);
transition: none;
}
.react-flow__edge path.animated-edge-path.animated-edge-path--error,
.animated-edge-path.animated-edge-path--error {
stroke: hsl(0 70% 50%);
transition: none;
}
@keyframes edge-flow {
0% {
stroke-dashoffset: 14;
}
100% {
stroke-dashoffset: 0;
}
}
/* Connection path roles: pushing (trigger) — classes on BaseNode root. Updating nodes have no extra border so rendering doesnt flash. */
/* Connection path trigger: amber cue to match manual/dirty state (e.g. Run button). */
.connection-path-trigger {
/*box-shadow: inset 3px 0 0 hsl(38 92% 50%);*/
}
.dark .connection-path-trigger {
/*box-shadow: inset 3px 0 0 hsl(45 93% 58%); */
}
/* NodeResizer: snappy drag; no border/background on handles; keep resize cursors. */
.react-flow__resize-control {
touch-action: none;
user-select: none;
}
.react-flow__resize-control,
.react-flow__resize-control * {
transition: none;
}
/* Subtle resize handles so they dont compete with the selected-node ring (primary). Shown only when node is selected. */
/* Single bottom-right resize handle: no border, transparent so only the corner grab area remains. */
/* Handles: invisible (no border/background); cursor unchanged so resize cursors stay. */
.base-node-resize-handle,
.base-node-resize-handle * {
border: none !important;
background: transparent !important;
}
/* Resize cursor per position (keep cursor style change on hover). */
.react-flow__resize-control.top.left {
cursor: nw-resize;
}
.react-flow__resize-control.top.right {
cursor: ne-resize;
}
.react-flow__resize-control.bottom.left {
cursor: sw-resize;
}
.react-flow__resize-control.bottom.right {
cursor: se-resize;
}
.react-flow__resize-control.top {
cursor: n-resize;
}
.react-flow__resize-control.right {
cursor: e-resize;
}
.react-flow__resize-control.bottom {
cursor: s-resize;
}
.react-flow__resize-control.left {
cursor: w-resize;
}
/* Small helper for monospace pre output */
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, 'Roboto Mono', 'Courier New', monospace;
}
/* Compact scrollbars that respect light/dark mode */
* {
scrollbar-width: thin;
scrollbar-color: hsl(var(--muted-foreground) / 0.35) transparent;
}
.dark * {
scrollbar-color: hsl(var(--muted-foreground) / 0.4) transparent;
}
*::-webkit-scrollbar {
width: 6px;
height: 6px;
}
*::-webkit-scrollbar-track {
background: transparent;
}
*::-webkit-scrollbar-thumb {
background: hsl(var(--muted-foreground) / 0.35);
border-radius: 3px;
}
*::-webkit-scrollbar-thumb:hover {
background: hsl(var(--muted-foreground) / 0.5);
}
*::-webkit-scrollbar-corner {
background: transparent;
}
/* Override Tailwind preflight: use theme border color instead of hardcoded #e5e7eb
so nodes and sidebar borders respect the design system. */
*,
::before,
::after {
border-color: hsl(var(--border));
}
.dark *::-webkit-scrollbar-thumb {
background: hsl(var(--muted-foreground) / 0.4);
}
.dark *::-webkit-scrollbar-thumb:hover {
background: hsl(var(--muted-foreground) / 0.55);
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
/* Nunjucks tokens in code editor: distinct from main language (Prism) */
.token.nunjucks-var {
color: #7c3aed;
}
.token.nunjucks-tag {
color: #c2410c;
}
.token.nunjucks-comment {
color: #0d9488;
}
.dark .token.nunjucks-var {
color: #a78bfa;
}
.dark .token.nunjucks-tag {
color: #fb923c;
}
.dark .token.nunjucks-comment {
color: #2dd4bf;
}
/* Prism default theme adds a background to .token.operator (e.g. "="); remove it in our code editor */
.code-editor .token.operator,
.code-editor .token.entity,
.code-editor .token.url {
background: none;
}
/* Recollection flipping card: Logos (front) / Flux (back). GPU-accelerated 3D flip. */
.recollection-flip-container {
flex: 1;
min-height: 0;
overflow: hidden;
}
.recollection-flip-card {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.recollection-flip-card[data-flipped="true"] {
transform: rotateY(-180deg);
}
.recollection-flip-face {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
overflow: hidden;
}
/* Non-visible face must not capture pointer/wheel so scroll and click work on the visible face only */
.recollection-flip-card[data-flipped="false"] .recollection-flip-face-back {
pointer-events: none;
}
.recollection-flip-card[data-flipped="true"] .recollection-flip-face-front {
pointer-events: none;
}
.recollection-flip-face-front {
transform: rotateY(0deg);
}
.recollection-flip-face-back {
transform: rotateY(-180deg);
}