mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-07 18:27:47 +08:00
The canvas now fills the window and the chat floats on its right. draw.io is served from our own origin (public/drawio, downloaded at build time and checked against the release's SHA-256), so the app can drive the editor directly: - every AI change becomes a version card with a thumbnail; compare, restore or undo it, and Ctrl+Z on the canvas takes an AI change back in one step - shapes the AI just changed are highlighted on the canvas - selected shapes appear as a chip and are sent to the model - the app's own canvas toolbar, shape library and page tabs replace draw.io's chrome - new start screen, session rename and switcher, open .drawio files - settings split into Models & keys, Appearance, Advanced and About - phones switch between canvas and chat - dark mode keeps the diagram on a white sheet and no longer flashes With an external draw.io (NEXT_PUBLIC_DRAWIO_BASE_URL) the app falls back to draw.io's own toolbar and the features above that need same origin are off. The chat logic moved from the 1500-line chat panel into a chat engine plus small UI components; zustand holds canvas, versions, settings and UI state.
321 lines
8.2 KiB
CSS
321 lines
8.2 KiB
CSS
@import "tailwindcss";
|
|
|
|
@plugin "tailwindcss-animate";
|
|
@plugin "@tailwindcss/typography";
|
|
|
|
@custom-variant dark (&:is(.dark *));
|
|
|
|
@theme inline {
|
|
--color-background: var(--background);
|
|
--color-foreground: var(--foreground);
|
|
--font-sans: var(--font-latin), var(--font-cjk), system-ui, sans-serif;
|
|
--font-mono: var(--font-mono);
|
|
--color-ring: var(--ring);
|
|
--color-input: var(--input);
|
|
--color-border: var(--border);
|
|
--color-destructive: var(--destructive);
|
|
--color-accent-foreground: var(--accent-foreground);
|
|
--color-accent: var(--accent);
|
|
--color-muted-foreground: var(--muted-foreground);
|
|
--color-muted: var(--muted);
|
|
--color-secondary-foreground: var(--secondary-foreground);
|
|
--color-secondary: var(--secondary);
|
|
--color-primary-foreground: var(--primary-foreground);
|
|
--color-primary: var(--primary);
|
|
--color-popover-foreground: var(--popover-foreground);
|
|
--color-popover: var(--popover);
|
|
--color-card-foreground: var(--card-foreground);
|
|
--color-card: var(--card);
|
|
--radius-sm: calc(var(--radius) - 4px);
|
|
--radius-md: calc(var(--radius) - 2px);
|
|
--radius-lg: var(--radius);
|
|
--radius-xl: calc(var(--radius) + 4px);
|
|
|
|
/* App-specific tokens */
|
|
--color-canvas: var(--canvas);
|
|
--color-faint: var(--faint);
|
|
--color-marker: var(--marker);
|
|
--color-marker-soft: var(--marker-soft);
|
|
--color-marker-ink: var(--marker-ink);
|
|
--color-surface-0: var(--surface-0);
|
|
--color-surface-1: var(--surface-1);
|
|
--color-surface-2: var(--surface-2);
|
|
--color-border-subtle: var(--border-subtle);
|
|
--color-border-default: var(--border-default);
|
|
--color-interactive-hover: var(--interactive-hover);
|
|
--color-interactive-active: var(--interactive-active);
|
|
--color-success: var(--success);
|
|
--color-success-muted: var(--success-muted);
|
|
}
|
|
|
|
/*
|
|
* Light: a quiet light table. Ink for primary actions, a highlighter
|
|
* yellow reserved for what the AI just changed.
|
|
*/
|
|
:root {
|
|
--radius: 0.75rem;
|
|
|
|
--canvas: #f4f5f7;
|
|
--background: #f4f5f7;
|
|
--foreground: #14181d;
|
|
|
|
--card: #ffffff;
|
|
--card-foreground: #14181d;
|
|
--popover: #ffffff;
|
|
--popover-foreground: #14181d;
|
|
|
|
--primary: #14181d;
|
|
--primary-foreground: #ffffff;
|
|
--secondary: #f1f3f5;
|
|
--secondary-foreground: #14181d;
|
|
--muted: #f1f3f5;
|
|
--muted-foreground: #5d6672;
|
|
--faint: #687180;
|
|
--accent: #eef0f3;
|
|
--accent-foreground: #14181d;
|
|
--destructive: #c9372c;
|
|
|
|
--border: #e4e7eb;
|
|
--input: #dfe3e8;
|
|
--ring: #14181d;
|
|
|
|
--marker: #ffd84d;
|
|
--marker-soft: #fff4bf;
|
|
--marker-ink: #6b5200;
|
|
|
|
--surface-0: #ffffff;
|
|
--surface-1: #f7f8fa;
|
|
--surface-2: #f1f3f5;
|
|
--border-subtle: #eceef1;
|
|
--border-default: #e4e7eb;
|
|
--interactive-hover: #f1f3f5;
|
|
--interactive-active: #e7eaee;
|
|
--success: #1f9e5a;
|
|
--success-muted: #e3f5ea;
|
|
|
|
--shadow-float:
|
|
0 1px 2px rgb(20 24 29 / 0.05), 0 10px 30px -8px rgb(20 24 29 / 0.14);
|
|
--shadow-pop:
|
|
0 1px 2px rgb(20 24 29 / 0.06), 0 18px 48px -12px rgb(20 24 29 / 0.22);
|
|
|
|
--duration-normal: 200ms;
|
|
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
|
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
}
|
|
|
|
/*
|
|
* Han characters are drawn differently in Simplified Chinese, Traditional
|
|
* Chinese and Japanese: each language lists its own system fonts first.
|
|
*/
|
|
:root {
|
|
--font-cjk:
|
|
"PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC",
|
|
"Microsoft YaHei", "Hiragino Sans", "Meiryo";
|
|
}
|
|
:root:lang(ja) {
|
|
--font-cjk:
|
|
"Hiragino Sans", "Hiragino Kaku Gothic ProN", "Noto Sans CJK JP",
|
|
"Yu Gothic UI", "Meiryo", "PingFang SC";
|
|
}
|
|
:root:lang(zh-Hant) {
|
|
--font-cjk:
|
|
"PingFang TC", "Noto Sans CJK TC", "Microsoft JhengHei",
|
|
"Hiragino Sans", "PingFang SC";
|
|
}
|
|
|
|
/*
|
|
* Dark: a blueprint-blue table. Surfaces get lighter the higher they float.
|
|
* The diagram stays on a white sheet so it looks the way it will export;
|
|
* primary actions are paper-white, and amber only marks AI changes.
|
|
*/
|
|
.dark {
|
|
--canvas: #0f2440;
|
|
--background: #0c1a29;
|
|
--foreground: #e9f0f6;
|
|
|
|
--card: #16304f;
|
|
--card-foreground: #e9f0f6;
|
|
--popover: #1b3a5d;
|
|
--popover-foreground: #e9f0f6;
|
|
|
|
--primary: #e9f0f6;
|
|
--primary-foreground: #0c1a29;
|
|
--secondary: #1d3b5e;
|
|
--secondary-foreground: #e9f0f6;
|
|
--muted: #1d3b5e;
|
|
--muted-foreground: #a8bbce;
|
|
--faint: #8aa0b6;
|
|
--accent: #21426a;
|
|
--accent-foreground: #e9f0f6;
|
|
--destructive: #f07167;
|
|
|
|
--border: rgb(170 200 230 / 0.16);
|
|
--input: rgb(170 200 230 / 0.24);
|
|
--ring: #e9f0f6;
|
|
|
|
--marker: #f5b13d;
|
|
--marker-soft: rgb(245 177 61 / 0.16);
|
|
--marker-ink: #f7c46b;
|
|
|
|
--surface-0: #16304f;
|
|
--surface-1: #132b48;
|
|
--surface-2: #1d3b5e;
|
|
--border-subtle: rgb(170 200 230 / 0.12);
|
|
--border-default: rgb(170 200 230 / 0.2);
|
|
--interactive-hover: #21426a;
|
|
--interactive-active: #264a75;
|
|
--success: #4fd18b;
|
|
--success-muted: rgb(79 209 139 / 0.14);
|
|
|
|
--shadow-float:
|
|
0 0 0 1px rgb(170 200 230 / 0.1), 0 14px 34px -12px rgb(0 0 0 / 0.55);
|
|
--shadow-pop:
|
|
0 0 0 1px rgb(170 200 230 / 0.14), 0 22px 56px -14px rgb(0 0 0 / 0.65);
|
|
}
|
|
|
|
@layer base {
|
|
* {
|
|
@apply border-border outline-ring/50;
|
|
}
|
|
html {
|
|
color-scheme: light;
|
|
}
|
|
html.dark {
|
|
color-scheme: dark;
|
|
}
|
|
body {
|
|
@apply bg-background text-foreground font-sans;
|
|
font-size: 14px;
|
|
line-height: 1.55;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
::selection {
|
|
background: var(--marker-soft);
|
|
color: inherit;
|
|
}
|
|
}
|
|
|
|
/* Fix for Radix ScrollArea viewport horizontal overflow */
|
|
[data-slot="scroll-area-viewport"] > div {
|
|
display: block !important;
|
|
width: 100% !important;
|
|
}
|
|
|
|
@layer utilities {
|
|
.scrollbar-thin {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: color-mix(in oklab, var(--foreground) 18%, transparent)
|
|
transparent;
|
|
}
|
|
.scrollbar-thin::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
.scrollbar-thin::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
.scrollbar-thin::-webkit-scrollbar-thumb {
|
|
background-color: color-mix(
|
|
in oklab,
|
|
var(--foreground) 18%,
|
|
transparent
|
|
);
|
|
border-radius: 3px;
|
|
}
|
|
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
|
background-color: color-mix(
|
|
in oklab,
|
|
var(--foreground) 30%,
|
|
transparent
|
|
);
|
|
}
|
|
|
|
/* Diagram pictures always render as on paper, also in dark mode
|
|
(draw.io SVGs use light-dark() colors) */
|
|
.sheet-light {
|
|
color-scheme: light;
|
|
}
|
|
.shadow-float {
|
|
box-shadow: var(--shadow-float);
|
|
}
|
|
.shadow-pop {
|
|
box-shadow: var(--shadow-pop);
|
|
}
|
|
/* Older names kept for components that still use them */
|
|
.shadow-soft {
|
|
box-shadow: var(--shadow-float);
|
|
}
|
|
.shadow-dialog {
|
|
box-shadow: var(--shadow-pop);
|
|
}
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(6px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes messageIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(8px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* The first prompt moves from the middle of the canvas into the panel */
|
|
@keyframes panelIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateX(12px) scale(0.985);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateX(0) scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes check-pop {
|
|
0% {
|
|
transform: scale(0.8);
|
|
opacity: 0;
|
|
}
|
|
50% {
|
|
transform: scale(1.1);
|
|
}
|
|
100% {
|
|
transform: scale(1);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.animate-fade-in {
|
|
animation: fadeIn 0.25s var(--ease-out) forwards;
|
|
}
|
|
.animate-message-in {
|
|
animation: messageIn 0.22s var(--ease-out) forwards;
|
|
}
|
|
.animate-panel-in {
|
|
animation: panelIn 0.32s var(--ease-out) both;
|
|
}
|
|
.animate-check-pop {
|
|
animation: check-pop 0.25s var(--ease-spring) forwards;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.animate-fade-in,
|
|
.animate-message-in,
|
|
.animate-panel-in,
|
|
.animate-check-pop {
|
|
animation: none;
|
|
}
|
|
}
|